From 93232d30c01e9fd9a77f4be82a7b3e82f442773f Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 11 Apr 2024 19:03:44 -0400 Subject: [PATCH 001/778] the ACTUAL fix Soul Collector's collecting souls seems to not work, i dont think this is a problem that i caused every NA can only be set once now fixed Jailer typo so it actually can exe NCs --- Modules/CustomRolesHelper.cs | 44 +- Modules/ExtendedPlayerControl.cs | 2 + Modules/GameState.cs | 2 + Modules/OptionHolder.cs | 30 +- Patches/CheckGameEndPatch.cs | 19 +- Resources/Lang/en_US.json | 7144 +++++++++++----------- Resources/roleColor.json | 8 +- Roles/(Ghosts)/Impostor/Bloodmoon.cs | 1 + Roles/AddOns/Common/Susceptible.cs | 20 + Roles/Core/AssignManager/RoleAssign.cs | 126 +- Roles/Crewmate/Captain.cs | 5 +- Roles/Crewmate/Jailer.cs | 5 +- Roles/Crewmate/Snitch.cs | 6 +- Roles/Neutral/Baker.cs | 170 + Roles/{Impostor => Neutral}/Berserker.cs | 77 +- Roles/Neutral/Executioner.cs | 3 + Roles/Neutral/PlagueBearer.cs | 5 +- Roles/Neutral/SoulCollector.cs | 51 +- main.cs | 10 +- 19 files changed, 4111 insertions(+), 3617 deletions(-) create mode 100644 Roles/Neutral/Baker.cs rename Roles/{Impostor => Neutral}/Berserker.cs (56%) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 43b9057c13..5601837242 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -298,7 +298,7 @@ public static bool IsNeutral(this CustomRoles role) //FFA CustomRoles.Killer) return true; - return role.IsNK() || role.IsNonNK(); + return role.IsNK() || role.IsNonNK() || role.IsNA(); } public static bool IsNK(this CustomRoles role) { @@ -336,10 +336,8 @@ CustomRoles.Shroud or CustomRoles.Virus or CustomRoles.BloodKnight or CustomRoles.Spiritcaller or - CustomRoles.PlagueBearer or CustomRoles.Agitater or - CustomRoles.RuthlessRomantic or - CustomRoles.Pestilence; + CustomRoles.RuthlessRomantic; } public static bool IsNonNK(this CustomRoles role) // ROLE ASSIGNING, NOT NEUTRAL TYPE { @@ -356,7 +354,6 @@ CustomRoles.Maverick or CustomRoles.Opportunist or CustomRoles.Pursuer or CustomRoles.Shaman or - CustomRoles.SoulCollector or CustomRoles.CursedSoul or CustomRoles.Doomsayer or CustomRoles.Executioner or @@ -383,6 +380,26 @@ CustomRoles.VengefulRomantic or CustomRoles.SchrodingersCat or CustomRoles.Provocateur; } + public static bool IsNA(this CustomRoles role) + { + return role is + CustomRoles.PlagueBearer or + CustomRoles.Pestilence or + CustomRoles.Berserker or + CustomRoles.War or + CustomRoles.SoulCollector or + CustomRoles.Death or + CustomRoles.Baker or + CustomRoles.Famine; + } + public static bool IsTNA(this CustomRoles role) + { + return role is + CustomRoles.Pestilence or + CustomRoles.War or + CustomRoles.Death or + CustomRoles.Famine; + } public static bool IsNB(this CustomRoles role) { return role is @@ -451,7 +468,6 @@ CustomRoles.Warlock or CustomRoles.Undertaker or CustomRoles.RiftMaker or CustomRoles.Ninja or - CustomRoles.Berserker or CustomRoles.Bloodmoon or CustomRoles.Anonymous or CustomRoles.Visionary or @@ -836,8 +852,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.CursedWolf) || pc.Is(CustomRoles.Masochist) - || pc.Is(CustomRoles.PlagueBearer) - || pc.Is(CustomRoles.Pestilence)) + || pc.IsNeutralApocalypse()) return false; if ((pc.GetCustomRole().IsCrewmate() && !Fragile.CrewCanBeFragile.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Fragile.NeutralCanBeFragile.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Fragile.ImpCanBeFragile.GetBool())) return false; @@ -1392,8 +1407,8 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Shroud => CountTypes.Shroud, CustomRoles.Werewolf => CountTypes.Werewolf, CustomRoles.Wraith => CountTypes.Wraith, - CustomRoles.Pestilence => CountTypes.Pestilence, - CustomRoles.PlagueBearer => CountTypes.PlagueBearer, + CustomRoles.Pestilence or CustomRoles.PlagueBearer or CustomRoles.SoulCollector or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.Berserker or CustomRoles.War + => CountTypes.Apocalypse, CustomRoles.Agitater => CountTypes.Agitater, CustomRoles.Parasite => CountTypes.Impostor, CustomRoles.SerialKiller => CountTypes.SerialKiller, @@ -1467,11 +1482,10 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Pickpocket => CustomWinner.Pickpocket, CustomRoles.Traitor => CustomWinner.Traitor, CustomRoles.Vulture => CustomWinner.Vulture, - CustomRoles.Pestilence => CustomWinner.Pestilence, + CustomRoles.Apocalypse => CustomWinner.Apocalypse, CustomRoles.Medusa => CustomWinner.Medusa, CustomRoles.Spiritcaller => CustomWinner.Spiritcaller, CustomRoles.Glitch => CustomWinner.Glitch, - CustomRoles.PlagueBearer => CustomWinner.Plaguebearer, CustomRoles.Masochist => CustomWinner.Masochist, CustomRoles.Doomsayer => CustomWinner.Doomsayer, CustomRoles.Shroud => CustomWinner.Shroud, @@ -1500,8 +1514,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CountTypes.Shroud => CustomRoles.Shroud, CountTypes.Werewolf => CustomRoles.Werewolf, CountTypes.Wraith => CustomRoles.Wraith, - CountTypes.Pestilence => CustomRoles.Pestilence, - CountTypes.PlagueBearer => CustomRoles.PlagueBearer, + CountTypes.Apocalypse => CustomRoles.Apocalypse, CountTypes.Agitater => CustomRoles.Agitater, CountTypes.SerialKiller => CustomRoles.SerialKiller, CountTypes.Quizmaster => CustomRoles.Quizmaster, @@ -1560,9 +1573,8 @@ public enum CountTypes Traitor, Medusa, Spiritcaller, - Pestilence, Quizmaster, - PlagueBearer, + Apocalypse, Glitch, Arsonist, Huntsman, diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 495a3abd9d..e34f03c25c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -696,6 +696,8 @@ public static List GetPlayersInAbilityRangeSorted(this PlayerCont public static bool IsNeutralBenign(this PlayerControl player) => player.GetCustomRole().IsNB(); public static bool IsNeutralEvil(this PlayerControl player) => player.GetCustomRole().IsNE(); public static bool IsNeutralChaos(this PlayerControl player) => player.GetCustomRole().IsNC(); + public static bool IsNeutralApocalypse(this PlayerControl player) => player.GetCustomRole().IsNA(); + public static bool IsTransformedNeutralApocalypse(this PlayerControl player) => player.GetCustomRole().IsTNA(); public static bool IsNonNeutralKiller(this PlayerControl player) => player.GetCustomRole().IsNonNK(); public static bool KnowDeathReason(this PlayerControl seer, PlayerControl target) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 1bb35477b3..8755a966bd 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -299,6 +299,8 @@ public enum DeathReason Slice, BloodLet, WrongAnswer, + Starved, + Armageddon, //Please add all new roles with deathreason & new deathreason in Susceptible.CallEnabledAndChange etc = -1, diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 670c1f322b..c0e50ca430 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -478,6 +478,8 @@ private enum RatesZeroOne public static OptionItem NonNeutralKillingRolesMaxPlayer; public static OptionItem NeutralKillingRolesMinPlayer; public static OptionItem NeutralKillingRolesMaxPlayer; + public static OptionItem NeutralApocalypseRolesMinPlayer; + public static OptionItem NeutralApocalypseRolesMaxPlayer; public static OptionItem NeutralRoleWinTogether; public static OptionItem NeutralWinTogether; @@ -666,6 +668,14 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetValueFormat(OptionFormat.Players); + NeutralApocalypseRolesMinPlayer = IntegerOptionItem.Create(60022, "NeutralApocalypseRolesMinPlayer", new(0, 4, 1), 0, TabGroup.NeutralRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetValueFormat(OptionFormat.Players); + NeutralApocalypseRolesMaxPlayer = IntegerOptionItem.Create(60023, "NeutralApocalypseRolesMaxPlayer", new(0, 4, 1), 0, TabGroup.NeutralRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Players); + NeutralRoleWinTogether = BooleanOptionItem.Create(60017, "NeutralRoleWinTogether", false, TabGroup.NeutralRoles, false) .SetGameMode(CustomGameMode.Standard) @@ -711,11 +721,6 @@ public static void Load() */ Arrogance.SetupCustomOption(); - /* - * Berserker - */ - Berserker.SetupCustomOption(); - /* * Bomber */ @@ -1500,8 +1505,6 @@ public static void Load() */ Solsticer.SetupCustomOption(); - SoulCollector.SetupCustomOption(); - Terrorist.SetupCustomOptions(); Vector.SetupCustomOptions(); @@ -1550,8 +1553,6 @@ public static void Load() Poisoner.SetupCustomOption(); - PlagueBearer.SetupCustomOption(); - PlagueDoctor.SetupCustomOption(); PotionMaster.SetupCustomOption(); @@ -1575,6 +1576,17 @@ public static void Load() Wraith.SetupCustomOption(); + TextOptionItem.Create(10000015, "RoleType.NeutralApocalypse", TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(127, 140, 141, byte.MaxValue)); + + Baker.SetupCustomOption(); + + Berserker.SetupCustomOption(); + + PlagueBearer.SetupCustomOption(); + + SoulCollector.SetupCustomOption(); #endregion #region Add-Ons Settings diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index c212a06fa6..4fdf4875f9 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -7,6 +7,7 @@ using TOHE.Roles.Neutral; using static TOHE.Translator; using TOHE.Roles.AddOns.Common; +using System.Threading.Channels; namespace TOHE; @@ -81,6 +82,13 @@ public static bool Prefix() CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); } break; + case CustomWinner.Apocalypse: + if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless)) + && !CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + break; case CustomWinner.Cultist: if (pc.Is(CustomRoles.Charmed) && !CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) { @@ -155,7 +163,7 @@ public static bool Prefix() foreach (var pc in Main.AllPlayerControls) { if (pc.Is(CustomRoles.Phantom) && pc.GetPlayerTaskState().IsTaskFinished && pc.Data.IsDead - && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Doppelganger || CustomWinnerHolder.WinnerTeam == CustomWinner.PotionMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Cultist || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Agitater || CustomWinnerHolder.WinnerTeam == CustomWinner.Pestilence || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Spiritcaller || CustomWinnerHolder.WinnerTeam == CustomWinner.Quizmaster ) && (Phantom.PhantomSnatchesWin.GetBool() || CustomWinnerHolder.WinnerTeam == CustomWinner.PlagueDoctor)))) //|| CustomWinnerHolder.WinnerTeam == CustomWinner.Occultist + && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Doppelganger || CustomWinnerHolder.WinnerTeam == CustomWinner.PotionMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Cultist || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Agitater || CustomWinnerHolder.WinnerTeam == CustomWinner.Apocalypse || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Spiritcaller || CustomWinnerHolder.WinnerTeam == CustomWinner.Quizmaster ) && (Phantom.PhantomSnatchesWin.GetBool() || CustomWinnerHolder.WinnerTeam == CustomWinner.PlagueDoctor)))) //|| CustomWinnerHolder.WinnerTeam == CustomWinner.Occultist { reason = GameOverReason.ImpostorByKill; if (!CustomWinnerHolder.CheckForConvertedWinner(pc.PlayerId)) @@ -168,7 +176,7 @@ public static bool Prefix() foreach (var pc in Main.AllPlayerControls) { if (pc.Is(CustomRoles.CursedSoul) && !pc.Data.IsDead - && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Doppelganger || CustomWinnerHolder.WinnerTeam == CustomWinner.PotionMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Cultist || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Agitater || CustomWinnerHolder.WinnerTeam == CustomWinner.Pestilence || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Jester || CustomWinnerHolder.WinnerTeam == CustomWinner.Executioner || CustomWinnerHolder.WinnerTeam == CustomWinner.PlagueDoctor)))) // || CustomWinnerHolder.WinnerTeam == CustomWinner.Occultist + && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Doppelganger || CustomWinnerHolder.WinnerTeam == CustomWinner.PotionMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Cultist || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Agitater || CustomWinnerHolder.WinnerTeam == CustomWinner.Apocalypse || CustomWinnerHolder.WinnerTeam == CustomWinner.Bandit || CustomWinnerHolder.WinnerTeam == CustomWinner.Jester || CustomWinnerHolder.WinnerTeam == CustomWinner.Executioner || CustomWinnerHolder.WinnerTeam == CustomWinner.PlagueDoctor)))) // || CustomWinnerHolder.WinnerTeam == CustomWinner.Occultist { reason = GameOverReason.ImpostorByKill; if (!CustomWinnerHolder.CheckForConvertedWinner(pc.PlayerId)) @@ -363,7 +371,12 @@ public static bool Prefix() CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Romantic); } } + foreach (var pc in Main.AllPlayerControls.Where(x => x.IsNeutralApocalypse() && Main.AllAlivePlayerControls.All(p => p.IsNeutralApocalypse()))) + { + if (!CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } foreach (var pc in Main.AllPlayerControls.Where(x => x.Is(CustomRoles.RuthlessRomantic)).ToArray()) { if (Romantic.BetPlayer.TryGetValue(pc.PlayerId, out var betTarget) && ( @@ -628,7 +641,7 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) var winnerRole = winners[0].Key.GetNeutralCustomRoleFromCountType(); reason = GameOverReason.ImpostorByKill; CustomWinnerHolder.ResetAndSetWinner(winnerRole.GetNeutralCustomWinnerFromRole()); - CustomWinnerHolder.WinnerRoles.Add(winnerRole); + if (winnerRole != CustomRoles.Apocalypse) CustomWinnerHolder.WinnerRoles.Add(winnerRole); } else if (winnnerLength == 0) { diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index bbb9944912..bcc2a4da73 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1,3554 +1,3594 @@ { - "LanguageID": "0", - "HostText": "Host", - "HostColor": "#902efd", - "IconColor": "#4bf4ff", - "Icon": "♥", - "HideHostText": "Hide 'Host♥' Text", - "NameColor": "#ffc0cb", - "kofi": "Ko-Fi", - "update": "Update", - "GitHub": "GitHub", - "Discord": "Discord", - "Website": "Website", - "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", - - "SubText.Crewmate": "Find and exile the Impostors", - "SubText.Impostor": "Sabotage and kill everyone", - "SubText.Neutral": "Work alone to achieve your victory", - "SubText.Madmate": "Help the Impostors", - - "TypeImpostor": "Impostors", - "TypeCrewmate": "Crewmates", - "TypeNeutral": "Neutrals", - "TypeAddon": "Add-ons", - "GuesserMode": "Guesser Mode", - - "TeamImpostor": "Impostor", - "TeamNeutral": "Neutral", - "TeamCrewmate": "Crewmate", - "TeamMadmate": "Madmate", - - "YouAreCrewmate": "You are a Crewmate", - "YouAreImpostor": "You are an Impostor", - "YouAreNeutral": "You are a Neutral", - "YouAreMadmate": "You are a Madmate", - - - "Role_Crewmate": "Crewmate", - "Role_Jester": "Jester", - "Role_Opportunist": "Opportunist", - "Role_Celebrity": "Celebrity", - "Role_Bodyguard": "Bodyguard", - "Role_Dictator": "Dictator", - "Role_Mayor": "Mayor", - "Role_Doctor": "Doctor", - "Role_Maverick": "Maverick", - "Role_Pursuer": "Pursuer", - "Role_Follower": "Follower", - "Role_Amnesiac": "Amnesiac", - "Role_Imitator": "Imitator", - "Role_Sheriff": "Sheriff", - "Role_Knight": "Knight", - "Role_Deputy": "Deputy", - "Role_NoChange": "Don't change the role", - - "CrewmatesCanGuess": "Crewmates can guess", - "ImpostorsCanGuess": "Impostors can guess", - "NeutralKillersCanGuess": "Neutral Killers can guess", - "PassiveNeutralsCanGuess": "Passive Neutrals can guess", - - "CanGuessAddons": "Can Guess Add-ons", - "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", - "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", - "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", - "GuessImmune": "Sorry, but target is immune to being guessed!", - - - "GM": "Game Master", - "Sunnyboy": "Sunnyboy", - "Bard": "Bard", - "Nuker": "Nuker", - "Crewmate": "Crewmate", - "CrewmateTOHE": "Crewmate", - "Engineer": "Engineer", - "EngineerTOHE": "Engineer", - "Scientist": "Scientist", - "ScientistTOHE": "Scientist", - "GuardianAngel": "Guardian Angel", - "GuardianAngelTOHE": "Guardian Angel", - "Impostor": "Impostor", - "ImpostorTOHE": "Impostor", - "Shapeshifter": "Shapeshifter", - "ShapeshifterTOHE": "Shapeshifter", - - "BountyHunter": "Bounty Hunter", - "Fireworker": "Fireworker", - "Mercenary": "Mercenary", - "ShapeMaster": "Shapemaster", - "Vampire": "Vampire", - "Vampiress": "Vampiress", - "Warlock": "Warlock", - "Ninja": "Ninja", - "Zombie": "Zombie", - "Anonymous": "Anonymous", - "Miner": "Miner", - "KillingMachine": "Killing Machine", - "Escapist": "Escapist", - "Witch": "Witch", - "Nemesis": "Nemesis", - "Bloodmoon": "Bloodmoon", - "Puppeteer": "Puppeteer", - "Mastermind": "Mastermind", - "TimeThief": "Time Thief", - "Sniper": "Sniper", - "Undertaker": "Undertaker", - "RiftMaker": "Rift Maker", - "EvilTracker": "Evil Tracker", - "EvilGuesser": "Evil Guesser", - "AntiAdminer": "Anti Adminer", - "Arrogance": "Arrogance", - "Bomber": "Bomber", - "Scavenger": "Scavenger", - "Trapster": "Trapster", - "Gangster": "Gangster", - "Cleaner": "Cleaner", - "Lightning": "Lightning", - "Greedy": "Greedy", - "CursedWolf": "Cursed Wolf", - "SoulCatcher": "Soul Catcher", - "QuickShooter": "Quick Shooter", - "Camouflager": "Camouflager", - "Eraser": "Eraser", - "Butcher": "Butcher", - "Hangman": "Hangman", - "Swooper": "Swooper", - "Crewpostor": "Crewpostor", - "Wildling": "Wildling", - "Trickster": "Trickster", - "Vindicator": "Vindicator", - "Parasite": "Parasite", - "Disperser": "Disperser", - "Inhibitor": "Inhibitor", - "Saboteur": "Saboteur", - "Councillor": "Councillor", - "Dazzler": "Dazzler", - "Deathpact": "Deathpact", - "Devourer": "Devourer", - "Consigliere": "Consigliere", - "Morphling": "Morphling", - "Twister": "Twister", - "Lurker": "Lurker", - "Visionary": "Visionary", - "Refugee": "Refugee", - "Underdog": "Underdog", - "Ludopath": "Ludopath", - "Godfather": "Godfather", - "Chronomancer": "Chronomancer", - "Pitfall": "Pitfall", - "Berserker": "Berserker", - "EvilMini": "Evil Mini", - "Blackmailer": "Blackmailer", - "Instigator": "Instigator", - "LazyGuy": "Lazy Guy", - "SuperStar": "Super Star", - "Celebrity": "Celebrity", - "Cleanser": "Cleanser", - "Keeper": "Keeper", - "Knight": "Knight", - "Mayor": "Mayor", - "Psychic": "Psychic", - "Mechanic": "Mechanic", - "Sheriff": "Sheriff", - "Vigilante": "Vigilante", - "Jailer": "Jailer", - "CopyCat": "Copycat", - "Snitch": "Snitch", - "Marshall": "Marshall", - "SpeedBooster": "Speed Booster", - "Doctor": "Doctor", - "Dictator": "Dictator", - "Detective": "Detective", - "NiceGuesser": "Nice Guesser", - "GuessMaster": "Guess Master", - "Transporter": "Transporter", - "TimeManager": "Time Manager", - "Veteran": "Veteran", - "Bastion": "Bastion", - "Bodyguard": "Bodyguard", - "Deceiver": "Deceiver", - "Grenadier": "Grenadier", - "Medic": "Medic", - "FortuneTeller": "Fortune Teller", - "Judge": "Judge", - "Mortician": "Mortician", - "Medium": "Medium", - "Pacifist": "Pacifist", - "Observer": "Observer", - "Monarch": "Monarch", - "Overseer": "Overseer", - "Coroner": "Coroner", - "Tracker": "Tracker", - "Merchant": "Merchant", - "President": "President", - "Hawk": "Hawk", - "Retributionist": "Retributionist", - "Deputy": "Deputy", - "Investigator": "Investigator", - "Guardian": "Guardian", - "Addict": "Addict", - "Mole": "Mole", - "Alchemist": "Alchemist", - "Tracefinder": "Tracefinder", - "Oracle": "Oracle", - "Spiritualist": "Spiritualist", - "Chameleon": "Chameleon", - "Inspector": "Inspector", - "Captain": "Captain", - "Admirer": "Admirer", - "TimeMaster": "Time Master", - "Crusader": "Crusader", - "Reverie": "Reverie", - "Lookout": "Lookout", - "Telecommunication": "Telecommunication", - "Lighter": "Lighter", - "TaskManager": "Task Manager", - "Witness": "Witness", - "Swapper": "Swapper", - "ChiefOfPolice": "Police Commissioner", - "NiceMini": "Nice Mini", - "Mini": "Mini", - "Spy": "Spy", - "Randomizer": "Randomizer", - "Enigma": "Enigma", - "Jester": "Jester", - "Arsonist": "Arsonist", - "Pyromaniac": "Pyromaniac", - "Kamikaze": "Kamikaze", - "Huntsman": "Huntsman", - "Terrorist": "Terrorist", - "Executioner": "Executioner", - "Lawyer": "Lawyer", - "Opportunist": "Opportunist", - "Vector": "Vector", - "Jackal": "Jackal", - "God": "God", - "Innocent": "Innocent", - "Stealth": "Stealth", - "Penguin": "Penguin", - "Pelican": "Pelican", - "PlagueDoctor": "Plague Scientist", - "Revolutionist": "Revolutionist", - "Hater": "Hater", - "Demon": "Demon", - "Stalker": "Stalker", - "Workaholic": "Workaholic", - "Solsticer": "Solsticer", - "Collector": "Collector", - "Provocateur": "Provocateur", - "BloodKnight": "Blood Knight", - "PlagueBearer": "Plaguebearer", - "Pestilence": "Pestilence", - "Glitch": "Glitch", - "Sidekick": "Sidekick", - "Follower": "Follower", - "Cultist": "Cultist", - "SerialKiller": "Serial Killer", - "Juggernaut": "Juggernaut", - "Infectious": "Infectious", - "Virus": "Virus", - "Pursuer": "Pursuer", - "Phantom": "Phantom", - "Pirate": "Pirate", - "Agitater": "Agitator", - "Maverick": "Maverick", - "CursedSoul": "Cursed Soul", - "Pickpocket": "Pickpocket", - "Traitor": "Traitor", - "Vulture": "Vulture", - "Taskinator": "Taskinator", - "Benefactor": "Benefactor", - "Medusa": "Medusa", - "Baker": "Baker", - "Famine": "Famine", - "Spiritcaller": "Spiritcaller", - "Amnesiac": "Amnesiac", - "Imitator": "Imitator", - "Bandit": "Bandit", - "Doppelganger": "Doppelganger", - "Masochist": "Masochist", - "Doomsayer": "Doomsayer", - "Shroud": "Shroud", - "Werewolf": "Werewolf", - "Shaman": "Shaman", - "Seeker": "Seeker", - "Pixie": "Pixie", - "Occultist": "Occultist", - "SoulCollector": "Soul Collector", - "SchrodingersCat": "Schrodingers Cat", - "Romantic": "Romantic", - "VengefulRomantic": "Vengeful Romantic", - "RuthlessRomantic": "Ruthless Romantic", - "Poisoner": "Poisoner", - "HexMaster": "Hex Master", - "Wraith": "Wraith", - "Jinx": "Jinx", - "PotionMaster": "Potion Master", - "Necromancer": "Necromancer", - "Warden": "Warden", - "Minion": "Minion", - "LastImpostor": "Last Impostor", - "Overclocked": "Overclocked", - "Lovers": "Lovers", - "Madmate": "Madmate", - "Ntr": "Neptune", - "Watcher": "Watcher", - "Flash": "Flash", - "Torch": "Torch", - "Seer": "Seer", - "Tiebreaker": "Tiebreaker", - "Oblivious": "Oblivious", - "Bewilder": "Bewilder", - "Sunglasses": "Sunglasses", - "Workhorse": "Workhorse", - "Fool": "Fool", - "Avanger": "Avenger", - "Youtuber": "YouTuber", - "Egoist": "Egoist", - "TicketsStealer": "Stealer", - "Schizophrenic": "Schizophrenic", - "Mimic": "Mimic", - "Guesser": "Guesser", - "Necroview": "Necroview", - "Reach": "Reach", - "Charmed": "Charmed", - "Cleansed": "Cleansed", - "Bait": "Bait", - "Trapper": "Beartrap", - "Infected": "Infected", - "Onbound": "Onbound", - "Rebound": "Rebound", - "Mundane": "Mundane", - "Knighted": "Knighted", - "Unreportable": "Disregarded", - "Contagious": "Contagious", - "Lucky": "Lucky", - "Unlucky": "Unlucky", - "VoidBallot": "Void Ballot", - "Aware": "Aware", - "Fragile": "Fragile", - "DoubleShot": "Double Shot", - "Rascal": "Rascal", - "Soulless": "Soulless", - "Gravestone": "Gravestone", - "Lazy": "Lazy", - "Autopsy": "Autopsy", - "Loyal": "Loyal", - "EvilSpirit": "Evil Spirit", - "Recruit": "Recruit", - "Admired": "Admired", - "Glow": "Glow", - "Diseased": "Diseased", - "Antidote": "Antidote", - "Stubborn": "Stubborn", - "Swift": "Swift", - "Ghoul": "Ghoul", - "Bloodlust": "Bloodlust", - "Mare": "Mare", - "Burst": "Burst", - "Sleuth": "Sleuth", - "Clumsy": "Clumsy", - "Nimble": "Nimble", - "Circumvent": "Circumvent", - "Cyber": "Cyber", - "Hurried": "Hurried", - "Oiiai": "OIIAI", - "Influenced": "Influenced", - "Silent": "Silent", - "Susceptible": "Susceptible", - "Tricky": "Tricky", - "Rainbow": "Rainbow", - "Tired": "Tired", - "Statue": "Statue", - "BracketAddons": "Add Brackets To Add-ons", - "EngineerTOHEInfo": "Use the vents to catch the Impostors", - "ScientistTOHEInfo": "Access portable vitals from anywhere", - "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", - "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", - "ImpostorTOHEInfo": "Kill and sabotage", - "CrewmateTOHEInfo": "Search for the Impostors", - "BountyHunterInfo": "Eliminate your target", - "FireworkerInfo": "Go out with a BANG", - "MercenaryInfo": "Keep killing, else you suicide", - "ShapeMasterInfo": "Swiftly kill with no shift cooldown", - "VampireInfo": "Your kills are delayed", - "VampiressInfo": "Your kills are delayed and direct", - "WarlockInfo": "Curse crewmates then shift to make them kill", - "NinjaInfo": "Mark a target, then shift to kill", - "ZombieInfo": "You are very slow", - "AnonymousInfo": "Force a player to report a body", - "MinerInfo": "Warp to your last used vent by shifting", - "KillingMachineInfo": "You can ONLY kill, but low cooldown", - "EscapistInfo": "Shift to mark places and warp back to them", - "WitchInfo": "Spell crewmates to kill them in meetings", - "NemesisInfo": "Kill when you're the last Impostor", - "BeforeNemesisInfo": "You can't kill yet", - "AfterNemesisInfo": "Now start killing", - "BloodmoonInfo": "Seek havoc upon the crewmates", - "PuppeteerInfo": "Make players kill for you", - "MastermindInfo": "Make others kill for you", - "TimeThiefInfo": "Lower meeting time by killing", - "SniperInfo": "Snipe players from a distance by shifting", - "UndertakerInfo": "Teleport dead body to a marked location", - "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", - "EvilTrackerInfo": "Track players by shifting", - "AntiAdminerInfo": "Know when players are near devices", - "ArroganceInfo": "With each kill you make, your cooldown decreases", - "BomberInfo": "Shapeshift to explode", - "TrapsterInfo": "Trap your kills", - "ScavengerInfo": "Your kills are unreportable", - "EvilGuesserInfo": "Guess crew roles in meetings to kill", - "GangsterInfo": "Convert players to your side", - "CleanerInfo": "Report bodies to make them unreportable", - "LightningInfo": "Convert players to Quantum Ghosts", - "GreedyInfo": "Your kill cooldown shifts", - "CursedWolfInfo": "You survive a few kill attempts", - "SoulCatcherInfo": "You swap places with your shift target", - "QuickShooterInfo": "Store ammo to offset kill cooldown", - "CamouflagerInfo": "Camouflage everyone for easy kills", - "EraserInfo": "Erase the role of your vote target", - "ButcherInfo": "Enjoy my beautiful work", - "HangmanInfo": "I will decide when your life will end", - "SwooperInfo": "Turn invisible temporarily", - "CrewpostorInfo": "Kill by completing tasks", - "WildlingInfo": "Kill with strength and disguise", - "TricksterInfo": "Kill and trick the crew", - "VindicatorInfo": "Use your extra votes to kill everyone", - "ParasiteInfo": "Help the Impostors kill the crew", - "DisperserInfo": "Teleport everyone to random vents", - "InhibitorInfo": "You cannot kill during sabotages", - "SaboteurInfo": "You can only kill during sabotages", - "CouncillorInfo": "Kill off crewmates during meetings", - "DazzlerInfo": "Reduce the vision of the crew", - "DeathpactInfo": "Assign players to a death pact", - "DevourerInfo": "Consume the skin of the crew", - "ConsigliereInfo": "Discover the roles of other players", - "MorphlingInfo": "You can only kill while shapeshifted", - "TwisterInfo": "Swap all player positions", - "LurkerInfo": "Reduce your kill cooldown by venting", - "ConvictInfo": "Your target died, now help the Impostors", - "VisionaryInfo": "You see the alignments of the living", - "RefugeeInfo": "Help the Impostors kill off the crew", - "UnderdogInfo": "Start killing on a low player count", - "LudopathInfo": "Your kill cooldown is random", - "GodfatherInfo": "Convert players to Refugees by voting", - "ChronomancerInfo": "Kill in bursts", - "PitfallInfo": "Setup traps around the map", - "BerserkerInfo": "Kill to increase your level", - "EvilMiniInfo": "No one can hurt you until you grow up", - "BlackmailerInfo": "Silence other players", - "InstigatorInfo": "Sow discord among the crewmates", - "LazyGuyInfo": "You're too lazy", - "SuperStarInfo": "Everyone knows you", - "CleanserInfo": "Erase All Addons of your vote target", - "KeeperInfo": "Reject the Eject, Keeper Protect!", - "MayorInfo": "Your vote counts multiple times", - "PsychicInfo": "One of the red names are evil", - "MechanicInfo": "Vent around and fix sabotages", - "SheriffInfo": "Shoot the Impostors", - "VigilanteInfo": "Not the hero we deserved but the hero we needed", - "JailerInfo": "Jail suspicious players", - "CopyCatInfo": "Use kill button to copy target's role", - "SnitchInfo": "Finish your tasks to find the Impostors", - "MarshallInfo": "Finish your tasks to prove your innocence", - "SpeedBoosterInfo": "Boost your speed", - "DoctorInfo": "Know how each player died", - "DictatorInfo": "Exile a player based on your own judgement", - "DetectiveInfo": "Gain extra info from your body reports", - "UndercoverInfo": "Impostors see you as their partner", - "KnightInfo": "You can kill 1 player", - "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", - "GuessMasterInfo": "Whispers heard, every guessed word.", - "TransporterInfo": "Do tasks to swap 2 players' locations", - "TimeManagerInfo": "Increase meeting time by doing tasks", - "VeteranInfo": "Alert to kill anyone who interacts with you", - "BastionInfo": "Bomb vents", - "BodyguardInfo": "Prevent nearby kills", - "DeceiverInfo": "Try to fool the players", - "GrenadierInfo": "Reduce Impostors' vision by venting", - "MedicInfo": "Cast a shield onto a player", - "FortuneTellerInfo": "Get clues to people's roles", - "JudgeInfo": "Silence in the courtroom!", - "MorticianInfo": "Locate dead bodies", - "MediumInfo": "Talk with ghosts", - "ObserverInfo": "You can see all shield-animations", - "PacifistInfo": "Vent to reset kill cooldowns", - "MonarchInfo": "Give your crew extra voting power!", - "StealthInfo": "Killing Blinds Everyone in the Room", - "PenguinInfo": "Drag your victims", - "OverseerInfo": "Reveal roles of other players", - "CoronerInfo": "Find corpses and their killers", - "TrackerInfo": "Keep track of other players", - "PresidentInfo": "You are in charge of the meeting", - "MerchantInfo": "Sell add-ons and bribe killers", - "RetributionistInfo": "Help the crew after you die", - "HawkInfo": "Seek murdering the bad guys!", - "DeputyInfo": "Handcuff killers to increase their cooldowns", - "InvestigatorInfo": "Find potential evils", - "GuardianInfo": "Complete your tasks to become immortal", - "AddictInfo": "Vent to become invulnerable, or you'll die", - "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", - "AlchemistInfo": "Brew potions by completing tasks", - "TracefinderInfo": "Sense the location of dead bodies", - "OracleInfo": "Vote a player to see their alignment", - "SpiritualistInfo": "Be guided by the ghostly life", - "ChameleonInfo": "Vent to disguise into your surroundings", - "InspectorInfo": "Validate the alignments of two players", - "CaptainInfo": "Sail with the Captain, lest addons be abandoned.", - "AdmirerInfo": "Choose a player to side with you", - "TimeMasterInfo": "Rewind time!", - "CrusaderInfo": "Kill a player's attacker", - "ReverieInfo": "With each kill, your cooldown decreases", - "LookoutInfo": "See through disguises", - "TelecommunicationInfo": "Track device usage", - "LighterInfo": "Catch killers with your enhanced vision", - "TaskManagerInfo": "See the total tasks completed in real time", - "WitnessInfo": "Find out if someone killed recently", - "SwapperInfo": "Swap the votes of two players", - "ChiefOfPoliceInfo": "Recruit as a Sheriff by killing players with knives", - "NiceMiniInfo": "No one can hurt you until you grow up.", - "ArsonistInfo": "Douse everyone and ignite", - "PyromaniacInfo": "Douse and kill everyone", - "HuntsmanInfo": "Kill your targets for a low cooldown", - "SpyInfo": "You know who interacts with you", - "RandomizerInfo": "You're going to be someone's burden when you die?", - "EnigmaInfo": "Get Clues about Killers", - "JesterInfo": "Get voted out", - "OpportunistInfo": "Stay alive until the end", - "TerroristInfo": "Finish your tasks, THEN die", - "ExecutionerInfo": "Get your target voted out", - "LawyerInfo": "Help your target win!", - "VectorInfo": "Jump in! Jump out!", - "JackalInfo": "Murder everyone", - "GodInfo": "Everything is under your control", - "InnocentInfo": "Get someone ejected by making them kill you", - "PelicanInfo": "Eat all players", - "RevolutionistInfo": "Recruit players to win with you", - "HaterInfo": "Kill Lovers and Neptunes", - "DemonInfo": "Consume blood volumes", - "StalkerInfo": "Descend into the darkness, release fear!", - "WorkaholicInfo": "Finish all tasks to solo win!", - "SolsticerInfo": "Speed run all your tasks!", - "CollectorInfo": "Collect votes from players", - "ProvocateurInfo": "Victory with help target", - "BloodKnightInfo": "Killing gives you a temporary shield", - "PlagueBearerInfo": "Plague everyone to turn into Pestilence", - "PestilenceInfo": "Obliterate everyone!", - "GlitchInfo": "Hack and kill everyone", - "SidekickInfo": "Help the Jackal kill everyone", - "FollowerInfo": "Follow a player and help them", - "CultistInfo": "Charm everyone", - "SerialKillerInfo": "Kill off everyone to win!", - "JuggernautInfo": "With each kill, your cooldown decreases", - "InfectiousInfo": "Infect everyone", - "VirusInfo": "Kill and infect everyone", - "PursuerInfo": "Protect yourself and live to the end!", - "PlagueDoctorInfo": "Spread the infection!", - "PhantomInfo": "Get killed and finish your tasks to win!", - "PirateInfo": "Successfully plunder players to win", - "AgitaterInfo": "Pass a Bomb onto others", - "MaverickInfo": "Kill and survive to the end", - "CursedSoulInfo": "Snatch souls and steal the win", - "PickpocketInfo": "Steal votes from your kills", - "TraitorInfo": "Eliminate the Impostors, then win", - "VultureInfo": "Eat bodies by reporting to win", - "TaskinatorInfo": "Silent tasks, deadly blasts", - "BenefactorInfo": "Task complete, shield elite!", - "MedusaInfo": "Stone bodies by reporting them", - "BakerInfo": "Complete your tasks to poison your bread", - "FamineInfo": "Give out your poisoned bread", - "SpiritcallerInfo": "Turn Players into Evil Spirits", - "AmnesiacInfo": "Remember the role of a dead body", - "ImitatorInfo": "Imitate a player's role", - "BanditInfo": "Rob a player's add-on", - "DoppelgangerInfo": "Steal your target's identity", - "MasochistInfo": "Get attacked a few times to win!", - "KamikazeInfo": "Kill players with a suicidal mission", - "DoomsayerInfo": "Successfully guess players to win", - "ShroudInfo": "Shroud players to make them kill", - "WerewolfInfo": "Kill crewmates in groups", - "ShamanInfo": "Deflect all the attacks on Voodoo doll", - "SeekerInfo": "Play Hide and Seek with your target", - "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", - "OccultistInfo": "Kill and curse your enemies", - "SoulCollectorInfo": "Predict deaths to collect souls", - "SchrodingersCatInfo": "The cat is both alive and dead until observed.", - "RomanticInfo": "Protect your partner to win together", - "VengefulRomanticInfo": "Revenge your partner to win together", - "RuthlessRomanticInfo": "Kill everyone to win with your partner", - "PoisonerInfo": "Kill everyone with delayed kills", - "HexMasterInfo": "Hex players to kill them in meetings", - "WraithInfo": "Vent to temporarily go invisible", - "JinxInfo": "Reflect attacks onto your attackers", - "PotionMasterInfo": "Use your potions to your advantage", - "NecromancerInfo": "Kill your killer to defy death", - "WardenInfo": "(Ghost) Alert about danger", - "MinionInfo": "(Ghost) Blind enemies", - "LoversInfo": "Stay alive and win together", - "MadmateInfo": "Help the Impostors", - "NtrInfo": "Everyone sees you as their Lover", - "WatcherInfo": "You see all the colors of votes", - "LastImpostorInfo": "Lower kill cooldown", - "OverclockedInfo": "Lower cooldown", - "FlashInfo": "You're faster", - "TorchInfo": "You have enhanced vision!", - "SeerInfo": "You are alerted when somebody is killed", - "TiebreakerInfo": "Break tied votes", - "ObliviousInfo": "You can't report bodies", - "BewilderInfo": "A twist of vision, a web of confusion", - "WorkhorseInfo": "Be the first to complete all tasks and get more", - "FoolInfo": "You can't fix sabotages", - "AvangerInfo": "You take someone with you upon death", - "YoutuberInfo": "Get killed first to win", - "EgoistInfo": "Win on your own", - "TicketsStealerInfo": "Gain votes with kills", - "SchizophrenicInfo": "You're dead and alive simultaneously", - "MimicInfo": "Reveal killed players' roles to impostors upon death", - "GuesserInfo": "Guess roles of players in meetings to kill", - "NecroviewInfo": "See the team of the dead", - "ReachInfo": "You have a longer kill range", - "BaitInfo": "Your killer self-reports your body", - "TrapperInfo": "Freeze your killer for a few seconds", - "OnboundInfo": "You can't be guessed", - "ReboundInfo": "Guess me right, and face your plight!", - "MundaneInfo": "Tasks all done, guessing's begun.", - "UnreportableInfo": "Your body can't be reported", - "LuckyInfo": "Dodge attackers", - "DoubleShotInfo": "You have an extra life when guessing", - "RascalInfo": "You appear evil in some cases", - "SoullessInfo": "You have no soul", - "GravestoneInfo": "Your role is revealed when you die", - "LazyInfo": "You're too lazy", - "AutopsyInfo": "You see how others died", - "LoyalInfo": "You cannot be recruited", - "EvilSpiritInfo": "You are an evil Spirit", - "RecruitInfo": "Help the Jackal", - "AdmiredInfo": "The Admirer chose you as their love", - "GlowInfo": "You glow in the dark", - "DiseasedInfo": "Increase the cooldown of player who interacts with you", - "AntidoteInfo": "Decrease the cooldown of player who interacts with you", - "StubbornInfo": "Protect your role and addons", - "SwiftInfo": "Your kills don't cause a lunge", - "UnluckyInfo": "Doing things has a chance to kill you", - "VoidBallotInfo": "Your vote count is 0", - "AwareInfo": "Know who revealed your role", - "FragileInfo": "Die instantly if someone uses kill button on you", - "GhoulInfo": "Kill your killer after dying", - "BloodlustInfo": "Unleash your bloodlust and kill", - "SunglassesInfo": "You have reduced vision!", - "MareInfo": "Kill in the darkness", - "BurstInfo": "Make your killer burst!", - "SleuthInfo": "Gain info from dead bodies", - "ClumsyInfo": "You have a chance to miss your kill", - "NimbleInfo": "You can vent!", - "CircumventInfo": "You can no longer vent", - "OiiaiInfo": "OIIAIOIIIAI", - "CyberInfo": "You're popular!", - "HurriedInfo": "God I got too much stuffs!", - "InfluencedInfo": "You lack decisiveness!", - "SilentInfo": "Vote like a Ghost!", - "SusceptibleInfo": "Deathreason lotto!", - "TrickyInfo": "Tricky slays, in mysterious ways.", - "TiredInfo": "Labor makes you rest Zzz..", - "StatueInfo": "You're still as a rock nearby people", - "GMInfo": "Spectate the chaos!", - "NotAssignedInfo": "No assigned role", - "SunnyboyInfo": "Shine, shine my sunshine!", - "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in dark embrace.", - "NukerInfo": "Shapeshift to nuke everyone", - "RainbowInfo": "Colorful melodies! You don't even know your own color.", - "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", - "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time which shows you who is alive and who is dead.", - "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", - "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", - "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", - "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", - "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow, if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased.The Target swaps after a certain amount of time.", - "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworker, up to the max amount set by host.\nWhen you are the last Impostor and all Fireworker have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworker, it's considered an Impostor victory.", - "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", - "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", - "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. If a meeting is called first, your target still dies. If you bite a Bait, you kill normally and report the body.", - "VampiressInfoLong": "(Impostors):\nAs the Vampiress, you can Bite players like a Vampire (single click) or kill normally (double click).", - "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", - "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown, but moves very slowly and has very little vision. Zombie will not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", - "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", - "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", - "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", - "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown, but cannot vent, have Crewmate vision, cannot sabotage, cannot report, and cannot call emergency meetings.\n\nNote: You will bypass any and all shield, killing bait and beartrap won't take any effect", - "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport, be careful).", - "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", - "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID is typed. Use /id to show the IDs of all players, or look next to their names.", - "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon attack the enemies to make them drip blood, this means they will die in a time set by host, and will be aware of it.", - "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", - "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button. But if the target has a kill button of any time, they'll be told after a delay that they were manipulated and they must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", - "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", - "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", - "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out and all the rifts will be destroyed.\n\nNote: Up to two rifts can be placed at a time, if you try to place a third, it removes the first one.", - "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other people, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", - "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies, and if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog and/or other devices. Note: Anti Adminer does not know for sure if the player is using the device while near it, they only know that someone is near the device.", - "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", - "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill-flash when the Bomber explodes.", - "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", - "TrapsterInfoLong": "(Impostors):\nAs the Trapster, your main method of killing is by body reports.\nWhen someone tries to report a body you killed, they'll die.", - "GangsterInfoLong": "(Impostors):\nThe Gangster can attempt to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", - "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned up body cannot be reported (including bait's).", - "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they come into contact with to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", - "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always an odd kill.", - "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The max of times you can counterattack is set by the host)", - "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", - "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets he can use one to bypass kill cooldown, he will kill even if it's still on cooldown, and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (Number is set by the host).", - "CamouflagerInfoLong": "(Impostors):\nWhen Camouflager uses Shapeshift, all players start to look exactly the same. This state ends when Camouflager reverts its shape-shifting. Note: the skills of communication sabotage camouflage and skills of Camouflager can be superimposed.\nSkill will be invalid if a meeting is held during the skill activation of the Camouflager", - "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players whose skills are erased will always be considered a vanilla role, including the game result page.\nA player can only be erased once(include Oiiai)", - "ButcherInfoLong": "(Impostors):\nButcher kills (including passive kills) have multiple dead bodies on targets, making it impossible to accurately identify other dead body when reporting. Note: Due to the principle of implementation, the killed target has to repeatedly display the animation of being killed. This animation cannot be skipped and cannot participate in the meeting normally during this period. In addition, if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", - "HangmanInfoLong": "(Impostors):\nThe killing method of the Hangman during the shapeshifting is strangling. Strangling ignores any status of the target, such as the shield of the Medic, the protection of the Bodyguard, the skills of the Super Star, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), in addition, Seer will not be prompted.", - "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", - "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you complete a task.", - "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but lack the ability to vent.\nWhen you kill, you temporarily become immune to attacks.", - "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", - "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", - "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", - "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain target by pressing the kill button, and drag around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period of time.\nPress the kill button twice for a direct kill.", - "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", - "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not be teleported with shapeshift and players who are in the vent cannot be teleported.", - "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (eg Lights or Reactor), you cannot kill.", - "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (eg Comms or O2), then you can kill.", - "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", - "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, the targets of your shapeshifting are marked for a deathpact.\nIf enough players are marked for a death pact, the marked players must meet within a defined period of time; if they fail to do so, they die.\nIf a marked player dies before the death pact is completed, the pact is withdrawn.", - "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to permanently change the appearance of the target of the shapeshift. Additionally, for each player's appearance changed, your kill cooldown is reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", - "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshift.", - "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shape-shifting to randomly swap the position of all players. The swap happens twice, once when you start your shape shift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone and players who are in vents cannot be teleported.", - "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown is reset to its original value.", - "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following info will be displayed on the player.:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in range of the infected player becomes infected themselves.\nInfection progress is cumulative, and does not reset with distance or after meetings.", - "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor, or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", - "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", - "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", - "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", - "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round if someone kills the target, the killer will turn into a Refugee.", - "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you can charge up your kill button. Once activated the Chronomancer can use their kill button infinitely until they run out of charge.", - "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized for a short period of time and their vision will be affected.", - "BerserkerInfoLong": "(Impostors):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode.", - "EvilMiniInfoLong": "(Impostors):\nAs an Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which is drastically shortened as you grow up.", - "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target you will blackmail that player, and the blackmailed player cannot speak.\n\nSpeaking by the blackmailed player will trigger the confusion command, please do not speak when the blackmailed player sees his icon", - "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate is voted out in a meeting, as long as you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The number of additional players dying is determined by the host.", - "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task In addition, Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for the Anonymous, marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", - "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only be killed when the Murderer is alone with the Super Star (regular kills only). In addition, the Super Star cannot be guessed by Guessers.", - "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", - "CleanserInfoLong": "(Crewmates):\nCleanser can vote for any target at the meeting to erase the target's Add-ons, and the erasure will take effect after the meeting ends. Depending on the settings cleansed player may never get add on in future", - "KeeperInfoLong": "(Crewmates):\nAs keeper you can vote someone to protect them from being ejected. You can only do this a configurable amount of times.", - "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. As a setting, these votes can be hidden, you can vent to call a meeting at any time, and you are revealed as Mayor upon tasks completion.", - "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting, at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", - "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, Communications by using only one side. Lights can be fixed by flicking only one switch. Opening a door will open all doors in the map.", - "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", - "VigilanteInfoLong": "(Crewmates):\nThe Vigilante is tasked with eliminating potential threats to the crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster can not convert Vigilante into madmate.", - "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or be voted (vote count will be 0). The Jailer may choose to execute the prisoner by voting them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote : Jailed players cannot be guessed or judged and jailed players can only guess Jailer.", - "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see Impostors names being displayed in red on meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", - "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", - "SpeedBoosterInfoLong": "(Crewmates):\nSpeed Booster increase their movement speed every time they complete a task. Note: due to technical limitations, the Speed Booster appears to be at a normal speed to others, so they look like glitch.", - "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, Doctor can access vitals wherever you are while he still have battery.", - "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes someone, the meeting will end on the spot and the player they voted will be ejected. The moment the Dictator vote someone out, Dictator will also die.", - "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", - "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", - "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", - "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role guesser tried to guess, and you will also be notified in case of a misguess.", - "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill any person but they can only do it once the whole game.", - "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", - "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", - "VeteranInfoLong": "(Crewmates):\nVeteran can enter the alert state by venting. If a player tries to kill the veteran in the alert state, the veteran will kill the murderer instead. Veteran will see a shield-animation on their body and a text displayed above their head as a reminder when they enter and exit the alert state.", - "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though, crewmates can also be killed with the bombs.", - "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil that has a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to copycat after every meeting", - "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate and the murderer is an Impostor, the Bodyguard will not activate the skill.", - "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the kill ability has the counterfeit, he will suicide when he tries to kill someone next time.", - "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", - "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game, when the Medic dies, the target's shield will be removed. The Medic can also see if someone is trying to break the target's shield.\nDepending on the host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", - "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote:- If the setting to give random active players as hint is on, you will not be able to check same player multiple times", - "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the host), and if it is wrong, the judge commits suicide.\nThe judgment command is: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", - "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", - "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after their dead body is reported. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", - "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. This typically indicates the use of some role ability, so look out for this.", - "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has extra votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exhiled.", - "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", - "OverseerInfoLong": "(Crewmates):\nAs a Overseer you have very limited vision but you can use your kill button to reveal the role of a nearby player. Use the kill button to start the reveal, a 「○」 will be displayed next to the reveal target. Stay near the target for a defined time to reveal his role, if you move too far away from the target the reveal will be aborted.", - "CoronerInfoLong": "(Crewmates):\nAs a Coroner you can't report corpses, instead after trying to report the corpse you will see an arrow leading you to the killer. If a meeting is called, the arrows disappear. Depending on the setting, the body you found cannot be reported.", - "TrackerInfoLong": "(Crewmates):\nAs a Tracker, you can vote for a player in the meeting, which will mark their position for you in the game with arrows. In addition, at the beginning of a meeting you will be shown in which room the player was last, if the option is activated.", - "PresidentInfoLong": "(Crewmates):\nThe President has 2 abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", - "MerchantInfoLong": "(Crewmates):\\As a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can avert the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The bribe money used is lost and is not available for additional bribes.", - "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", - "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk you can kill a limited amount of players decided by host, tough there's a chance you miss, slicing someone multiple times increases the chances.", - "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", - "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in either red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when a meeting is called.", - "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon tasks completion. You can't even be guessed in meetings.", - "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is at 0 seconds, you still have a short time to vent.\nIf you don't make it you die, if you make it the suicide timer is reset.\nAlso, after you are ventilated, no one can interact with you for a defined period of time.\nAfter this period is over, you are immobilized for another defined period of time and cannot report any bodies.", - "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you come out of the vent, you will spawn near a random vent in the map (Except the one you just used).", - "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", - "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by host.", - "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", - "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability, if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", - "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", - "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message If they are in the same team, or a denial message if they are not in the same team.\n\nAll neutrals and converted playes are counted in the same team. Trickster is counted as crew and Rascal is counted as Impostor.\nChecking command : /cmp [player id 1] [player id 2]", - "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non crew role. Crewmates can see ☆ besides captain's name.\n\nIf anyone betrays the captain's trust by voting captain out, they will lose an addon.", - "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", - "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will be rewinded back to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", - "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", - "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the host's setting you may misfire on reaching the max kill cooldown and your target dies with you. \n\nYou win with other crewmates.", - "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", - "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, doorlogs, or admin.", - "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", - "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real time.", - "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings)", - "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", - "ChiefOfPoliceInfoLong": "(Crewmates):\nPlayers with swords can be recruited to join the sheriff's team to serve the crew, but players without swords cannot be recruited.\n note: only one recruitment opportunity", - "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, you can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses.", - "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability that is used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you have no ability uses left, you won't see orange names at all!\nNote: If the kill button interaction is blocked the player's cooldown will reset to 10s'", - "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player", - "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse by clicking the kill button on the player and following them for a few seconds. When the dousing starts and it's successful, a shield animation will be displayed as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he failed to kill everyone, he loses.", - "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting, depending on the setting, you may have to report the body to receive a clue. The more tasks you complete the more precise the clues get.", - "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", - "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double click to kill normally. When you die all marked also die, with death reason Targeted.", - "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you have a certain amount of targets that reset every meeting. If you kill one of your targets, your kill cooldown decreases by the set amount permanately. If you kill someone else other than any of your targets, your kill cooldown permanately increases by the set amount. You see your targets with a colored name.", - "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini is two roles. Either a Nice Mini or an Evil Mini is chosen.\n\nUse '/r nicemini' and '/r evilmini' respectively for more details.", - "JesterInfoLong": "(Neutrals):\nIf the Jester get voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", - "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", - "ExecutionerInfoLong": "(Neutrals):\nExecutioner has an execution target, which will be indicated by a diamond 「♦」 next to their name. If the execution target is killed, the Executioner will be changed to Crewmate, Jester or Opportunist according to the settings. If the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", - "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", - "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", - "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", - "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill normally (i.e. don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, then they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", - "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you snatch the win, i.e. everyone else loses and you win.", - "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target is voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", - "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those who are swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", - "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by host, to kill players (though they are still recruited). When the required number of players are recruited, (displayed next to your name) you must vent within the specified time in order to win the game immediately with all of your recruits. If you do not vent in time, you lose and die.", - "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, you can only kill Lovers, and other recruiting roles and add-ons, depending on the settings. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", - "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", - "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause electricity sabotage (if electricity is already sabotaged, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker win alone.", - "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on host's settings, you can only win if alive and/or you are revealed to everyone at the beginning (these settings are almost never both on).", - "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting is finished, your tasks get reset, and you need to start all over again.\nVotes on the Solsticer will be directly cancelled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in game.", - "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required number of votes, the game ends and you win alone, even if you voted a Jester or Executioner's target out.", - "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect, and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", - "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", - "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", - "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill that makes them immortal for a few seconds.", - "PlagueBearerInfoLong": "(Neutrals):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence you will become immortal and gain the ability to kill.\nIn addition to this, after turning into Pestilence you will kill anyone who tries to kill you.\n\nTo win, turn into Pestilence and kill everyone.", - "PestilenceInfoLong": "(Neutrals):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected back towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.", - "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", - "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", - "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive. Depending on settings, you will not be impacted by any harmful interactions and may have a teammate.", - "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", - "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you then can simply outnumber the crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", - "VirusInfoLong": "(Neutrals):\nThe task of the virus is to kill or infect all other players. When the virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected and joins the virus team or dies at the end of the meeting if the virus won't get voted out, dependent on the settings. If there are more players on the Virus team than on the Crewmate team, the Virus team wins.", - "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, just survive to the end of the game.", - "PhantomInfoLong": "(Neutrals):\nAs the Phantom, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", - "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both Pirate and the target chooses same number, Pirate wins.\nAdditionally, if Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command:- /duel X (where X can be 0, 1 or 2)\n\nYou win after winning a certain number of duels set by the host.\n\nNote: If target did not participate in duel, the kill will not count towards pirate victory", - "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", - "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", - "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you snatch the victory if you survive to the end of the game.\n\nYou can snatch the win from a Jester or Executioner.\n\nAdditionally, you can snatch the souls of other players.\nSoulless players win with you and count as dead.", - "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\nThese votes are hidden.\n\nKill everyone to win.", - "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors but they don't know you.\nThe twist? They can kill you but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", - "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", - "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you complete a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", - "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you complete a task, the task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks", - "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", - "BakerInfoLong": "(Neutrals):\nAs the Baker, you give out bread.\nAt the beginning of each meeting, messages will be given out.\n\nIf you finish your tasks, you become Famine when a meeting is called.", - "FamineInfoLong": "(Neutrals):\nAs the Famine, you give out poisoned bread.\nPlayers with poisoned bread die at the end of the meeting if the Famine is not exiled.\n\nIf the Famine is alive at the end of the game, they steal the win.", - "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players for a short time and/or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", - "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", - "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee or some Neutral", - "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button once to steal a player's addon and twice to kill. Depending on the settings, you may instantly steal the addon or after the meeting starts. After the max number of steals are reached you will kill normally. Additionally, if there are no stealable addons present on the target or the target is stubborn you will kill the target.\n\nKill everyone to win.\n\nNote:- Cleansed, Last Impostor and Lovers can not be stolen.\nNote:- If Bandit can vent is on, Nimble will become unstealable", - "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote:- You can not steal the identity of the target when Camouflage is active.", - "MasochistInfoLong": "(Neutrals):\nAs the Masochist, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", - "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings) then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themself after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", - "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", - "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.", - "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If seeker tags wrong player a point is deducted and if seeker tags correct player a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target\n\n The seeker needs to collect certain number of points set by the host to win", - "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark upto x amount of targets each round by using kill button on them. When the meeting starts, your job is to have one of the marked targets ejected. If unsuccessful you will suicide, except if you didn't mark any targets or all the targets are dead. The selected targets resets to 0 after the meeting ends. If you succeed you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the host.", - "SoulCollectorInfoLong": "(Neutrals):\nAs a Soul Collector, you vote players to predict their death. If the prediction is correct and the target dies in the next round you collect their soul. \n\nYou win by collecting configurable number of souls set by the host", - "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use their kill button on you, the interaction is not blocked, and you will die.", - "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield which protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote : If your role changes your win condition will be changed accordingly", - "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As Ruthless Romantic, you win if you kill everyone and be the last one standing. If you win your dead partner also wins with you.", - "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non neutral killer) is killed. As a Vengeful Romantic, Your goal is to avenge your partner, which means you have to kill the killer of your partner. If you succeed to do so, then both you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", - "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", - "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", - "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you are attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", - "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons defaults to killing.", - "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally, when someone tries to kill you, the kill will be blocked and you will be teleported to a random vent. You will have a limited time to kill your killer. If you succeed to do so, you live. If the time runs out before you kill your killer, you die permanately. If you try to kill someone else other than your killer, you will die.", - "LastImpostorInfoLong": "(Add-ons):\nThis effect is given to the last surviving Impostor. Reduces their kill cooldown.", - "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nOnly assigned to roles with a kill button.", - "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when only the Lovers are left. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 mark next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", - "MadmateInfoLong": "(Add-ons):\nOnly Crewmate can become Madmate. Madmate's task is to help the Impostors win the game, Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone.", - "NtrInfoLong": "(Add-ons):\nWhen there is Neptune, all players will see that they are Lovers with Neptune, and they will not die in love together and will not change the win conditions. Note: Lovers won't become Neptune, and Neptune won't become Lovers.", - "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", - "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the host)", - "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", - "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", - "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreaker choose different tie targets at the same time, the skills of the Tiebreaker will not take effect.", - "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still be reported automatically, and Oblivious can still be used as a scapegoat for the Anonymous.", - "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder is killed, the murderer's vision may become the same as the Bewilder's vision depending on the settings.", - "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, Workhorse will give the player extra tasks. The amount of additional tasks are set by the host.", - "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", - "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out and unconventional kills are not counted), the Avenger will revenge a random player.", - "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to be killed in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc. will not trigger the skills of the YouTuber.", - "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", - "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the vote number is set by the host, and the decimal is rounded down). Also, extra votes from the Stealer are hidden during meeting.", - "SchizophrenicInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Schizophrenic, you will be considered as two players in the game for the purpose of determining when the game ends due to killers having majority. Additionally, this grants you an extra vote, depending on options.", - "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called, this message will include information on roles who were killed by the Mimic.", - "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess roles of players in meetings to kill them.\nGuessing incorrectly kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. You have the longest kill range possible in the game, unlike everyone else.", - "BaitInfoLong": "(Add-ons):\nWhen the Bait is killed, the murderer who killed the Bait will be forced to self-report the Bait's body. However, this won't happen when the Bait is killed by a Scavenger, Cleaner, Swooper, Wraith, or Killing Machine. The report may have a delay according to Host's settings.", - "TrapperInfoLong": "(Add-ons):\nWhen Beartrap is killed, Beartrap immobilize killer for a configurable amount of time.", - "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", - "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", - "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", - "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", - "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", - "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess after all your tasks has been finished.", - "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", - "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse cannot be reported.", - "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", - "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on there is a probability for you to evade the kill, the specific probability is set by the host. When the evasion takes effect, the killer will see the shield-animation, but you not know anything.", - "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", - "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", - "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul snatches your soul, you get this add-on.\n\nYou are not counted as alive.", - "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", - "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", - "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", - "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to temporarily give the Spiritcaller a shield against a kill attempt.", - "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", - "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", - "GlowInfoLong": "(Add-ons):\nAs the Glow, your name is colored during a lights sabotage.", - "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be increased by configurable amount of time", - "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be decreased by configurable amount of time", - "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add on, Eraser can’t erase your role, Cleanser can't cleanse you, Bandit can't steal from you and Monarch can't knight you.\nAdditionally, you can’t gain any new addons from the merchant", - "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.", - "UnluckyInfoLong": "(Add-ons):\nAs the Unlucky, doing tasks, killing, or venting as a chance to kill you.", - "VoidBallotInfoLong": "(Add-ons):\nHolder of this addon will have 0 vote count", - "AwareInfoLong": "(Add-ons):\nAs the Aware, you will be notified in the next meeting if a revealing role had interacted with you", - "FragileInfoLong": "(Add-ons):\nAs Fragile, you will die instantly if someone tries to use kill button on you (even if the role can not directly kill)", - "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on tasks completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nOnly assigned to crewmates, and not crewmates with no tasks or are task based.", - "BloodlustInfoLong": "(Add-ons):\nAs the Bloodlust, doing tasks allows you to kill.\nWhen you complete a task, the next player you come in contact with dies.\n\nYour bloodlust remains after a meeting.\nUpon making a kill, your bloodlust clears till the next task you complete.\nBloodlusts do not stack.\n\nOnly assigned to crewmates with tasks.", - "SunglassesInfoLong": "(Add-ons):\nAs the Sunglasses, your vision is reduced.", - "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", - "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", - "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", - "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset and the target remains untouched.\n\nOnly assigned to killers.", - "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", - "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", - "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced,then the vote result won't shift\nCollector cannot become influenced.", - "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", - "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", - "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", - "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they complete a task, they will temporarily get lower vision & lower speed.", - "StatueInfoLong": "(Add-ons):\nWhenever many people are near Statue, the Statue is completely frozen or slowed down dependand on settings.", - "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot be killed while in a group.\nAdditionally, your death will be known.", - "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you failed with your tasks, you lose.\nHurried hurries to his goal so it won't get madmate,charmed or so.", - "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", - "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy", - "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence has no effect on the game, and all players know who the Game Master is. The Game Master role will be assigned to the host, who will automatically become a ghost at the start of the game.", - "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. When you are alive, the game will not end due to killers gaining majority.\nAdditionally, you have access to portable vitals.", - "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown is permanently halved.", - "NukerInfoLong": "(Impostors):\nAs the Nuker, you're a stronger Bomber.\n\nShapeshift to nuke everyone.", - "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", - "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", - "ShowTextOverlay": "Text Overlay", - "Overlay.GuesserMode": "Guesser Mode", - "Overlay.NoGameEnd": "No Game End", - "Overlay.DebugMode": "Debug Mode", - "Overlay.LowLoadMode": "Low Load Mode", - "Overlay.AllowConsole": "Console", - "DisableShieldAnimations": "Disable Unnecessary Shield Animations", - "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", - "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", - "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", - "AbilityUseLimit": "Initial Ability Use Limit", - "ShowArrows": "Has Arrows pointing toward bodies", - "ArrowDelayMin": "Minimum Arrow show-up delay", - "ArrowDelayMax": "Maximum Arrow show-up delay", - "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", - "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", - - "AbilityCD": "Ability Cooldown", - "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", - "ShowSpecificRole": "Know specific roles on Task Completion", - "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", - "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", - "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", - "DisableMeeting": "Disable Meetings", - "DisableCloseDoor": "Disable Doors Sabotage", - "DisableSabotage": "Disable Sabotages", - "NoGameEnd": "No Game End", - "AllowConsole": "BepInEx Console", - "DebugMode": "Debug Mode", - "SyncButtonMode": "Sync Buttons Mode", - "RandomMapsMode": "Random Maps Mode", - "SyncedButtonCount": "Max Number of Emergency Meetings Allowed", - "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", - "HHFailureKCDIncrease": "Kill cooldown increase on killing others", - "HHNumOfTargets": "Number of targets", - "Targets": "Targets: ", - "HHMaxKCD": "Maximum kill cooldown", - "HHMinKCD": "Minimum kill cooldown", - "AllAliveMeeting": "Meeting When No One is Dead", - "AllAliveMeetingTime": "Meeting Time When No One is Dead", - "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", - "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", - "AdditionalEmergencyCooldownTime": "Additional Cooldown", - "LadderDeath": "Fall From Ladders", - "LadderDeathChance": "Fall To Death Chance", - "DisableSwipeCardTask": "Disable Swipe Card Task", - "DisableSubmitScanTask": "Disable Submit Scan Task", - "DisableUnlockSafeTask": "Disable Unlock Safe Task", - "DisableUploadDataTask": "Disable Upload Data Task", - "DisableStartReactorTask": "Disable Start Reactor Task", - "DisableResetBreakerTask": "Disable Reset Breakers Task", - "DisableShortTasks": "Disable Short Tasks", - "DisableCleanVent": "Disable Clean Vent Task", - "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", - "DisableChartCourse": "Disable Chart Course Task", - "DisableStabilizeSteering": "Disable Stabilize Steering Task", - "DisableCleanO2Filter": "Disable Clean O2 Filter Task", - "DisableUnlockManifolds": "Disable Unlock Manifolds Task", - "DisablePrimeShields": "Disable Prime Shields Task", - "DisableMeasureWeather": "Disable Measure Weather", - "DisableBuyBeverage": "Disable Buy Beverage", - "DisableAssembleArtifact": "Disable Assemble Artifact Task", - "DisableSortSamples": "Disable Sort Samples Task", - "DisableProcessData": "Disable Process Data Task", - "DisableRunDiagnostics": "Disable Run Diagnostics Task", - "DisableRepairDrill": "Disable Repair Drill Task", - "DisableAlignTelescope": "Disable Align Telescope Task", - "DisableRecordTemperature": "Disable Record Temperature Task", - "DisableFillCanisters": "Disable Fill Canisters Task", - "DisableMonitorTree": "Disable Monitor Tree Task", - "DisableStoreArtifacts": "Disable Store Artifacts Task", - "DisablePutAwayPistols": "Disable Put Away Pistols Task", - "DisablePutAwayRifles": "Disable Put Away Rifles Task", - "DisableMakeBurger": "Disable Make Burger Task", - "DisableCleanToilet": "Disable Clean Toilet Task", - "DisableDecontaminate": "Disable Decontaminate Task", - "DisableSortRecords": "Disable Sort Records Task", - "DisableFixShower": "Disable Fix Shower Task", - "DisablePickUpTowels": "Disable Pick Up Towels Task", - "DisablePolishRuby": "Disable Polish Ruby Task", - "DisableDressMannequin": "Disable Dress Mannequin Task", - "DisableCommonTasks": "Disable Common Tasks", - "DisableFixWiring": "Disable Fix Wiring Task", - "DisableEnterIdCode": "Disable Enter ID Code Task", - "DisableInsertKeys": "Disable Insert Keys Task", - "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", - "DisableLongTasks": "Disable Long Tasks", - "DisableAlignEngineOutput": "Disable Align Engine Output Task", - "DisableInspectSample": "Disable Inspect Sample Task", - "DisableEmptyChute": "Disable Empty Chute Task", - "DisableClearAsteroids": "Disable Clear Asteroids Task", - "DisableWaterPlants": "Disable Water Plants Task", - "DisableOpenWaterways": "Disable Open Waterways Task", - "DisableReplaceWaterJug": "Disable Replace Water Jug Task", - "DisableRebootWifi": "Disable Reboot Wifi Task", - "DisableDevelopPhotos": "Disable Develop Photos Task", - "DisableRewindTapes": "Disable Rewind Tapes Task", - "DisableStartFans": "Disable Start Fans Task", - "DisableOtherTasks": "Disable Situational Tasks", - "DisableEmptyGarbage": "Disable Empty Garbage Task", - "DisableFuelEngines": "Disable Fuel Engines Task", - "DisableDivertPower": "Disable Divert Power Task", - "DisableActivateWeatherNodes": "Disable Weather Nodes Task", - "DisableRoastMarshmallow": "Disable Roast Marshmallow", - "DisableCollectSamples": "Disable Collect Samples", - "DisableReplaceParts": "Disable Replace Parts", - "DisableCollectVegetables": "Disable Collect Vegetables", - "DisableMineOres": "Disable Mine Ores", - "DisableExtractFuel": "Disable Extract Fuel", - "DisableCatchFish": "Disable Catch Fish", - "DisablePolishGem": "Disable Polish Gem", - "DisableHelpCritter": "Disable Help Critter", - "DisableHoistSupplies": "Disable Hoist Supplies", - "DisableFixAntenna": "Disable Fix Antenna", - "DisableBuildSandcastle": "Disable Build Sandcastle", - "DisableCrankGenerator": "Disable Crank Generator", - "DisableMonitorMushroom": "Disable Monitor Mushroom", - "DisablePlayVideoGame": "Disable Play Video Game", - "DisableFindSignal": "Disable Find Signal", - "DisableThrowFisbee": "Disable Throw Frisbee", - "DisableLiftWeights": "Disable Lift Weights", - "DisableCollectShells": "Disable Collect Shells", - "SuffixMode": "Suffix", - "SuffixMode.None": "None", - "SuffixMode.Version": "Version", - "SuffixMode.Streaming": "Streaming", - "SuffixMode.Recording": "Recording", - "SuffixMode.RoomHost": "Room Host", - "SuffixMode.OriginalName": "Original Name", - "SuffixMode.DoNotKillMe": "Don't kill me", - "SuffixMode.NoAndroidPlz": "No phones", - "SuffixMode.AutoHost": "Auto-Host", - "SuffixModeText.DoNotKillMe": "Don't kill me", - "SuffixModeText.NoAndroidPlz": "No phones please", - "SuffixModeText.AutoHost": "Auto-hosting", - "FormatNameMode": "Player Name Mode", - "FormatNameModes.None": "Disable", - "FormatNameModes.Color": "Color", - "FormatNameModes.Snacks": "Random", - "DisableEmojiName": "Disable Emoji in names", - "FixFirstKillCooldown": "Override Starting Kill Cooldown", - "FixKillCooldownValue": "Starting Kill Cooldown", - "OverclockedReduction": "Kill Cooldown Reduction", - "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", - "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", - "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", - "GhostIgnoreTasks": "Ghosts Exempt From Tasks", - "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", - "MaxImpGhostRole": "Max Impostor Ghost-Roles", - "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", - "DefaultAngelCooldown": "Default Ability Cooldown", - "DisableTaskWin": "Disable Task Win", - "HideGameSettings": "Hide Game Settings", - "DIYGameSettings": "Enable only custom /n messages", - "Settings:": "Settings:", - "PlayerCanSetColor": "Players can use the /color command", - "PlayerCanSetName": "Players can use the /rn command", - "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", - "PlayerCanUseTP": "Players can use the /tpin and /tpout command", - "CanPlayMiniGames": "Players can play mini games", - "KPDCamouflageMode": "Camouflage Appearance", - "RoleOptions": "Role Options", - "DarkTheme": "Enable Dark Theme", - "AutoStart": "Auto start", - "EnableCustomButton": "Enable Custom Button Images", - "EnableCustomSoundEffect": "Enable Custom Sound Effects", - "SwitchVanilla": "Switch Vanilla", - "ModeForSmallScreen": "Small Screen Mode", - "UnlockFPS": "Unlock FPS", - "ForceOwnLanguage": "Force mod to use your language if possible", - "ForceOwnLanguageRoleName": "Force role names in your language if possible", - "VersionCheat": "Bypass version synchronization check", - "GodMode": "God Mode", - - "AutoDisplayKillLog": "Display Kill-log", - "AutoDisplayLastRoles": "Display Last Roles", - "AutoDisplayLastResult": "Auto Display Last Result", - "RevertOldKillLog": "Revert to old kill-log", - - "HideExileChat": "Hide exile (lava) chat", - "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", - - "EnableYTPlan": "Enable Youtuber Plan", - "KickLowLevelPlayer": "Kick players whose level is lower than", - "TempBanLowLevelPlayer": "Temporarily ban low-level players", - "ApplyWhiteList": "Turn on Whitelist to bypass level kick", - "AllowOnlyWhiteList": "Allow only whitelisted players to join", - "AutoKickStart": "Kick players that say start", - "AutoKickStartTimes": "Number of warnings before kick", - "AutoKickStartAsBan": "Block a player after they're kicked", - "AutoKickStopWords": "Kick players who write banned words", - "AutoKickStopWordsTimes": "Number of warnings for banned words", - "AutoKickStopWordsAsBan": "Block a player after they're kicked", - "AutoWarnStopWords": "Warning to those who write banned words", - "TempBanPlayersWhoKeepQuitting": "Temporarily ban players who leave and join repeatedly", - "QuitTimesTillTempBan": "The quit frequency needed for temp ban", - "KickOtherPlatformPlayer": "Kick Non-PC players", - "OptKickAndroidPlayer": "Kick Android players", - "OptKickIphonePlayer": "Kick iOS players", - "OptKickXboxPlayer": "Kick Xbox players", - "OptKickPlayStationPlayer": "Kick PlayStation players", - "OptKickNintendoPlayer": "Kick Nintendo Switch players", - "ShareLobby": "Allow TOHE-Chan Shares Lobby Code To Discord", - "ShareLobbyMinPlayer": "Share Lobby Code When The Number Of Players Reaches", - "DisableVanillaRoles": "Disable Vanilla Roles", - "VoteMode": "Voting Mode", - "WhenSkipVote": "If the Player Skipped", - "WhenSkipVoteIgnoreFirstMeeting": "Ignore the First Meeting", - "WhenSkipVoteIgnoreNoDeadBody": "Ignore When No Dead Body", - "WhenSkipVoteIgnoreEmergency": "Ignore at Emergency Meetings", - "WhenNonVote": "If the Player didn't vote", - "Default": "No vote", - "Suicide": "Suicide", - "SelfVote": "Self Vote", - "Skip": "Skip", - "WhenTie": "When Tied Vote", - "TieMode.Default": "No ejects", - "TieMode.All": "Eject All", - "TieMode.Random": "Eject Random", - "DisableDevices": "Disable Devices", - "DisableSkeldDevices": "Disable Skeld Devices", - "DisableMiraHQDevices": "Disable MIRA HQ Devices", - "DisablePolusDevices": "Disable Polus Devices", - "DisableAirshipDevices": "Disable Airship Devices", - "DisableFungleDevices": "Disable Fungle Devices", - "DisableSkeldAdmin": "Disable Admin", - "DisableMiraHQAdmin": "Disable Admin", - "DisablePolusAdmin": "Disable Admin", - "DisableAirshipCockpitAdmin": "Disable Cockpit Admin", - "DisableAirshipRecordsAdmin": "Disable Records Admin", - "DisableSkeldCamera": "Disable Cameras", - "DisablePolusCamera": "Disable Cameras", - "DisableAirshipCamera": "Disable Cameras", - "DisableMiraHQDoorLog": "Disable DoorLog", - "DisablePolusVital": "Disable Vitals", - "DisableAirshipVital": "Disable Vitals", - "DisableFungleVital": "Disable Vitals", - "DisableFungleBinoculars": "Disable Binoculars (Not work for vanilla)", - "IgnoreConditions": "Ignore Conditions", - "IgnoreImpostors": "Ignore Impostors", - "IgnoreNeutrals": "Ignore Neutrals", - "IgnoreCrewmates": "Ignore Crewmates", - "IgnoreAfterAnyoneDied": "Ignore After First Death", - "LightsOutSpecialSettings": "Fix Lights Special Settings", - "BlockDisturbancesToSwitches": "Block Switches When They Are Up", - "DisableAirshipViewingDeckLightsPanel": "Disable Viewing Deck Lights Panel (Airship)", - "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", - "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", - "RandomSpawnMode": "Random Spawns Mode", - "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", - "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", - "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", - "CommsCamouflage": "Camouflage during Comms Sabotage", - "DisableOnSomeMaps": "Disable comms camouflage on some maps", - "DisableOnSkeld": "Disable on The Skeld", - "DisableOnMira": "Disable on MIRA HQ", - "DisableOnPolus": "Disable on Polus", - "DisableOnDleks": "Disable on dlekS ehT", - "DisableOnAirship": "Disable on Airship", - "DisableOnFungle": "Disable on The Fungle", - "DisableReportWhenCC": "Disable body reporting while camouflaged", - "EnableDebugMode": "Enable Debug Mode", - "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", - "SendRoleDescriptionFirstMeeting": "Show Role Descriptions to Unmodded Clients at First Meeting", - "RoleAssigningAlgorithm": "Role Assigning Algorithm", - "RoleAssigningAlgorithm.Default": "Default", - "RoleAssigningAlgorithm.NetRandom": ".NET System.Random", - "RoleAssigningAlgorithm.HashRandom": "HashRandom", - "RoleAssigningAlgorithm.Xorshift": "Xorshift", - "RoleAssigningAlgorithm.MersenneTwister": "MersenneTwister", - "MapModification": "Map Modifications", - "DisableAirshipMovingPlatform": "Disable Moving Platform (Airship)", - "AirshipVariableElectrical": "Variable Electrical (Airship)", - "DisableSporeTriggerOnFungle": "Disable Spore Trigger (Fungle)", - "DisableZiplineOnFungle": "Disable Zipline (Fungle)", - "DisableZiplineFromTop": "Disable Use From Top", - "DisableZiplineFromUnder": "Disable Use From Under", - "ResetDoorsEveryTurns": "Reset Doors After Meeting (Airship/Polus/Fungle)", - "DoorsResetMode": "Reset Doors Mode", - "AllOpen": "All Open", - "AllClosed": "All Closed", - "RandomByDoor": "Closed Random", - "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", - "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", - "DecontaminationTimeOnPolus": "Decontamination Time On Polus", - "ApplyDenyNameList": "Apply DenyName List", - "KickPlayerFriendCodeNotExist": "Kick players without a friend code", - "TempBanPlayerFriendCodeNotExist": "Temp Ban players without a friend code", - "ApplyBanList": "Apply BanList", - "EndWhenPlayerBug": "End the game when a player has a critical error", - "RemovePetsAtDeadPlayers": "Remove pets at dead players", - "KillFlashDuration": "Kill-Flash Duration", - "ConfirmEjectionsMode": "Confirm Ejections Mode", - "ConfirmEjections.None": "None", - "ConfirmEjections.Team": "Team", - "ConfirmEjections.Role": "Role", - "ShowImpRemainOnEject": "Show remaining Impostors on ejects", - "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", - "ConfirmEgoistOnEject": "Confirm Egoists on ejection", - "ConfirmLoversOnEject": "Confirm Lovers on ejection", - "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", - "HideBittenRolesOnEject": "Hide roles of bitten players on ejection", - "ShowTeamNextToRoleNameOnEject": "Show what team the ejected player's role is on", - "Ban": "Ban", - "Kick": "Kick", - "NoticeMe": "Notify me", - "NoticeEveryone": "Notify everyone", - "TempBan": "Temporary Ban", - "OnlyCancel": "Only Cancel the cheat actions", - "CheatResponses": "When a cheating player is found", - "NeutralRoleWinTogether": "Neutrals win together", - "NeutralWinTogether": "All Neutrals win together", - "MenuTitle.Disable": "★ Disable ★", - "MenuTitle.MapsSettings": "★ Maps ★", - "MenuTitle.Sabotage": "★ Sabotage ★", - "MenuTitle.Meeting": "★ Meeting ★", - "MenuTitle.Ghost": "★ Ghost ★", - "MenuTitle.Other": "★ Different ★", - "MenuTitle.Ejections": "★ Ejection ★", - "MenuTitle.Settings": "★ Settings ★", - "MenuTitle.TaskSettings": "★ Task Management ★", - "MenuTitle.Guessers": "★ Guesser Mode ★", - "MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★", - "ShieldPersonDiedFirst": "Shield player who dead first in the last game", - "LegacyNemesis": "Use Legacy Version", - "ArsonistKeepsGameGoing": "Arsonist keeps the game going", - "ArsonistCanIgniteAnytime": "Can Ignite Anytime", - "ArsonistMinPlayersToIgnite": "Minimum doused needed for ignite", - "ArsonistMaxPlayersToIgnite": "Maximum doused needed for ignite", - "PuppeteerDoubleKills": "Puppet dies alongside victim", - "MastermindCD": "Manipulate Cooldown", - "MastermindTimeLimit": "Time limit to kill someone", - "MastermindDelay": "Manipulation notification delay", - "ManipulateNotify": "Kill someone in {0}s or die!", - "ManipulatedKilled": "{0} has killed someone", - "SurvivedManipulation": "You survived the Mastermind's manipulation!", - "Glitch_HackCooldown": "Hack Cooldown", - "Glitch_HackDuration": "Hack Duration", - "Glitch_MimicCooldown": "Mimic Cooldown", - "Glitch_MimicDuration": "Mimic Duration", - "Glitch_MimicButtonText": "Mimic", - "Glitch_MimicDur": "Mimic Duration: {0}s", - "Glitch_HackCD": "Hack Cooldown: {0}s", - "Glitch_KCD": "Kill Cooldown: {0}s", - "Glitch_MimicCD": "Mimic Cooldown: {0}s", - "HackedByGlitch": "You are hacked by the Glitch, you can't {0}.", - "GlitchKill": "kill", - "GlitchReport": "report", - "GlitchVent": "vent", - "ShowFPS": "Show FPS", - "FPSGame": "FPS: ", - "Cooldown": "Cooldown", - "KillCooldown": "Kill Cooldown", - "AbilityCooldown": "Ability Cooldown", - "VentCooldown": "Vent Cooldown", - "ControlCooldown": "Control Cooldown", - "PoisonCooldown": "Poison Cooldown", - "PoisonerKillDelay": "Poison Kill Delay", - "WardenNotifyLimit": "Max number of alerts", - "CanVent": "Can Vent", - "CanKill": "Can Kill", - "CanGuess": "Can Guess in Guesser Mode or as Guesser", - "BombCooldown": "Bomb Cooldown", - "ImpostorVision": "Has Impostor Vision", - "CanUseSabotage": "Can Sabotage", - "CanKillAllies": "Can Kill Impostors", - "CanKillSelf": "Can Kill Themself", - "CrewpostorKnowsAllies": "Knows Impostors", - "AlliesKnowCrewpostor": "Known to Impostors", - "CrewpostorLungeKill": "Crewpostor lunges on kill", - "CrewpostorKillAfterTask": "Number of tasks completed to make 1 kill", - - "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", - "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", - "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", - "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", - "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", - "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", - "ImpKnowWhosMadmate": "Impostors know Madmates", - "MadmateKnowWhosImp": "Madmates know Impostors", - "MadmateKnowWhosMadmate": "Madmates know each other", - "ImpCanKillMadmate": "Impostors can kill Madmates", - "MadmateCanKillImp": "Madmates can kill Impostors", - "MadmateHasImpostorVision": "Madmates Have Impostor Vision", - "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", - "EGCanGuessImp": "Can Guess Impostor Roles", - "GGCanGuessCrew": "Can Guess Crewmate Roles", - "EGCanGuessAdt": "Can Guess Add-Ons", - "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "GGCanGuessAdt": "Can Guess Add-Ons", - "GuesserCanGuessTimes": "Maximum number of guesses", - "GuesserTryHideMsg": "Try to hide guesser's command", - "GCanGuessImp": "Impostor can guess Impostor roles", - "GCanGuessCrew": "Crewmate can guess Crewmate roles", - "GCanGuessAdt": "Can guess Add-ons", - "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "BountyTargetChangeTime": "Time Until Target Swaps", - "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", - "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", - "BountyShowTargetArrow": "Show arrow pointing towards target", - "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", - "ShapeshiftDuration": "Shapeshift Duration", - "ShapeshiftCooldown": "Shapeshift Cooldown", - "VitalsDuration": "Vitals Duration", - "VitalsCooldown": "Vitals Cooldown", - "DeadImpCantSabotage": "Impostors can't sabotage after they've died", - "VampireKillDelay": "Bite Kill Delay", - - "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", - "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", - - "MechanicSkillLimit": "Initial repair use limit", - "MechanicFixesDoors": "Can open all doors in the same building", - "MechanicFixesReactors": "Can Fix Both Reactors Alone", - "MechanicFixesOxygens": "Can Fix Both O2 Alone", - "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", - "MechanicFixesElectrical": "Can Fix Lights With One Switch", - - "SheriffShowShotLimit": "Display Shot Limit next to Role Name", - "SheriffCanKill%role%": "Can Kill %role%", - "SheriffCanKillNeutrals": "Can Kill Neutrals", - "SheriffCanKillNeutralsMode": "Neutral Configuration", - "SheriffCanKillAll": "All ON", - "SheriffCanKillSeparately": "Individual Settings", - "In%team%": "(Team %team%)", - "SheriffMisfireKillsTarget": "Misfire Kills Target", - "SheriffShotLimit": "Max number of Kills", - "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", - "SheriffCanKillCharmed": "Can kill Charmed players", - "SheriffCanKillEgoist": "Can Kill Egoists", - "SheriffCanKillSidekick": "Can Kill Sidekicks", - "SheriffCanKillLovers": "Can Kill Lovers", - "SheriffCanKillMadmate": "Can Kill Madmates", - "SheriffCanKillInfected": "Can Kill Infected players", - "SheriffCanKillContagious": "Can Kill Contagious players", - "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", - "SheriffMadCanKillImp": "Can kill Impostors", - "SheriffMadCanKillNeutral": "Can kill Neutrals", - "SheriffMadCanKillCrew": "Can kill Crewmates", - - "ReverieIncreaseKillCooldown": "Increase kill cooldown", - "ReverieMaxKillCooldown": "Max kill cooldown", - "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", - "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", - "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", - - "VigilanteNotify": "You have become the very thing you swore to destroy", - - "DoctorTaskCompletedBatteryCharge": "Battery Duration", - "SnitchEnableTargetArrow": "See Arrow Towards Target", - "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", - "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", - "SnitchCanFindMadmate": "Can Find Madmates", - "SnitchRemainingTaskFound": "Remaining tasks to be known", - "SpeedBoosterUpSpeed": "Increase Speed by", - "SpeedBoosterTimes": "Max Boosts", - "MayorAdditionalVote": "Additional Votes Count", - "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", - "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", - "MayorHideVote": "Hide additional vote(s)", - "HideJesterVote": "Hide Jester's vote", - "MeetingsNeededForWin": "Meetings needed to win", - "ExecutionerCanTargetImpostor": "Can Target Impostors", - "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", - "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", - "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", - "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", - "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", - "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", - "LawyerCanTargetImpostor": "Can Target Impostors", - "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", - "LawyerCanTargetCrewmate": "Can Target Crewmates", - "LawyerCanTargetJester": "Can Target Jester", - "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", - "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", - "LawyerTargetDeadInMeeting": "Your target was killed while meeting./nYour role may change depending on the settings.", - - "MercenaryLimit": "Time Until Suicide", - "ArsonistDouseTime": "Douse Duration", - "CanTerroristSuicideWin": "Can Win By Suicide", - "FireworkerMaxCount": "Fireworker Count", - "FireworkerRadius": "Firework Explosion Radius", - "SniperCanKill": "Sniper can kill with bullets remaining", - "SniperBulletCount": "Ammo", - "SniperPrecisionShooting": "Precise Shooting", - "SniperAimAssist": "Aim Assist", - "SniperAimAssistOneshot": "One shot Assist", - - "PyroDouseCooldown": "Douse cooldown", - "PyroBurnCooldown": "Kill cooldown after killing a doused player", - - "UndertakerFreezeDuration": "Freeze Duration", - - "NameDisplayAddons": "Display Add-Ons next to the role name", - "YourAddon": "Your Addons:", - "NoLimitAddonsNumMax": "Max Add-ons Per Player", - "LoverSpawnChances": "Spawn Chance of Lovers", - "AdditionRolesSpawnRate": "Spawn Chance", - "TorchVision": "Torch Vision", - "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", - "BewilderVision": "Bewilder Vision", - "JesterVision": "Jester Vision", - "LawyerVision": "Lawyer Vision", - "FlashSpeed": "Flash Speed", - "LoverSuicide": "Lovers die together", - "NumberOfLovers": "Number of Lover Pairs (x2 members)", - "LoverKnowRoles": "Lovers know the roles of each other", - "TrapperBlockMoveTime": "Freeze time", - "BecomeTrapperBlockMoveTime": "Freeze time", - "ImpCanBeTrapper": "Impostors can become Beartrap", - "CrewCanBeTrapper": "Crewmates can become Beartrap", - "NeutralCanBeTrapper": "Neutrals can become Beartrap", - "ImpCanBeGravestone": "Impostors can become Gravestone", - "CrewCanBeGravestone": "Crewmates can become Gravestone", - "NeutralCanBeGravestone": "Neutrals can become Gravestone", - "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", - "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", - "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", - "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", - "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", - "EvilTrackerTargetMode": "Can Set Target", - "EvilTrackerTargetMode.Never": "Never", - "EvilTrackerTargetMode.OnceInGame": "Once in game", - "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", - "EvilTrackerTargetMode.Always": "Any time", - "WitchModeSwitchAction": "Switch Action via", - "NBareRed": "Neutral Benign can be red", - "NEareRed": "Neutral Evil can be red", - "NCareRed": "Neutral Chaos can be red", - "CrewKillingRed": "Crewmate Killings can be red", - "PsychicCanSeeNum": "Max number of red names", - "PsychicFresh": "New red names every meeting", - "DetectiveCanknowKiller": "Can find the killer's role", - "EveryOneKnowSuperStar": "Everyone knows the Super Star", - "HackLimit": "Ability Use Count", - "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", - "NemesisCanKillNum": "Max number of revenges", - "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", - "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", - "JesterCanUseButton": "Can call emergency meetings", - "VectorVentNumWin": "Number of Vents to win", - "CanCheckCamera": "Can track camera usage", - "Arrogance/Juggernaut___DefaultKillCooldown": "Starting kill cooldown", - "Arrogance/Juggernaut___ReduceKillCooldown": "Reduce kill cooldown by", - "Arrogance/Juggernaut___MinKillCooldown": "Minimum kill cooldown", - "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", - "NotifyGodAlive": "Inform players at meetings that God is still alive", - "TransporterTeleportMax": "Max number of teleports", - "TriggerKill": "Kill", - "TriggerVent": "Vent", - "TriggerDouble": "Double Click", - "TimeManagerIncreaseMeetingTime": "Increase voting time by", - "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", - "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", - "AssignOnlyToCrewmate": "Assign only to Crewmates", - "WorkhorseNumLongTasks": "Additional Long Tasks", - "WorkhorseNumShortTasks": "Additional Short Tasks", - "SnitchCanBeWorkhorse": "Snitch can become Workhorse", - "InnocentCanWinByImp": "If their target was an Impostor then they win with them", - "ImpCanBeSchizophrenic": "Impostors can become Schizophrenic", - "CrewCanBeSchizophrenic": "Crewmates can become Schizophrenic", - "DualVotes": "Duplicate votes", - "ProtectCooldown": "Protect Cooldown", - "ProtectDur": "Protection Duration", - "ProtectVisToImp": "Protect Visible To Impostors", - "VeteranSkillCooldown": "Alert Cooldown", - "VeteranSkillDuration": "Alert Duration", - "BodyguardProtectRadius": "Protect Radius", - "ImpCanBeEgoist": "An Impostor can become Egoist", - "CrewCanBeEgoist": "Crewmates can become Egoist", - "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", - "EgoistCountAsConverted": "Egoist count as converted neutral", - "ImpCanBeSeer": "Impostors can become Seer", - "CrewCanBeSeer": "Crewmates can become Seer", - "NeutralCanBeSeer": "Neutrals can become Seer", - "ImpCanBeGuesser": "Impostors can become Guesser", - "CrewCanBeGuesser": "Crewmates can become Guesser", - "NeutralCanBeGuesser": "Neutrals can become Guesser", - "ImpCanBeWatcher": "Impostors can become Watcher", - "CrewCanBeWatcher": "Crewmates can become Watcher", - "NeutralCanBeWatcher": "Neutrals can become Watcher", - "ImpCanBeBait": "Impostors can become Bait", - "CrewCanBeBait": "Crewmates can become Bait", - "NeutralCanBeBait": "Neutrals can become Bait", - "ImpCanBeRainbow": "Impostors can become Rainbow", - "NeutralCanBeRainbow": "Neutrals can become Rainbow", - "CrewCanBeRainbow": "Crewmates can become Rainbow", - "GuessRainbow": "He seems too obvious, doesn't he?", - "RainbowColorChangeCoolDown": "The cooldown for changing colors", - "RainbowInCamouflage": "Rainbow color changes during Camouflage", - "BaitDelayMin": "Minimum Report Delay", - "BaitDelayMax": "Maximum Report Delay", - "BaitDelayNotify": "Warn the killer about the upcoming self-report", - "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", - "BaitNotification": "Reveal Bait at the first meeting", - "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self report.", - "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if meeting is disabled during comms sabotage", - "DeceiverSkillCooldown": "Ability cooldown", - "DeceiverSkillLimitTimes": "Max number of uses", - "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", - "PursuerSkillCooldown": "Ability cooldown", - "PursuerSkillLimitTimes": "Max number of uses", - "AddictSuicideTimer": "Time Until Suicide", - "GrenadierSkillCooldown": "Grenade Cooldown", - "GrenadierSkillDuration": "Grenade Duration", - "GrenadierCauseVision": "Lowered vision", - "GrenadierCanAffectNeutral": "Can affect Neutrals", - "TicketsPerKill": "Votes Increase Amount Per Kill", - "GangsterRecruitCooldown": "Recruit cooldown", - "GangsterRecruitLimit": "Recruit limit", - "KamikazeMaxMarked": "Max Marked", - "RevolutionistDrawTime": "Tag Duration", - "RevolutionistCooldown": "Tag Cooldown", - "RevolutionistDrawCount": "Amount of Players needed to Tag", - "RevolutionistKillProbability": "Tagged player sacrifice probability", - "RevolutionistVentCountDown": "Time to Vent", - "PelicanKillCooldown": "Eat Cooldown", - "Pelican.TargetCannotBeEaten": "Target cannot be eaten", - "MadSnitchTasks": "Snitch Tasks", - "MedicWhoCanSeeProtect": "Who can see shield", - "MedicKnowShieldBroken": "Who sees kill attempt", - "Medic_SeeMedicAndTarget": "Medic+Shielded", - "Medic_SeeMedic": "Medic", - "Medic_SeeTarget": "Shielded", - "Medic_SeeNoOne": "Nothing", - "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", - "MedicShielDeactivationIsVisible": "Shield deactivation is visible", - "MedicShieldDeactivationIsVisible_DeactivationImmediately": "Immediately", - "MedicShieldDeactivationIsVisible_DeactivationAfterMeeting": "After Meeting", - "MedicShieldDeactivationIsVisible_DeactivationIsVisibleOFF": "OFF", - "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", - "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", - "FortuneTellerSkillLimit": "Max number of ability uses", - "MadmateSpawnMode": "Madmate spawning mode", - "MadmateSpawnMode.Assign": "Assign", - "MadmateSpawnMode.FirstKill": "First Kill", - "MadmateSpawnMode.SelfVote": "Self Vote", - "MadmateCountMode": "Madmates count as", - "MadmateCountMode.None": "Nothing", - "MadmateCountMode.Imp": "Impostors", - "MadmateCountMode.Original": "Original Team", - - "SnatchesWin": "Snatches victory", - "DemonKillCooldown": "Attack Cooldown", - "DemonHealthMax": "Player max health", - "DemonDamage": "Damage ", - "DemonSelfHealthMax": "Demon max health", - "DemonSelfDamage": "Demon damage received", - "LightningConvertTime": "Duration of the transformation to Quantum Ghost", - "LightningKillCooldown": "Lightning Cooldown", - "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", - "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", - "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", - "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", - "WorkaholicCannotWinAtDeath": "Can't win after they died", - "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", - "WorkaholicGiveAdviceAlive": "Advice at first meeting if alive, can win after death, ghost tasks ON", - "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", - "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", - "Jinx/CursedWolf___KillAttacker": "Kill attacker when ability is remaining", - "JinxSpellTimes": "Amount of Jinx Spells", - "CollectorCollectAmount": "Required number of votes", - "GlitchCanVote": "Can vote", - "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", - "MeetingReserved": "Max Bullets reserved for a meeting", - "AccurateCheckMode": "Can know specific role when tasks are not done", - "RandomActiveRoles": "Show random active roles in Fortune Teller hints", - "CamouflageCooldown": "Camouflage Cooldown", - "CamouflageDuration": "Camouflage Duration", - "EraseLimit": "Max Erases", - "EraserHideVote": "Hide Eraser Votes", - "NinjaMarkCooldown": "Mark Cooldown", - "NinjaAssassinateCooldown": "Ass​assinate Cooldown", - "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", - "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", - "JudgeCanTrialNeutralB": "Can trial Neutral Benign", - "JudgeCanTrialNeutralK": "Can trial Neutral Killing", - "JudgeCanTrialNeutralE": "Can trial Neutral Evil", - "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", - "JudgeCanTrialSidekick": "Can trial Sidekick", - "JudgeCanTrialInfected": "Can trial Infected", - "JudgeCanTrialContagious": "Can trial Contagious", - "JudgeTryHideMsg": "Hide Judge's commands", - "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", - "JudgeCanTrialMadmate": "Can trial Madmates", - "JudgeCanTrialCharmed": "Can trial Charmed players", - "JudgeDead": "Sorry, you can't trial after death.", - "JudgeTrialMax": "\nNo more trials left!", - "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", - "Judge_TrialKill": "{0} was judged.", - "Judge_TrialKillTitle": "COURT", - "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Judge_TrialNull": "Please choose a living player for the trial", - "VeteranSkillMaxOfUseage": "Max number of Alerts", - "SwooperCooldown": "Swoop Cooldown", - "SwooperDuration": "Swoop Duration", - "WraithCooldown": "Vanish Cooldown", - "WraithDuration": "Vanish Duration", - "BastionNotify": "A bomb was set off", - "EnteredBombedVent": "That vent was bombed!", - "BastionVentButtonText": "Bomb", - "BombsClearAfterMeeting": "Bombs clear after meetings", - "BastionMaxBombs": "(Initial) Maximum bombs", - "VentBombSuccess": "Bomb has been planted", - "LowLoadMode": "Low Load Mode", - "ShowLobbyCode": "Show lobby code in Discord status", - "BKProtectDuration": "Protection Duration", - "FollowerMaxBetTimes": "Maximum Number of Follows", - "FollowerBetCooldown": "Follow Cooldown", - "FollowerMaxBetCooldown": "Maximum Follow Cooldown", - "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", - "FollowerKnowTargetRole": "Follower knows their target's role", - "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", - "FortuneTellerHideVote": "Hide Fortune Teller's Votes", - "CultistCharmCooldown": "Charm Cooldown", - "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", - "CultistCharmMax": "Maximum Number Of Charm", - "CultistKnowTargetRole": "Know Charmed Player's Role", - "CultistTargetKnowOtherTarget": "Charmed players know each other", - "CultistCanCharmNeutral": "Neutral Roles can be Charmed", - "InfectiousBiteCooldown": "Infect Cooldown", - "KnowTargetRole": "Knows role of target", - "TargetKnowsLawyer": "Target knows their Lawyer", - "InfectiousBiteMax": "Maximum Infections", - "InfectiousKnowTargetRole": "Know infected player's role", - "InfectiousTargetKnowOtherTarget": "Infected players know each other", - "DoubleClickKill": "Double click to kill the target", - - "VirusInfectMax": "Maximum Number Of Spreads", - "VirusKnowTargetRole": "Know Contagious Player's Role", - "VirusTargetKnowOtherTarget": "Contagious players know each other", - "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", - "Virus_ContagiousCountMode": "Contagious players count as", - "Virus_ContagiousCountMode_None": "Nothing", - "Virus_ContagiousCountMode_Virus": "Virus", - "Virus_ContagiousCountMode_Original": "Original Team", - "VirusNoticeTitle": "[ Infected Corpse! ]", - "VirusNoticeMessage": "The body your reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", - "VirusNoticeMessage2": "The body your reported was infected by the Virus! Vote the Virus out this meeting or you will die.", - - "Cultist_CharmedCountMode": "Charmed players count as", - "Cultist_CharmedCountMode_None": "Nothing", - "Cultist_CharmedCountMode_Cultist": "Cultist", - "Cultist_CharmedCountMode_Original": "Original Team", - - "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", - "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", - "JackalResetKillCooldownOn": "Kill Cooldown On Reset", - "JackalCanRecruitSidekick": "Can recruit Sidekick", - "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", - "Jackal_SidekickCountMode": "Sidekicks count as", - "Jackal_SidekickCountMode_None": "Nothing", - "Jackal_SidekickCountMode_Jackal": "Jackal", - "Jackal_SidekickCountMode_Original": "Original Team", - "Jackal_SidekickAssignMode": "Sidekick Assign Mode", - "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", - "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", - "Jackal_SidekickAssignMode_Recruit": "Recruit Only", - "JackalWinWithSidekick": "Jackal can win with Sidekick's team", - "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", - "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", - "JackalCanKillSidekick": "Jackal can kill Sidekick", - - "ImpCanBeNecroview": "Impostors can become Necroview", - "CrewCanBeNecroview": "Crewmates can become Necroview", - "NeutralCanBeNecroview": "Neutrals can become Necroview", - "ImpCanBeInLove": "Impostors can be in love", - "CrewCanBeInLove": "Crewmates can be in love", - "NeutralCanBeInLove": "Neutrals can be in love", - "ImpCanBeOblivious": "Impostors can become Oblivious", - "CrewCanBeOblivious": "Crewmates can become Oblivious", - "NeutralCanBeOblivious": "Neutrals can become Oblivious", - "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", - "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", - "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", - "HexesLookLikeSpells": "Hexes appear as spells", - "HexButtonText": "Hex", - "ObliviousBaitImmune": "Immune to Bait", - "ImpCanBeOnbound": "Impostors can become Onbound", - "CrewCanBeOnbound": "Crewmates can become Onbound", - "NeutralCanBeOnbound": "Neutrals can become Onbound", - - "ImpCanBeRebound": "Impostors can become Rebound", - "CrewCanBeRebound": "Crewmates can become Rebound", - "NeutralCanBeRebound": "Neutrals can become Rebound", - - "CrewCanBeMundane": "Crewmates can become Mundane", - "NeutralCanBeMundane": "Neutrals can become Mundane", - "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", - - "ImpCanBeUnreportable": "Impostors can become Disregarded", - "CrewCanBeUnreportable": "Crewmates can become Disregarded", - "NeutralCanBeUnreportable": "Neutrals can become Disregarded", - "PacifistCooldown": "Ability Cooldown", - "PacifistMaxOfUseage": "Max Number of Ability Uses", - "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", - "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", - "TrackerHideVote": "Hide Tracker Votes", - "TrackerCanGetArrowColor": "Can See Colored Arrows", - - "PresidentAbilityUses": "Max Number of Ability Uses", - "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", - "HidePresidentEndCommand": "Hide President's commands", - "NeutralsSeePresident": "Neutrals can see revealed President", - "MadmatesSeePresident": "Madmates can see revealed President", - "ImpsSeePresident": "Impostors can see revealed President", - "PresidentDead": "Sorry, you can't force end the meeting after death.", - "PresidentEndMax": "No more force end meeting uses left!", - "PresidentRevealMax": "You have already revealed yourself...", - "PresidentRevealed": "[{0}] has chose to reveal themselves as President!", - "GuessPresident": "President has revealed themselves, you can't guess them.", - "PresidentRevealTitle": "PRESIDENT REVEAL", - - "LuckyProbability": "Probability of surviving a kill", - "ImpCanBeLucky": "Impostors can become Lucky", - "CrewCanBeLucky": "Crewmates can become Lucky", - "NeutralCanBeLucky": "Neutrals can become Lucky", - "ImpCanBeFool": "Impostors can become Fool", - "CrewCanBeFool": "Crewmates can become Fool", - "NeutralCanBeFool": "Neutrals can become Fool", - "ImpCanBeDoubleShot": "Impostors can have Double Shot", - "CrewCanBeDoubleShot": "Crewmates can have Double Shot", - "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", - "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", - "DisableReportWhenCamouflageIsActive": "Disable body reporting when сamouflage is active", - "CanUseCommsSabotage": "Can use comms sabotage", - "ModTag": "Moderator♥", - "ApplyModeratorList": "Apply Moderator List", - "VipTag": "VIP★", - "ApplyVipList": "Apply VIP List", - "AllowSayCommand": "Allow moderators to use /say command", - "KickCommandDisabled": "The kick command is currently disabled.", - "KickCommandNoAccess": "You do not have access to the kick command.", - "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reseaon]' to kick a player.\nExample :- /kick 5 not following rules", - "KickCommandKickHost": "You are not permitted to kick the host.", - "KickCommandKickMod": "You are not permitted to kick other moderators.", - "KickCommandKicked": "was kicked from the game by ", - "KickCommandKickedRole": "Their role was", - "BanCommandDisabled": "The ban command is currently disabled.", - "BanCommandNoAccess": "You do not have access to the ban command.", - "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", - "BanCommandBanHost": "You are not permitted to ban the host.", - "BanCommandBanMod": "You are not permitted to ban other moderators.", - "BanCommandBanned": "was banned from the game by ", - "BanCommandBannedRole": "Their role was", - "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", - "ColorCommandDisabled": "The modcolor command is currently disabled.", - "ColorCommandNoAccess": "You do not have access to the modcolor command.", - "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff", - "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", - "VipColorCommandDisabled": "The vipcolor command is currently disabled.", - "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", - "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff", - "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", - "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change color of your tag.\nExample :- /tagcolor ff00ff", - "midCommandDisabled": "The mid command is currently disabled.", - "midCommandNoAccess": "You do not have access to the mid command.", - "DisableVoteBan": "Disable VoteKick System", - "WarnCommandDisabled": "The warn command is currently disabled.", - "WarnCommandNoAccess": "You do not have access to the warn command.", - "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", - "WarnCommandWarnHost": "You are not permitted to warn the host.", - "WarnCommandWarnMod": "You are not permitted to warn other moderators.", - "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", - "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", - "SayCommandDisabled": "The say command is currently disabled.", - "MessageFromModerator": "MODERATOR", - "DeathReason.Kill": "Kill", - "DeathReason.Vote": "Ejected", - "DeathReason.Suicide": "Suicide", - "DeathReason.Spell": "Spelled", - "DeathReason.Cursed": "Cursed", - "DeathReason.Hex": "Hexed", - "DeathReason.Bite": "Bitten", - "DeathReason.Poison": "Poisoned", - "DeathReason.Gambled": "Guessed", - "DeathReason.FollowingSuicide": "Heartbroken", - "DeathReason.Bombed": "Exploded", - "DeathReason.Misfire": "Misfire", - "DeathReason.Torched": "Burned", - "DeathReason.Sniped": "Sniped", - "DeathReason.Execution": "Executed", - "DeathReason.Disconnected": "Disconnected", - "DeathReason.Fall": "Fall", - "DeathReason.Revenge": "Revenge", - "DeathReason.Eaten": "Eaten", - "DeathReason.Sacrifice": "Victim", - "DeathReason.Quantization": "Quantization", - "DeathReason.Overtired": "Overtired", - "DeathReason.Ashamed": "Ashamed", - "DeathReason.PissedOff": "Destroyed", - "DeathReason.Dismembered": "Dismembered", - "DeathReason.LossOfHead": "Strangled", - "DeathReason.Trialed": "Judged", - "DeathReason.Infected": "Infected", - "DeathReason.Jinx": "Jinxed", - "DeathReason.Pirate": "Plundered", - "DeathReason.Shrouded": "Shrouded", - "DeathReason.etc": "Other", - "DeathReason.Mauled": "Mauled", - "DeathReason.Hack": "Hacked", - "DeathReason.Curse": "Cursed", - "DeathReason.Drained": "Drained", - "DeathReason.Shattered": "Shattered", - "DeathReason.Trap": "Trapped", - "DeathReason.Targeted": "Targeted", - "DeathReason.Retribution": "Retribution", - "DeathReason.Slice": "Sliced", - "DeathReason.BloodLet": "Bleed", - "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", - "Alive": "Alive", - "Win": " Wins!", - - "Last-": "Last ", - "Madmate-": "Madmate ", - "Recruit-": "Recruit ", - "Charmed-": "Charmed ", - "Soulless-": "Soulless ", - "Infected-": "Infected ", - "Contagious-": "Contagious ", - "Admired-": "Admired ", - - "DeputyHandcuffCooldown": "Handcuff Cooldown", - "DeputyHandcuffMax": "Maximum Handcuffs", - "DeputyHandcuffedPlayer": "Handcuffed target", - "HandcuffedByDeputy": "You were handcuffed!", - "DeputyInvalidTarget": "Target cannot be handcuffed", - "DeputyHandcuffText": "Handcuff", - "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", - - "RejectShapeshift.AbilityWasUsed": "Ability was used", - "ShowShapeshiftAnimations": "Show Shapeshift animations", - - "EscapisMtarkedPosition": "You marked self position", - - "InvestigateCooldown": "Investigate Cooldown", - "InvestigateMax": "Maximum Investigations", - "InvestigateRoundMax": "Maximum Investigations in one round", - - "Color.Red": "Red", - "Color.Green": "Green", - "Color.Gray": "Gray", - "InvestigatorInvestigatedPlayer": "Player Investigated", - "InvestigatorInvalidTarget": "Can not investigate", - "InvestigatorButtonText": "Check", - - "Investigator.Suspicion": "Suspicion", - "Investigator.Role": "Role", - "SabotageCooldownControl": "Sabotage Cooldown Control", - "SabotageCooldown": "Sabotage Cooldown", - "SabotageTimeControl": "Sabotage Duration Control", - "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", - "SkeldO2TimeLimit": "The Skeld O2 Time Limit", - "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", - "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", - "PolusReactorTimeLimit": "Polus Reactor Time Limit", - "AirshipReactorTimeLimit": "Airship Reactor Time Limit", - "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", - "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", - "CommandList": "★ Command list:", - "Command.now": "→ Display active Settings", - "Command.roles": "[RoleName] → Display Role description", - "Command.myrole": "→ Displays a description of your role", - "Command.lastresult": "→ Display match results", - "Command.winner": "→ Display winners", - "CommandOtherList": "● Other commands:", - "Command.color": "[Color] → Change your color", - "Command.rename": "[Name] → Change Host Name", - "Command.quit": "→ I don't want to enter this lobby anymore", - "CommandHostList": "▲ Host Commands:", - "Command.say": "[Content] → Send message as Host", - "Command.mw": "[Seconds] → Set the message waiting duration", - "Command.solvecover": "→ Fix an issue where role names overlap the messages", - "Command.kill": "[Player ID] → Kill assigned player", - "Command.exe": "[Player ID] → Eject assigned player", - "Command.level": "[Level] → Change your in-game level", - "Command.idlist": "→ Display a list of player IDs", - "Command.qq": "→ Lobby will be posted on QQ website (China only)", - "Command.dump": "→ Output Log to Desktop", - "Command.death": "→ Display info on how you died", - "Command.icons": "Icon Meanings\n† - This player was spelled by a Witch and will die if the Witch is not killed by the end of the meeting\n乂 - This player was hexed by a Hex Master and will die if the Hex Master is not killed by the end of the meeting\n❖ - This player was hexed by an Occultist and will die if the Occultist is not killed by the end of the meeting\n◈ - This player was shrouded by a Shroud and will die if the Shroud is not killed by the end of the meeting\n⦿ - This player is being dueled by a Pirate\n♥ - This player is a Lover\n⚠ - This player is a Snitch who has finished their tasks", - "Command.iconinfo": "→ Display info on in-meeting icons", - "Command.iconhelp": "→ Display info on in-meeting icons to everyone", - "Remaining.ImpostorCount": "Impostors left: ", - "Remaining.NeutralCount": "Neutral Killers left: ", - "EnableKillerLeftCommand": "Enable use of /kcount command", - "SeeEjectedRolesInMeeting": "See ejected roles in meetings", - - "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", - "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", - "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", - "NemesisKillDead": "Choose a living player to take revenge", - "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", - "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", - - "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period of time!", - "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", - "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", - "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", - "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer, this is most likely suicide.", - "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", - "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", - "GuessDead": "Sorry, but it's impossible to guess roles after your death", - "GuessSuperStar": "The Super Star can't be guessed.... you thought it would be that easy, right?", - "GuessNotifiedBait": "Bait can't be guessed because it was announced, you thought it would be that easy, right?", - "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", - "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", - "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", - "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", - "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", - "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", - "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", - "GuessKill": "{0} was guessed", - "GuessNull": "Please select an ID of a living player to guess their role", - "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "GGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", - "EGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", - "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all of their tasks are done? Nice try.... You're not getting out of this that easily.", - "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot so you get another chance!", - "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", - "GuessDisabled": "Sorry, the host restricted guessing for your role.", - "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", - "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", - "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", - "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", - "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", - "GuessShielded": "Sorry, you can't guess the player who is shielded by Medic", - "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", - "MimicDeadMsg": "Mimic's hint: ", - "FortuneTellerCheck": "According to your fortune...", - "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", - "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", - "FortuneTellerCheckReachLimit": "You've run out of fortunes.", - "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", - "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", - "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", - "EraserEraseSelf": "Unfortunately, you can't erase yourself.... Wait, why would you do that in the first place?!", - "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", - "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", - - "MediumContactLimit": "Max number of contacts (ability uses)", - "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", - "MediumTitle": "MEDIUM", - "MediumHelp": "/ms yes to agree\n/ms no to disagree", - "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", - "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", - "MediumDone": "You successfully responded to the Medium.", - "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", - "MediumNotifySelf": "You established contact with {0}, please ask questions to them and wait for them to respond.\n\nRemaining ability uses: {1}", - "MediumKnowPlayerDead": "Someone died somewhere", - - "ByBard": "by Bard", - "ByBardGetFailed": "oops, I seem to be out of inspiration.", - "GangsterSuccessfullyRecruited": "You successfully recruited a player", - "GangsterRecruitmentFailure": "Target cannot be recruited", - "BeRecruitedByGangster": "You have been recruited by the Gangster", - "KamikazeHostage": "Can't hold target hostage", - "VeteranOnGuard": "Ability in use", - "VeteranOffGuard": "Ability expired, {0} uses remain", - "VeteranMaxUsage": "Ability use limit reached", - "GrenadierSkillInUse": "Ability in use", - "GrenadierSkillStop": "Ability expired", - "TicketsStealerGetTicket": "You've got {0} votes", - "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", - "SpeedBoosterTaskDone": "Your speed is {0} now", - "SpeedBoosterSpeedLimit": "You reached your maximum speed (3x)", - "CleanerCleanBody": "The body has been cleaned", - "QuickShooterStoraging": "Bullets stored successfully", - "VampireTargetDead": "Target died", - "PoisonerTargetDead": "Target died", - "BloodlustAdded": "Your bloodlust is now active!", - "WarlockNoTarget": "Manipulation failed due to no target", - "WarlockNoTargetYet": "You haven't mark a target.", - "WarlockTargetDead": "Manipulation failed due to target dead", - "WarlockControlKill": "Target died", - "OnCelebrityDead": "Warning: Celebrity death!", - "OnCyberDead": "Warning: Cyber died!", - "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", - "TeleportedByTransporter": "Swapping places with: {0}", - "ErrorTeleport": "Teleport failed", - "LostRoleByEraser": "You lost your role because of the Eraser", - "KilledByScavenger": "You were killed by the Scavenger and thus teleported off-map", - "SnitchDoneTasks": "Call a meeting to find the impostors", - "SwooperCanVent": "Vent to turn invisible", - "SwooperInvisState": "You're invisible", - "SwooperInvisStateOut": "You're now visible", - "SwooperInvisInCooldown": "Swoop cooldown isn't up yet, swooping failed", - "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", - "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", - "WraithCanVent": "Vent to turn invisible", - "WraithInvisState": "You are invisible", - "WraithInvisStateOut": "You are visible again", - "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", - "WraithInvisStateCountdown": "Invisibility will expire in {0}s", - "WraithInvisCooldownRemain": "{0}s left in invisibility", - "BKInProtect": "Currently immortal", - "BKProtectOut": "Shield expired", - "BKSkillTimeRemain": "You're immune for {0} seconds", - "BKSkillNotice": "Kill a player to enter immune status", - "BKOffsetKill": "Someone tried killing you", - "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", - "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", - "FollowerBetPlayer": "You're now following your target", - "FollowerBetOnYou": "The Follower is now following you", - "CultistCharmedPlayer": "You successfully charmed a player", - "CharmedByCultist": "You have been charmed by the Cultist", - "CultistInvalidTarget": "Target cannot be charmed", - "KillBaitNotify": "You'll self-report in {0}s", - "InfectiousInvalidTarget": "Target cannot be infected", - "BittenByInfectious": "You were infected by the Infectious!", - "InfectiousBittenPlayer": "You successfully infected a player", - "GuessNotAllowed": "Sorry, your role does not have access to guessing.", - "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", - "GuessPhantom": "You can't guess a Phantom, that allows them to win!", - "PacifistOnGuard": "Ability used, {0} uses remain", - "PacifistMaxUsage": "Ability use limit reached", - "PacifistSkillNotify": "Pacifist reset your kill cooldown", - "BeRecruitedByJackal": "You have been recruited by the Jackal", - "CoronerTrackRecorded": "Track recorded", - "CoronerNoTrack": "Nothing to track", - "CoronerIsTrackingYou": "The Coroner is tracking you!", - "TrackerLastRoomMessage": "The evaluation of your tracking showed that the last room in which your target was located was:[{0}]", - "MerchantAddonDelivered": "Add-on sold", - "MerchantAddonSell": "The Merchant sold you a new Add-on", - "MerchantAddonSellFail": "Could not sell an Add-on", - "BribedByMerchant": "The Merchant bribed you, you can't kill him", - "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", - "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", - "TrapTrapsterBody": "Trap Trapster's body", - "TrapConsecutiveBodies": "Trap consecutive bodies", - "HauntedByEvilSpirit": "Haunted by an Evil Spirit", - "MonarchKnightCooldown": "Knight Cooldown", - "MonarchKnightMax": "Maximum Knights", - "MonarchKnightedPlayer": "You successfully knighted a player!", - "KnightedByMonarch": "You have been knighted by a Monarch!", - "MonarchInvalidTarget": "Target cannot be knighted", - "GhostTransformTitle": "Your Role Has Transformed!", - "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", - "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", - "OverseerRevealCooldown": "Reveal Cooldown", - "OverseerRevealTime": "Reveal Time", - "OverseerVision": "Overseer Vision", - "MerchantMaxSell": "Max number of Add-ons to sell", - "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", - "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", - "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", - "MerchantTargetCrew": "Can sell to Crewmates", - "MerchantTargetImpostor": "Can sell to Impostors", - - "MerchantTargetNeutral": "Can sell to Neutrals", - "MerchantSellHelpful": "Can sell Helpful Add-ons", - "MerchantSellHarmful": "Can sell Harmful Add-ons", - "MerchantSellMixed": "Can sell Mixed Add-ons", - "MerchantSellExperimental": "Can sell experimental Add-ons", - "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", - "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", - "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", - - "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", - "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", - "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", - "SpiritcallerProtectTime": "Evil Spirit ability protect time", - "SpiritcallerCauseVision": "Evil Spirit ability caused vision", - "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", - "Message.SetToSeconds": "Set to [{0}] seconds.", - "Message.MessageWaitHelp": "Specify the first argument in seconds.", - "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", - "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", - "Message.SyncButtonLeft": "There are {0} more emergency buttons left", - "Message.Executed": "{0} was executed", - "Message.HideGameSettings": "Game settings have been hidden by the host.", - "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", - "Message.NoDescription": "No description", - "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", - "Message.BannedByBanList": "{0} was banned because they were banned in the past.", - "Message.BannedByEACList": "{0} has been banned because he is in EAC list of Banned people.", - "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", - "Message.DumpcmdUsed": "{0} used /dump command.", - "Message.KickedByNoFriendCode": "{0} was kicked because their friend code does not exist.", - "Message.TempBannedByNoFriendCode": "{0} was temporary banned because their friend code does not exist.", - "Message.AddedPlayerToBanList": "Added {0} to the ban list", - "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", - "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", - "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", - "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", - "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", - "Message.NoticeByEAC": "[{0}]Detected:{1}", - "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", - "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", - "Message.KickedByWhiteList": "{0} kicked because friendcode not found in WhiteList.txt", - "Message.SetLevel": "Your game level is set to: {0}", - "Message.SetColor": "Your color is set to: {0}", - "Message.SetName": "Your name is set to: {0}", - "Message.AllowLevelRange": "The game level can be set in the range: 0-100", - "Message.AllowNameLength": "Nickname can be set length: 1-10", - "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", - "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", - "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", - "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", - "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", - "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", - "Message.HostLeftGameInGame": "★Warning★ Host left the game, in next time game wouldn't start normally. Please, exit the lobby or wait until new Host opens a lobby.", - "Message.HostLeftGameInLobby": "★Warning★ Host left the game, in next time game wouldn't start normally. If new host has TOHE, you need to re-enter the lobby to play normally.", - "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, just start a game and end it immediately to reset the lobby!", - "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please, exit the lobby or wait until new Host opens a lobby.", - "Message.LobbyShared": "The lobby has successfully been shared!", - "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", - "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", - "Message.YTPlanSelected": "In the next game, your role will be {0}", - "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", - "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. In case the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", - "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", - "Message.MaxPlayers": "Maximum players set to ", - "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little bit about ghost roles...\n\nGhost roles drastically impact the game, so not recommended for smaller lobbies, if you're unfamiliar.\n\nSpawning:\nGhost-roles only spawn after death, the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g sheriff), your tasks as a ghost-role aren't needed for task-win", - - "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", - "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", - "WarningTitle": "Warning!", - "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", - "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", - - "AntiBlackoutProtectionTitle": "Anti Blackout", - "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normaly", - "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in last meeting.\n", - "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", - - "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE installed.", - "Warning.NoModHost": "TOHE is not installed on the host", - "Warning.MismatchedVersion": "{0} has a different version of {1}", - "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", - "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", - "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", - "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", - "Error.InvalidColor": "Error: Only default colors are available", - "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors, otherwise it will result in a serious error", - "ErrorLevel1": "Bugs may occur.", - "ErrorLevel2": "This may be a bug.", - "ErrorLevel3": "This version shouldn't have been released.", - "TerminateCommand": "Abort Command", - "ERR-000-000-0": "No Error", - "ERR-000-900-0": "Test Error Lv.0", - "ERR-000-910-1": "Test Error Lv.1", - "ERR-000-920-2": "Test Error Lv.2", - "ERR-000-930-3": "Test Error Lv.3", - "ERR-000-804-1": "TownofHost-Enhanced does not support the Vanilla HnS, so unloaded.", - "ERR-001-000-3": "Main dictionary has duplicated keys.", - "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", - "DefaultSystemMessageTitle": "SYSTEM MESSAGE", - "MessageFromTheHost": "HOST MESSAGE", - "MessageFromEAC": "EAC", - "DetectiveNoticeTitle": "INVESTIGATION", - "SleuthNoticeTitle": "SLEUTH", - "GuessKillTitle": "GUESSING INFO", - "CelebrityNewsTitle": "CELEBRITY", - "CyberNewsTitle": "CYBER", - "GodAliveTitle": "GOD ", - "WorkaholicAliveTitle": "WORKAHOLIC", - "BaitAliveTitle": "BAIT", - "MessageFromKPD": "KARPED1EM ", - "MessageFromSponsor": "SPONSOR MESSAGE ", - "MessageFromDev": "DEVELOPER MESSAGE ", - "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", - "MimicMsgTitle": "MIMIC", - "EraserEraseMsgTitle": "ERASER", - "MorticianCheckTitle": "CORPSE EXAMINATION", - "NemesisRevengeTitle": "NEMESIS", - "RetributionistRevengeTitle": "RETRIBUTIONIST", - "TabGroup.SystemSettings": "System Settings", - "TabGroup.GameSettings": "Game Settings", - "TabGroup.CrewmateRoles": "Crewmate Roles", - "TabGroup.NeutralRoles": "Neutral Roles", - "TabGroup.ImpostorRoles": "Impostor Roles", - "TabGroup.Addons": "Add-Ons", - "TabGroup.OtherRoles": "Experimental Roles (Not recommended)", - "TabGroup.TaskSettings": "Game Modifiers", - "OtherRoles.CrewmateRoles": "★ Crewmate Roles", - "OtherRoles.NeutralRoles": "★ Neutral Roles", - "OtherRoles.ImpostorRoles": "★ Impostor Roles", - "OtherRoles.Addons": "★ Add-Ons", - "ActiveRolesList": "Active Roles List", - "ForExample": "Example Use", - "updateButton": "Update", - "updatePleaseWait": "Please Wait...", - "updateManually": "Update failed.\nPlease Update Manually.", - "updateRestart": "Update Finished!\nPlease restart the game.", - "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease update.", - "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", - "UnsupportedVersion": "Unsupported Among Us version.\nPlease update Among Us", - "DisabledByProgram": "Public rooms have been disabled by the program", - "EnterVentToWin": "Enter Vent to Win!!", - "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", - "FireworkerPutPhase": "{0} Fireworker Left", - "FireworkerWaitPhase": "Wait for it...", - "FireworkerReadyFirePhase": "Fire!", - "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", - "On": "ON", - "Off": "OFF", - "ColoredOn": "ON", - "ColoredOff": "OFF", - "CurrentActiveSettingsHelp": "Current Active Settings Help", - "WitchCurrentMode": "Current Mode", - "WitchModeKill": "Kill", - "WitchModeSpell": "Spell", - "HexMasterModeHex": "Hex", - "HexMasterModeKill": "Kill", - "PoisonerPoisonButtonText": "Poison", - "WitchModeDouble": "Double Click = Kill, Single Click = Spell", - "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", - "BountyCurrentTarget": "Current Target", - "Roles": "Roles", - "Settings": "Settings", - "Addons": "Add-Ons", - "LastResult": "★ Match Results", - "LastEndReason": "★ End Reason", - "KillLog": "Kill Log", - "Maximum": "Max", - "RoleRate": "ON", - "RoleOn": "ALWAYS", - "RoleOff": "OFF", - "Chance0": "0%", - "Chance5": "5%", - "Chance10": "10%", - "Chance15": "15%", - "Chance20": "20%", - "Chance25": "25%", - "Chance30": "30%", - "Chance35": "35%", - "Chance40": "40%", - "Chance45": "45%", - "Chance50": "50%", - "Chance55": "55%", - "Chance60": "60%", - "Chance65": "65%", - "Chance70": "70%", - "Chance75": "75%", - "Chance80": "80%", - "Chance85": "85%", - "Chance90": "90%", - "Chance95": "95%", - "Chance100": "100%", - "Preset": "Preset", - "Preset_1": "Preset 1", - "Preset_2": "Preset 2", - "Preset_3": "Preset 3", - "Preset_4": "Preset 4", - "Preset_5": "Preset 5", - "Standard": "Standard", - "GameMode": "Game Mode", - "PressTabToNextPage": "Press Tab or Number for Next Page...", - "RoleSummaryText": "Role Summary:", - "doOverride": "Override %role%'s Tasks", - "assignCommonTasks": "%role% has Common Tasks", - "roleLongTasksNum": "Amount of Long Tasks for %role%", - "roleShortTasksNum": "Amount of Short Tasks for %role%", - "Format.Players": "{0}", - "Format.Seconds": "{0}s", - "Format.Percent": "{0}%", - "Format.Times": "{0}", - "Format.Multiplier": "{0}x", - "Format.Votes": "{0}", - "Format.Pieces": "{0}", - "Format.Health": "{0}", - "Format.Level": "{0}", - "KillButtonText": "Kill", - "ReportButtonText": "Report", - "VentButtonText": "Vent", - "SabotageButtonText": "Sabotage", - "SniperSnipeButtonText": "Snipe", - "FireworkerExplosionButtonText": "Detonate", - "FireworkerInstallAtionButtonText": "Install", - "MercenarySuicideButtonText": "Suicide Timer", - "WarlockCurseButtonText": "Curse", - "NinjaShapeshiftText": "Kill", - "NinjaMarkButtonText": "Mark", - "WitchSpellButtonText": "Spell", - "VampireBiteButtonText": "Bite", - "MinerTeleButtonText": "Warp", - "ArsonistDouseButtonText": "Douse", - "PuppeteerOperateButtonText": "Manipulate", - "WarlockShapeshiftButtonText": "Spell", - "BountyHunterChangeButtonText": "Swap", - "EvilTrackerChangeButtonText": "Track", - "InnocentButtonText": "Frame", - "PelicanButtonText": "Eat", - "DeceiverButtonText": "Cheat", - "PursuerButtonText": "Trick", - "GangsterButtonText": "Recruit", - "RevolutionistDrawButtonText": "Win over", - "HaterButtonText": "Hatred", - "MedicalerButtonText": "Protect", - "DemonButtonText": "Attack", - "SoulCatcherButtonText": "Teleport", - "LightningButtonText": "Evaporate", - "ProvocateurButtonText": "Greet", - "ButcherButtonText": "Dismember", - "BomberShapeshiftText": "Explode", - "QuickShooterShapeshiftText": "Keep", - "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", - "CamouflagerShapeshiftTextAfterDisguise": "Duration", - "AnonymousShapeshiftText": "Hack", - "DefaultShapeshiftText": "Shift", - "CleanerReportButtonText": "Clean", - "SwooperVentButtonText": "Swoop", - "SwooperRevertVentButtonText": "Expose", - "WraithVentButtonText": "Vanish", - "WraithRevertVentButtonText": "Expose", - "VectorVentButtonText": "Hop", - "VeteranVentButtonText": "Alert", - "GrenadierVentButtonText": "Flash", - "MayorVentButtonText": "Button", - "SheriffKillButtonText": "Shoot", - "UndertakerButtonText": "Mark", - "ArsonistVentButtonText": "Ignite", - "RevolutionistVentButtonText": "Revolution", - "FollowerKillButtonText": "Follow", - "PacifistVentButtonText": "Reset", - "CultistKillButtonText": "Charm", - "InfectiousKillButtonText": "Infect", - "MonarchKillButtonText": "Knight", - "OverseerKillButtonText": "Reveal", - "DisabledBySettings": "Disabled by Settings", - "Disabled": "Disabled", - "FailToTrack": "Failed To Track", - "KillCount": "Kills: {0}", - "CantUse.lastroles": "Unable to use /lastroles during a game.", - "CantUse.killlog": "Unable to use /killlog during a game.", - "CantUse.lastresult": "Unable to use /lastresult during a game.", - "IllegalColor": "Please enter the correct color", - "DisableUseCommand": "The Host's settings do not allow this command to be used.", - "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", - "PlayerIdList": "List of player IDs: ", - "CancelStartCountDown": "The starting countdown was cancelled", - "RestTOHESetting": "TOHE settings have been restored to default", - "FPSSetTo": "FPS Set To: {0}", - "HostKillSelfByCommand": "The lobby Host decided to commit suicide", - "SyncCustomSettingsRPC": "Synchronized RPC", - "Mode": "Mode", - "Target": "Target", - "PlayerInfo": "Player Info", - "NoInfoExists": "No Info Exists", - "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", - "PlayerLeftByError": "Game will auto-end to prevent black screens.", - "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", - "KickBecauseLowLevel": "{0} was kicked because their level was too low", - "TempBannedBecauseLowLevel": "{0} was temporary banned because their level was too low", - "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", - - "FFADisplayScore": "Ranking: {0} Score: {1}", - "FFATimeRemain": "Time Remaining: {0} second(s)", - - "GameOver": "Game Over", - "TOHEOptions": "TOHE Options", - "Cancel": "Cancel", - "Back": "Back", - "Yes": "Yes", - "No": "No", - "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", - "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, game will end to prevent black screen.", - "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred, to prevent black screen, turn off [{1}] in settings.", - "AntiBlackOutNotifyInLobby": "An unknown error occurred, to prevent black screen. Sadly, this error exists in all Town of Host versions. Automatic ending game is required, or the game will not start.", - "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent black screen.", - "AntiBlackOutRequestHostToForceEnd": "You were the reason of the black screen, you are asking the host to stop the game...", - "AntiBlackOutHostRejectForceEnd": "You were the reason of the black screen, and the host is not going to end the game, we will disconnect you soon.", - "NextPage": "Next Page", - "PreviousPage": "Previous Page", - "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", - "PressF1ShowMainRoleDes": "Press F1: Show Role Description", - "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", - "FakeTask": "Fake Tasks:", - "PVP.ATK": "Attack", - "PVP.DF": "Defend", - "PVP.RCO": "Recover", - "SettingsAreLoading": "Loading\nsettings...", - "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", - "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", - "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", - "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", - "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", - "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", - "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", - "IsGood": "{0} was a good guy", - "BelongTo": "{0} belongs to {1}", - "PlayerIsRole": "{0} was The {1}", - "PlayerExiled": "{0} was ejected", - "NoImpRemain": "0 Impostors remain", - "OneImpRemain": "1 Impostor remains", - "TwoImpRemain": "2 Impostors remain", - "ThreeImpRemain": "3 Impostors remain", - "ImpRemain": "{0} Impostors remaining", - "NeutralRemain": "\n{0} Neutral Killers remain", - "OneNeutralRemain": "\n{0} Neutral Killer remains", - "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", - "GameOverReason.HumansByTask": "The Crewmates completed all tasks", - "GameOverReason.HumansDisconnect": "Crewmates disconnected", - "GameOverReason.ImpostorByVote": "The Crewmates were ejected", - "GameOverReason.ImpostorByKill": "The Impostors killed everyone", - "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", - "GameOverReason.ImpostorDisconnect": "Impostors disconnected", - "FortuneTellerCheck.Honest": "Looks like [{0}] is being honest.", - "FortuneTellerCheck.Impulse": "Looks like [{0}] is suppressing some kind of impulse.", - "FortuneTellerCheck.Weirdo": "Looks like [{0}] is mixed in crowd.", - "FortuneTellerCheck.Blockbuster": "Looks like [{0}] got big desires to change something.", - "FortuneTellerCheck.Strong": "Looks like [{0}] has a strong power but is dim in society.", - "FortuneTellerCheck.Incomprehensible": "Looks like [{0}] is being misunderstood.", - "FortuneTellerCheck.Enthusiasm": "Looks like [{0}] speaks with everyone very enthusiastic.", - "FortuneTellerCheck.Disturbed": "Looks like [{0}] is disturbed by something.", - "FortuneTellerCheck.None": "Looks like [{0}] has just an ordinary life.", - "FortuneTellerCheck.Glitch": "TV2gPZ4wUJWYr{0}05gA6T5PKzBG17", - "FortuneTellerCheck.HideMsg": "Looks like [{0}] hides secrets.", - "FortuneTellerCheck.Love": "♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥", - "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", - "DevAndSpnTitle": "TOHE family", - "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", - "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", - "SunnyboyChance": "Sunnyboy Chance", - "BardChance": "Bard Chance", - "VampiressChance": "Vampiress Chance", - "NukerChance": "Nuker Chance", - "SkeldChance": "Chance that the map is The Skeld", - "MiraChance": "Chance that the map is MIRA HQ", - "PolusChance": "Chance that the map is Polus", - "DleksChance": "Chance that the map is dlekS ehT", - "AirshipChance": "Chance that the map is Airship", - "FungleChance": "Chance that the map is The Fungle", - "UseMoreRandomMapSelection": "Use a more random map selection", - "CamouflageMode.Default": "Default", - "CamouflageMode.Host": "Host", - "CamouflageMode.Random": "Random", - "CamouflageMode.OnlyRandomColor": "Only Random Color", - "CamouflageMode.Karpe": "KARPED1EM", - "CamouflageMode.Lauryn": "Lauryn", - "CamouflageMode.Moe": "Moe", - "CamouflageMode.Pyro": "Pyro", - "CamouflageMode.ryuk": "ryuk", - "CamouflageMode.Gurge44": "Gurge44", - "CamouflageMode.TommyXL": "TommyXL", - "DeathCmd.HeyPlayer": "Hey ", - "DeathCmd.YouAreRole": ", looks like you're the ", - "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", - "DeathCmd.KillerName": "You were killed by ", - "DeathCmd.KillerRole": "Their role is ", - "DeathCmd.DeathReason": "Your cause of death was ", - "DeathCmd.YourName": "You are ", - "DeathCmd.YourRole": "Your role is ", - "DeathCmd.Ejected": "You were ejected during a meeting", - "DeathCmd.Misfired": "You misfired.", - "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", - "DeathCmd.Lovers": "Your lover had died.", - - "RpsCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", - "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", - "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", - "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I just have the world's worst luck algorithm.", - - "CoinFlipCommandInfo": "This Command can only be used when in lobby or after you die.", - "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", - - "GNoCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", - "GNoLost": "Oh, you were so close! Just one more guess, and you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", - "GNoLow": "Oh, you're really nailing this! It's so low, I almost need a shovel to dig it up!\nYou have {0} guesses left!", - "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high, I need a telescope to see it from here! \nYou have {0} guesses left!", - "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", - - "RandCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", - "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", - - "ChanceToMiss": "Chance to miss a kill", - - "SoulCollectorPointsToWin": "Required number of souls", - "SoulCollectorTarget": "You have predicted the death of {0}", - "SoulCollectorTitle": "SOUL COLLECTOR", - "CollectOwnSoulOpt": "Can collect their own soul", - "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", - - "ChronomancerKillCooldown": "Time to fully charge the kill button", - - "ShamanButtonText": "Voodoo", - "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", - "VoodooCooldown": "Voodoo Cooldown", - - "AdminWarning": "Admin Table in use!", - "VitalsWarning": "Vitals in use!", - "DoorlogWarning": "Doorlogs in use!", - "CameraWarning": "Cameras in use!", - "MinWaitAutoStart": "Minutes to wait before auto-starting", - "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", - "PlayerAutoStart": "Minimum Player Threshold to auto-start", - "AutoStartTimer": "Initial countdown for auto-starting", - "AutoPlayAgainCountdown": "Delay before re-entering lobby", - "AutoPlayAgain": "Auto Play Again", - "AutoRehost": "Auto Re-Host on Bad Disconnect", - "CountdownText": "Rejoining lobby in {0}s", - "TimeMasterSkillDuration": "Time Shield Duration", - "TimeMasterSkillCooldown": "Time Shield Cooldown", - "TimeMasterOnGuard": "Time Shield is active!", - "TimeMasterSkillStop": "Time Shield has ended!", - "TimeMasterVentButtonText": "Time Shield", - "BodyCannotBeReported": "Body could not be reported", - "BurstKillDelay": "Burst Kill Delay", - "BurstNotify": "That was a Burst! Get in a vent or die.", - "ImpCanBeBurst": "Impostors can become Burst", - "CrewCanBeBurst": "Crewmates can become Burst", - "NeutralCanBeBurst": "Neutrals can become Burst", - "BurstFailed": "Burst failed to bomb you", - "ShroudButtonText": "Shroud", - "ShroudCooldown": "Shroud Cooldown", - "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", - "LudopathRandomKillCD": "Maximum kill cooldown", - "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", - "GodfatherTargetCountMode": "Killer turns into", - "GodfatherCount_Refugee": "Refugee", - "GodfatherCount_Madmate": "Madmate", - "MissChance": "Chance To Miss", - "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", - "HawkMissed": "Missed!", - "HawkCanKillNum": "Max Slices", - "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", - "BloodMoonCanKillNum": "Max BloodLettings", - "BloodMoonTimeTilDie": "Time Until Death", - "DeathTimer": "Death In: {DeathTimer}s", - "BerserkerKillCooldown": "Berserker kill cooldown", - "BerserkerMax": "Max level that Berserker can reach", - "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", - "BerserkerOneKillCooldown": "Kill cooldown after unlocking", - "BerserkerTwoCanScavenger": "Unlock scavenged kills", - "BerserkerThreeCanBomber": "Unlock bombed kills", - "BerserkerFourCanNotKill": "Unlock immortality", - "BerserkerMaxReached": "Maximum level reached!", - "BerserkerLevelChanged": "Increased level to {0}", - "BerserkerLevelRequirement": "Level requirement for unlock", - "KilledByBerserker": "Killed by Berserker", - - "ImpCanBeUnlucky": "Impostors can become Unlucky", - "CrewCanBeUnlucky": "Crewmates can become Unlucky", - "NeutralCanBeUnlucky": "Neutrals can become Unlucky", - "BlackmailerSkillCooldown": "Blackmail Cooldown", - "BlackmailerMax": "Maximum times blackmailed players may speak", - "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", - "BlackmaileKillTitle": "BLACKMAILER", - "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", - "UnluckyKillSuicideChance": "Chance to suicide from killing", - "UnluckyVentSuicideChance": "Chance to suicide from venting", - "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", - "UnluckySabotageSuicideChance": "Chance to suicide from opening a door", - "ImpCanBeVoidBallot": "Impostors can become VoidBallot", - "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", - "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", - "ImpCanBeAware": "Impostors can become Aware", - "NeutralCanBeAware": "Neutrals can become Aware", - "CrewCanBeAware": "Crewmates can become Aware", - "AwareKnowRole": "Knows the role of player", - "AwareInteracted": "{0} tried to reveal your role.", - "AwareTitle": "AWARE MESSAGE", - "LighterVentButtonText": "Light", - "LighterSkillCooldown": "Light Cooldown", - "LighterSkillDuration": "Light Duration", - "LighterVisionNormal": "Increased Vision", - "LighterVisionOnLightsOut": "Increased Vision During Lights Out", - "LighterSkillInUse": "Ability in use", - "LighterSkillStop": "Ability expired", - "StealthDarkened": "Darkened: {0}", - "StealthExcludeImpostors": "Ignore Impostors when Blinding", - "StealthDarkenDuration": "Blinding Duration", - "PenguinAbductTimerLimit": "Dragging Time", - "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", - "PenguinKillButtonText": "Drag", - "PenguinTimerText": "Drag Timer", - "PenguinTargetOnCheckMurder": "You are grabbed, Try escape that first!", - "WitnessTime": "Max Time after killing where killer appears red", - "WitnessButtonText": "Examine", - "WitnessFoundInnocent": "✓", - "WitnessFoundKiller": "⚠", - "SwapperMax": "Maximum swaps", - "CanSwapSelfVotes": "Can exchange your own votes.", - "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", - "CantSwapSelf": "Can't exchange of one's own vote", - "SwapVote": "The votes of {0} and {1} were swapped!", - "SwapDead": "Sorry, you can't swap votes after death.", - "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", - "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", - "Swap1": "Swap target 1 selected", - "Swap2": "Swap target 2 selected", - "CancelSwap": "Cleared your previous swap!", - "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your target is dead.", - "Swap1=Swap2": "The target you input is same as Swap target 1.\nPls input a different one", - "SwapTitle": "SWAPPER", - "SwapperTryHideMsg": "Try to hide Swapper's command", - "SwapperPreResult": "Currently you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", - "ImpCanBeFragile": "Impostors can become Fragile", - "NeutralCanBeFragile": "Neutrals can become Fragile", - "CrewCanBeFragile": "Crewmates can become Fragile", - "ImpCanKillFragile": "Impostors can force kill Fragile", - "NeutralCanKillFragile": "Neutrals can force kill Fragile", - "CrewCanKillFragile": "Crewmates can force kill Fragile", - "FragileKillerLunge": "Killer lunges on kill", - "CrusaderSkillLimit": "Maximum Crusades", - "CrusaderSkillCooldown": "Crusade Cooldown", - "CrusaderKillButtonText": "Crusade", - "JailorKillButtonText": "Jail", - "AgitaterKillButtonText": "Pass", - "HasSerialKillerBuddy": "Has Serial Killer buddy", - "ChanceToSpawn": "Chance to spawn", - "ChanceToSpawnAnother": "Chance to spawn another", - "BloodlustKillCD": "Bloodlust Kill Cooldown", - "BloodlustPlayerCount": "Max players alive for Bloodlust", - "ReflectHarmfulInteractions": "Reflect harmful interactions", - - "ImpCanBeDiseased": "Impostors can become Diseased", - "NeutralCanBeDiseased": "Neutrals can become Diseased", - "CrewCanBeDiseased": "Crewmates can become Diseased", - "DiseasedCDOpt": "Increase the cooldown by", - "DiseasedCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeAntidote": "Impostors can become Antidote", - "NeutralCanBeAntidote": "Neutrals can become Antidote", - "CrewCanBeAntidote": "Crewmates can become Antidote", - "AntidoteCDOpt": "Decrease the cooldown by", - "AntidoteCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeStubborn": "Impostors can become Stubborn", - "NeutralCanBeStubborn": "Neutrals can become Stubborn", - "CrewCanBeStubborn": "Crewmates can become Stubborn", - - "ImpCanBeAvanger": "Impostors can become Avenger", - "NeutralCanBeAvanger": "Neutrals can become Avenger", - "CrewCanBeAvanger": "Crewmates can become Avenger", - "ImpCanBeSleuth": "Impostors can become Sleuth", - "CrewCanBeSleuth": "Crewmates can become Sleuth", - "NeutralCanBeSleuth": "Neutrals can become Sleuth", - "SleuthCanKnowKillerRole": "Can find the role of the killer", - "SleuthNoticeKiller": "\nThe killer's role is {0}.", - "SleuthNoticeVictim": "{0}'s role is {1}.", - "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", - "BomberDiesInExplosion": "Bomber dies in their explosion", - "ImpostorsSurviveBombs": "Impostors survive bombs", - "NukeRadius": "Nuke radius (12x is very large)", - "NukeCooldown": "Nuke Cooldown", - - "MasochistKillMax": "Amount of attacks needed to win", - "GuessMasochist": "You just tried to guess a Masochist!\nThey're now one step closer to winning!\n\nDo not guess them again.", - "MasochistKill": "You were attacked!", - "SelfGuessMasochist": "You can't self guess as a Masochist, you cheater!", - "GuessMasochistBlocked": "Masochist cannot guess due to self guessing.", - - "RememberCooldown": "Imitate Cooldown", - "RefugeeKillCD": "Refugee's Kill Cooldown", - "RememberedNeutralKiller": "You remembered you were a neutral killer!", - "RememberedMaverick": "You remembered you were a Maverick!", - "RememberedPursuer": "You remembered you were a Pursuer!", - "RememberedFollower": "You remembered you were a Follower!", - "RememberedAmnesiac": "You failed to remember your role.", - "RememberedImitator": "You remmebered you were an Imitator.", - "RememberedImpostor": "You remembered you were an Impostor!", - "RememberedCrewmate": "You remembered you were a crewmate!", - "ImitatorImitated": "An Imitator imitated your role!", - "ImitatorInvalidTarget": "Imitation failed", - "RememberButtonText": "Remember", - "ImitatorKillButtonText": "Imitate", - "IncompatibleNeutralMode": "If neutral is incompatible, turn into", - "RememberedYourRole": "An Amnesiac rmembered your role!", - "YouRememberedRole": "You remembered who you were!", - - "BanditStealMode": "Steal Mode", - "BanditStealMode_OnMeeting": "On Meeting", - "BanditStealMode_Instantly": "Instantly", - "BanditMaxSteals": "Maximum Steals", - "BanditCanStealBetrayalAddon": "Can Steal Betrayal Addons", - "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", - "NoStealableAddons": "Could not steal addon from the player", - "StealCooldown": "Steal cooldown", - - "DoppelMaxSteals": "Maximum Steals", - - "NecromancerRevengeTime": "Necromancy time", - "NecromancerRevenge": "You have {0}s to kill {1}", - "NecromancerSuccess": "Necromancy complete! You live to see another day.", - "NecromancerHide": "Venting is disabled, hide from the Necromancer!", - "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", - "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", - "RetributionistKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", - "RetributionistKillDead": "Choose a living player to kill.", - "RetributionistKillSucceed": "{0} was killed by the Retributionist!", - "RetributionistKillDisable": "You can't retribute until your tasks are done.", - "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", - "RetributionistCanKillNum": "Max retributions", - "RetributionistKillTooManyDead": "Too many players are dead, you can't retribute.", - "MinimumPlayersAliveToRetri": "Maximum players needed to block retributions", - "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", - "BakerChangeChances": "Chance that Baker turns into Famine", - "BakerChange": "The Baker has turned into Famine!\nThe bread is now poisoned.\nIf the Famine is not exiled, all players with poisoned bread die.", - "BakerChangeNow": "A player has poisoned bread!\nExile the Famine or that player dies.", - "BakerChangeNONE": "The Famine has not given out poisoned bread.\nNobody will die of poison, but you should still exile the Famine.", - "PanAliveMessageTitle": "BAKER ", - "PanAlive": "The Baker has given out bread.", - "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", - - "TwisterCooldown": "Twist Cooldown", - "TwisterButtonText": "Twist", - "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", - "InstigatorAbilityLimit": "Ability Use Count", - "InstigatorKillsPerAbilityUse": "Kills per Ability use", - - "CrewCanFindCaptain": "Crewmates can find Captain", - "MadmateCanFindCaptain": "Madmates can find Captain", - "ReducedSpeed": "Reduced speed", - "ReducedSpeedTime": "Time duration for reduced speed", - "CaptainCanTargetNB": "Captain can target Neutral Benign", - "CaptainCanTargetNE": "Captain can target Neutral Evil", - "CaptainCanTargetNC": "Captain can target Neutral Chaos", - "CaptainCanTargetNK": "Captain can target Neutral Killer", - "CaptainSpeedReduced": "Captain reduced your speed", - "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", - "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", - - "InspectorTryHideMsg": "Hide Inspector's commands", - "MaxInspectCheckLimit": "Max inspections per game", - "InspectCheckLimitPerMeeting": "Max inspections per meeting", - "InspectCheckTargetKnow": "Targets know they were checked by Inspector", - "InspectCheckOtherTargetKnow": "Targets know who they were checked with", - "InspectorDead": "You can not use your power after death", - "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", - "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", - "InspectCheckSelf": "HA!! you thought it would be this easy. You can not check yourself", - "InspectCheckReveal": "HA! you thought it would be this easy. You can not check a role that is revealed", - "InspectCheckTitle": "INSPECTOR ", - "InspectCheckTrue": "{0} and {1} are in the same team!", - "InspectCheckFalse": "{0} and {1} are NOT in the same team!", - "InspectCheckTargetMsg": " were checked by Inspector.", - "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "InspectCheckNull": "Please select an ID of a living player to check their team", - "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", - "InspectCheckRevealTarget": "When tasks done, target knows team of other target", - "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", - - "EgoistCountMode.Original": "Original", - "EgoistCountMode.Neutral": "Neutral", - - "JailerJailCooldown": "Jail cooldown", - "JailerMaxExecution": "Maximum executions", - "JailerNBCanBeExe": "Can execute Neutral Benign", - "JailerNCCanBeExe": "Can execute Neutral Chaos", - "JailerNECanBeExe": "Can execute Neutral Evil", - "JailerNKCanBeExe": "Can execute Neutral Killing", - "JailerCKCanBeExe": "Can execute Crew Killing", - "JailerTargetAlreadySelected": "You have already selected a target", - "SuccessfullyJailed": "Target successfully jailed", - "CantGuessJailed": "You can not guess the target", - "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", - "CanNotTrialJailed": "You can not trial the target.", - "notifyJailedOnMeeting": "Notify jailed player when meeting starts", - "JailedNotifyMsg": "You have been locked up in jail by the Jailer. No one can guess or judge you and you can only guess Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", - "JailerTitle": "Jailer", - - "CopyCatCopyCooldown": "Copy cooldown", - "CopyCatRoleChange": "Your role has been changed to {0}", - "CopyCatCanNotCopy": "You can not copy target's role", - "CopyButtonText": "Copy", - "CopyCrewVar": "Can copy evil variants of crew roles", - "CopyTeamChangingAddon": "Can copy team changing addon", - - "MaxCleanserUses": "Max cleanses", - "CleansedCanGetAddon": "Cleansed player can get Add-on", - "CleanserTitle": "CLEANSER", - "CleanserRemoveSelf": "You can not cleanse yourself", - "CleanserCantRemove": "Oops! the player can not be cleansed.", - "CleanserRemovedRole": "{0} has been cleansed. All their Addons will be removed after the meeting.", - "LostAddonByCleanser": "All your Addons were removed by the cleanser", - - "MaxProtections": "Max protections", - "KeeperHideVote": "Hide Keeper's vote", - "KeeperProtect": "You chose to protect {0}, your vote has been returned", - "KeeperTitle": "Keeper", - - "MaulRadius": "Maul Radius", - "ImpCanBeAutopsy": "Impostors can become Autopsy", - "CrewCanBeAutopsy": "Crewmates can become Autopsy", - "NeutralCanBeAutopsy": "Neutrals can become Autopsy", - "ImpCanBeCyber": "Impostors can become Cyber", - "CrewCanBeCyber": "Crewmates can become Cyber", - "NeutralCanBeCyber": "Neutrals can become Cyber", - "ImpKnowCyberDead": "Impostors know if Cyber died", - "CrewKnowCyberDead": "Crewmates know if Cyber died", - "NeutralKnowCyberDead": "Neutrals know if Cyber died", - "CyberKnown": "Everyone can see Cyber", - "ImpCanBeInfluenced": "Impostors can become Influenced", - "CrewCanBeInfluenced": "Crewmates can become Influenced", - "NeutralCanBeInfluenced": "Neutrals can become Influenced", - "ImpCanBeBewilder": "Impostors can become Bewilder", - "CrewCanBeBewilder": "Crewmates can become Bewilder", - "NeutralCanBeBewilder": "Neutrals can become Bewilder", - "KillerGetBewilderVision": "Killer gets Bewilder's vision", - "ImpCanBeOiiai": "Impostors can be OIIAI", - "CrewCanBeOiiai": "Crewmates can be OIIAI", - "NeutralCanBeOiiai": "Neutrals can be OIIAI", - "OiiaiCanPassOn": "OIIAI can pass on to the killer", - "NeutralChangeRolesForOiiai": "Neutrals turns to ", - "LostRoleByOiiai": "You got erased by OIIAI!", - "ImpCanBeSunglasses": "Impostors can become Sunglasses", - "CrewCanBeSunglasses": "Crewmates can become Sunglasses", - "NeutralCanBeSunglasses": "Neutrals can become Sunglasses", - "SunglassesVision": "Sunglasses Vision", - "ImpCanBeLoyal": "Impostors can become Loyal", - "CrewCanBeLoyal": "Crewmates can become Loyal", - "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", - "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", - "SheriffCanBeMadmate": "Sheriff can become Madmate", - "MayorCanBeMadmate": "Mayor can become Madmate", - "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", - "SnitchCanBeMadmate": "Snitch can become Madmate", - "JudgeCanBeMadmate": "Judge can become Madmate", - "MarshallCanBeMadmate": "Marshall can become Madmate", - "GanRetributionistCanBeMadmate": "Retributionist can be converted", - "RetributionistCanBeMadmate": "Retributionist can become Madmate", - "OverseerCanBeMadmate": "Overseer can become Madmate", - "GanSheriffCanBeMadmate": "Sheriff can be converted", - "GanMayorCanBeMadmate": "Mayor can be converted", - "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", - "GanJudgeCanBeMadmate": "Judge can be converted", - "GanMarshallCanBeMadmate": "Marshall can be converted", - "GanOverseerCanBeMadmate": "Overseer can be converted", - "RascalAppearAsMadmate": "Appear As Madmate On Ejection", - "CouncillorDead": "Sorry, you can't murder from the dead.", - "CouncillorMurderMax": "Sorry, you've reached the maximum amount of murders for the meeting.", - "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", - "Councillor_MurderKill": "{0} was judged.", - "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Councillor_MurderNull": "Please choose a living player to murder.", - "Councillor_MurderKillTitle": "COURT ", - "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", - "CouncillorCanMurderMadmate": "Can Murder Madmates", - "CouncillorCanMurderImpostor": "Can Murder Impostors", - "CouncillorTryHideMsg": "Try to hide Councillor's commands", - "DazzlerDazzled": "You were dazzled by the Dazzler!", - "DazzlerCauseVision": "Reduced vision", - "DazzlerDazzleLimit": "Max number of players affected by reduced vision", - "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", - "DazzleCooldown": "Dazzle Cooldown", - "DazzleButtonText": "Dazzle", - - "MoleVentButtonText": "Dig", - "MoleVentCooldown": "Dig cooldown", - - "AddictVentButtonText": "Get Fix", - "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", - "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerble", - - "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", - "AlchemistShieldDur": "Resistance Potion Duration", - "AlchemistInvisDur": "Invisibility Potion Duration", - "AlchemistVision": "Night Vision", - "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", - "AlchemistVisionDur": "Night Vision Potion Duration", - "AlchemistSpeed": "Speed Potion Boost", - "AlchemistVentButtonText": "Drink", - "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", - "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", - "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", - "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", - "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", - "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", - "AlchemistGotBloodlustPotion": "Potion of Harming: Kill the next player you touch", - "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", - "NoPotion": "You have no potions", - - "StoreShield": "Potion of Resistance", - "StoreSuicide": "Potion of Poison", - "StoreTP": "Potion of Warping", - "StoreSP": "Potion Of Speed", - "StoreQF": "Potion of Fixing", - "StoreBL": "Potion of Harming", - "StoreNS": "Potion of Night Vision", - "StoreNull": "None", - "PotionStore": "Potion in store: ", - "WaitQFPotion": "\nPotion of Fixing waiting for use", - - "AlchemistShielded": "Potion of Resistance started", - "AlchemistHasVision": "Potion of Night Vision started", - "AlchemistShieldOut": "Potion of Resistance ended", - "AlchemistVisionOut": "Potion of Night Vision ended", - "AlchemistPotionBloodlust": "You gained bloodlust", - "AlchemistHasSpeed": "Potion Of Speed started", - "AlchemistSpeedOut": "Potion Of Speed ended", - - "DeathpactDuration": "Death Pact duration", - "DeathPactCooldown": "Death Pact Assign Cooldown", - "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", - "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", - "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", - "DeathpactVisionWhileInPact": "Vision for players in Death Pact", - "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", - "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", - "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", - "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", - "DeathpactComplete": "Death Pact was concluded.", - "DeathpactExecuted": "Death Pact was executed.", - "DeathpactAverted": "Death Pact was averted.", - "DeathpactButtonText": "Assign", - "DevourerHideNameConsumed": "Hide the names of consumed players", - "DevourCooldown": "Devour Cooldown", - "DevourerButtonText": "Devour", - "EatenByDevourer": "Your skin was eaten by the Devourer", - "DevourerEatenSkin": "Target skin eaten", - "DevouredName": "Devoured", - "PitfallTrapCooldown": "Trap Cooldown", - "PitfallMaxTrapCount": "Number of Traps that can be set", - "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", - "PitfallTrapDuration": "Time the Trap remains active", - "PitfallTrapRadius": "Trap Radius", - "PitfallTrapFreezeTime": "Trap freeze time", - "PitfallTrapCauseVision": "Trap caused vision", - "PitfallTrapCauseVisionTime": "Trap caused vision time", - "PitfallTrap": "You have fallen into a trap!", - "ConsigliereDivinationMaxCount": "Maximum Reveals", - "RitualMaxCount": "Maximum Reveals", - "CleanserHideVote": "Hide Cleanser's vote", - "OracleSkillLimit": "Maximum Uses", - "OracleHideVote": "Hide Oracle's vote", - "OracleCheckReachLimit": "You're out of uses!", - "OracleCheckSelfMsg": "You can't even trust yourself, huh?", - "OracleCheckLimit": "Reminder: You have {0} uses left", - "OracleCheckMsgTitle": "ORACLE ", - "OracleCheck.NotCrewmate": "Appears to not be a crewmate", - "OracleCheck.Crewmate": "Appears to be a crewmate", - "OracleCheck.Neutral": "Appears to be a neutral", - "OracleCheck.Impostor": "Appears to be an Impostor", - "OracleCheck": "Target Results:", - "FailChance": "Chance of showing incorrect result", - "OracleCheckAddons": "Oracle checks add-ons", - "ChameleonCanVent": "Vent to disguise", - "ChameleonInvisState": "You are disguising!", - "ChameleonInvisStateOut": "Your disguise ended", - "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", - "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", - "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", - "ChameleonCooldown": "Disguise Cooldown", - "ChameleonDuration": "Disguise Duration", - "ChameleonRevertDisguise": "Expose", - "ChameleonDisguise": "Disguise", - "KillCooldownAfterCleaning": "Kill Cooldown On Clean", - "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", - "MedusaStoneBody": "Body stoned", - "MedusaReportButtonText": "Stone", - - "CursedSoulCurseCooldown": "Soul Snatch Cooldown", - "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", - "CursedSoulCurseMax": "Maximum Soul Snatches", - "CursedSoulKnowTargetRole": "Know the roles of Soulless players", - "CursedSoulCanCurseNeutral": "Neutral roles have souls", - "CursedSoulKillButtonText": "Snatch", - "SoullessByCursedSoul": "Your soul was snatched by a Cursed Soul", - "CursedSoulSoullessPlayer": "Soul snatched", - "CursedSoulInvalidTarget": "No soul found", - - "AdmireCooldown": "Admire Cooldown", - "AdmirerKnowTargetRole": "Know the roles of Admired players", - "AdmirerSkillLimit": "Skill Limit", - "AdmireButtonText": "Admire", - "AdmirerAdmired": "The Admirer admired you!", - "AdmiredPlayer": "Player admired", - "AdmirerInvalidTarget": "Target cannot be admired", - - "SpiritualistNoticeTitle": "SPIRITUALIST ", - "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", - "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", - "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", - "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", - "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", - "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", - "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", - "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", - "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", - "EnigmaClueHat1": "The Killer wears a Hat!", - "EnigmaClueHat2": "The Killer does not wear a Hat!", - "EnigmaClueHat3": "The Killer wears {0} as a Hat!", - "EnigmaClueSkin1": "The Killer wears a Skin!", - "EnigmaClueSkin2": "The Killer does not wear a Skin!", - "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", - "EnigmaClueVisor1": "The Killer wears a Visor!", - "EnigmaClueVisor2": "The Killer does not wear a Visor!", - "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", - "EnigmaCluePet1": "The Killer does have a Pet!", - "EnigmaCluePet2": "The Killer does not have a Pet!", - "EnigmaCluePet3": "The Killer has {0} as a Pet!", - "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", - "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", - "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", - "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", - "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", - "EnigmaClueColor1": "The Killer has a light color!", - "EnigmaClueColor2": "The Killer has a dark color!", - "EnigmaClueColor3": "The Killer's color is {0}!", - "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", - "EnigmaClueStatus1": "The Killer is currently inside a Vent!", - "EnigmaClueStatus2": "The Killer is currently on a Ladder!", - "EnigmaClueStatus3": "The Killer is already Dead!", - "EnigmaClueStatus4": "The Killer is still Alive!", - "EnigmaClueRole1": "The Killer is an Impostor!", - "EnigmaClueRole2": "The Killer is a Neutral!", - "EnigmaClueRole3": "The Killer is a Crewmate!", - "EnigmaClueRole4": "The Killer's Role is {0}!", - "EnigmaClueLevel1": "The Killer's Level is above 50!", - "EnigmaClueLevel2": "The Killer's Level is below 50!", - "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", - "EnigmaClueLevel4": "The Killer's Level is {0}!", - "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", - "EnigmaClueHatTitle": "Enigma Hat Clue!", - "EnigmaClueVisorTitle": "Enigma Visor Clue!", - "EnigmaClueSkinTitle": "Enigma Skin Clue!", - "EnigmaCluePetTitle": "Enigma Pet Clue!", - "EnigmaClueNameTitle": "Enigma Name Clue!", - "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", - "EnigmaClueColorTitle": "Enigma Color Clue!", - "EnigmaClueLocationTitle": "Enigma Location Clue!", - "EnigmaClueStatusTitle": "Enigma Status Clue!", - "EnigmaClueRoleTitle": "Enigma Role Clue!", - "EnigmaClueLevelTitle": "Enigma Level Clue!", - "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", - - "ChiefOfPoliceSkillCooldown": "Cooldown for recruiting sheriffs", - "PolicCanImpostorAndNeutarl": "You can recruit Impostor or Kill Neutral to become sheriffs", - "SheriffSuccessfullyRecruited": "You recruited a sheriff.", - "BeSheriffByPolice": "You've been recruited by the police chief! Serve the crew!", - "ChiefOfPoliceKillButtonText": "Recruitment", - "VotesPerKill": "Votes gained for each kill", - "PickpocketGetVote": "You've got {0} votes", - "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "VultureNumberOfReportsToWin": "Bodies needed to win", - "VultureReportBody": "Body eaten!", - "VultureEatButtonText": "Consume", - "VultureReportCooldown": "Eat Cooldown", - "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", - "VultureCooldownUp": "Eat Cooldown finished", - - "TasksMarkPerRound": "Number of tasks that can be marked in one round", - "TaskinatorBombPlanted": "Bomb has been planted", - - "ShieldDuration": "Shield duration", - "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", - "BenefactorTaskMarked": "Task marked successfully", - "BenefactorTargetGotShield": "You got shield by Benefactor", - - "PirateTryHideMsg": "Hide Pirate's commands", - "SuccessfulDuelsToWin": "Number of successful duels needed to win", - "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the duel if you choose the same option as the target", - "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", - "PirateTitle": "PIRATE ", - "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", - "PirateDead": "Ye be dead. Ye cannot duel anymore.", - "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", - "DuelDone": "Ye 'ave chosen yer option fer the duel.\nWait fer the meetin' to end to see the result.", - "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", - "PirateDuelButtonText": "Duel", - "DuelCooldown": "Duel Cooldown", - "Rock": "Rock", - "Paper": "Paper", - "Scissors": "Scissors", - "Heads": "Heads", - "Tails": "Tails", - "SpyRedNameDur": "Colored Name Duration", - "SpyInteractionBlocked": "Block kill button interaction", - "AgitaterBombCooldown": "Agitator bomb cooldown", - "AgitaterPassCooldown": "Bomb pass cooldown", - "BombExplodeCooldown": "Bomb explode cooldown", - "AgitaterPassNotify": "Bomb successfully passed", - "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", - "AgitaterCanGetBombed": "Agitator can get bomb", - "AgitaterAutoReportBait": "Agitator Auto Report Bait", - - "SeekerPointsToWin": "Number of points required to win", - "SeekerTagCooldown": "Tag Cooldown", - "SeekerNotify": "Your target is {0}", - "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", - - "PixiePointsToWin": "Number of points required to win", - "MaxTargets": "Maximum number of targets per round", - "MarkCooldown": "Mark cooldown", - "PixieSuicide": "Pixie suicides if target is not voted out", - "PixieMaxTargetReached": "You have already selected all the targets this round", - "PixieTargetAlreadySelected": "Target is already selected", - "PixieButtonText": "Mark", - - "PlagueBearerCD": "Plague cooldown", - "PestilenceCD": "Pestilence Kill cooldown", - "PlagueBearerAlreadyPlagued": "Player has already been plagued", - "PlagueBearerToPestilence": "You have turned into Pestilence!!", - "PestilenceCanVent": "Pestilence Can Vent", - "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", - "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", - "RomanticBetCooldown": "Pick Partner Cooldown", - "RomanticProtectCooldown": "Protect Cooldown", - "RomanticBetPlayer": "You picked your partner", - "RomanticBetOnYou": "The Romantic chose you as their Partner!", - "VengefulKCD": "Vengeful Romantic Kill Cooldown", - "VengefulCanVent": "Vengeful Romantic Can Vent", - "RuthlessKCD": "Ruthless Romantic Kill Cooldown", - "RuthlessCanVent": "Ruthless Romantic Can Vent", - "RomanticProtectPartner": "Your partner is under protection", - "RomanticIsProtectingYou": "The Romantic is protecting you", - "ProtectingOver": "Shield expired", - "RomanticProtectDuration": "Protect Duration", - "RomanticKnowTargetRole": "Romantic knows their target's role", - "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", - - "GuessMasterMisguess": "{0} misguessed", - "GuessMasterTargetRole": "Someone tried to guess {0}", - "GuessMasterTitle": "Guess Master ", - - "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", - "DCanGuessImpostors": "Can Guess Impostors", - "DCanGuessCrewmates": "Can Guess Crewmates", - "DCanGuessNeutrals": "Can Guess Neutrals", - "DCanGuessAdt": "Can Guess Add-Ons", - "DoomsayerAdvancedSettings": "Advanced Settings", - "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", - "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", - "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", - "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", - "DoomsayerTryHideMsg": "Hide Doomsayer's commands", - "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", - "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", - "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", - "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", - "DoomsayerGuessCountTitle": "DOOMSAYER", - "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", - - "EveryoneCanKnowMini": "Everyone can see the Mini", - "CanBeEvil": "Mini can be an Impostor", - "EvilMiniSpawnChances": "Probability of Mini being an Impostor", - "GuessMini": "Sorry, you can't hurt a kid Mini.", - "GrowUpDuration": "Time required to grow (s)", - "MajorCooldown": "Kill Cooldown when over 18", - "UpDateAge": "Display age change in real-time", - "Cantkillkid": "You can't kill a Mini that hasn't grown up.", - "CantEat": "You can't eat a Mini that hasn't grown up", - "CantShroud": "You can't control a Mini that hasn't grown up.", - "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", - "CantRecruit": "You can't recruit a Mini that hasn't grown up.", - "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", - "MiniUp": "You're a year older!", - "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilty while you can no longer guess.\nYou can guess again after you have grown up.", - "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", - "CountMeetingTime": "Meeting time can continue to grow", - "YouKillRandomizer1": "You kill Randomizer, Self report!", - "YouKillRandomizer2": "You kill Randomizer, Cannot move!", - "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", - "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", - "MadmateCanBeHurried": "Madmate can be Hurried on game start", - "TaskBasedCrewCanBeHurried": "Task based Crews can be Hurried", - "HurriedCanBeConverted": "Hurried can be recruited in game (excludes madmate)", - "Developer": "Developer", - "Sponsor": "Sponsor", - "Booster": "Server Booster", - "Translator": "Translator", - "NoAccess": "Unauthorized Access!!!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", - "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", - "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", - "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", - "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", - "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", - "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", - "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", - "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", - "DCNotify.Inactivity": "The lobby closed due to inactivity.", - "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", - "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", - "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", - "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", - "RoleType.VanillaRoles": "★ Vanilla Roles", - "RoleType.ImpKilling": "★ Impostor Killing Roles", - "RoleType.ImpSupport": "★ Impostor Support Roles", - "RoleType.ImpConcealing": "★ Impostor Concealing Roles", - "RoleType.ImpHindering": "★ Impostor Hindering Roles", - "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", - "RoleType.Madmate": "★ Madmate Roles", - "RoleType.CrewSupport": "★ Crewmate Support Roles", - "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", - "RoleType.CrewPower": "★ Crewmate Power Roles", - "RoleType.CrewKilling": "★ Crewmate Killing Roles", - "RoleType.CrewBasic": "★ Crewmate Basic Roles", - "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", - "RoleType.NeutralEvil": "★ Neutral Evil Roles", - "RoleType.NeutralBenign": "★ Neutral Benign Roles", - "RoleType.NeutralChaos": "★ Neutral Chaos Roles", - "RoleType.NeutralKilling": "★ Neutral Killing Roles", - "RoleType.Harmful": "★ Harmful Add-ons", - "RoleType.Support": "★ Supportive Add-ons", - "RoleType.Helpful": "★ Helpful Add-ons", - "RoleType.Mixed": "★ Mixed Add-ons", - "RoleType.Misc": "★ Miscellaneous Add-ons", - "RoleType.Impostor": "★ Impostor Add-ons", - "RoleType.Neut": "★ Neutral Add-ons", - "SubType.Impostor": "★ Impostors", - "SubType.Shapeshifter": "★ Shapeshifters", - "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", - "SubType.Madmate": "★ Madmates", - "SubType.CrewmateKilling": "★ Crewmate Killings", - "SubType.Crewmate": "★ Regular Crewmates", - "SubType.New": "★ New!", - "CrewmateRoles": "★ Crewmate Roles ★", - "ImpostorRoles": "★ Impostor Roles ★", - "NeutralRoles": "★ Neutral Roles ★", - "AddonRoles": "★ Add-ons ★", - "WinnerRoleText.Impostor": "Impostors Win!", - "WinnerRoleText.Crewmate": "Crewmates Win!", - "WinnerRoleText.Terrorist": "Terrorist Wins!", - "WinnerRoleText.Jester": "Jester Wins!", - "WinnerRoleText.Lovers": "Lovers Win!", - "WinnerRoleText.Executioner": "Executioner Wins!", - "WinnerRoleText.Arsonist": "Arsonist Wins!", - "WinnerRoleText.Revolutionist": "Revolutionist Wins!", - "WinnerRoleText.Jackal": "Jackals Win!", - "WinnerRoleText.God": "God Wins!", - "WinnerRoleText.Vector": "Vector Wins!", - "WinnerRoleText.Innocent": "Innocent Wins!", - "WinnerRoleText.Pelican": "Pelican Wins!", - "WinnerRoleText.Youtuber": "YouTuber Wins!", - "WinnerRoleText.Necromancer": "Necromancer Wins!", - "WinnerRoleText.Egoist": "Egoists Win!", - "WinnerRoleText.Demon": "Demon Wins!", - "WinnerRoleText.Stalker": "Stalker Wins!", - "WinnerRoleText.Workaholic": "Workaholic Wins!", - "WinnerRoleText.Collector": "Collector Wins!", - "WinnerRoleText.BloodKnight": "Blood Knight Wins!", - "WinnerRoleText.Poisoner": "Poisoner Wins!", - "WinnerRoleText.Huntsman": "Huntsman Wins!", - "WinnerRoleText.HexMaster": "Hex Master Wins!", - "WinnerRoleText.Cultist": "Cultist Wins!", - "WinnerRoleText.Wraith": "Wraith Wins!", - "WinnerRoleText.SerialKiller": "Serial Killers Win!", - "WinnerRoleText.Juggernaut": "Juggernaut Wins!", - "WinnerRoleText.Infectious": "Infectious Wins!", - "WinnerRoleText.Virus": "Virus Wins!", - "WinnerRoleText.Phantom": "Phantom Wins!", - "WinnerRoleText.Jinx": "Jinx Wins!", - "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", - "WinnerRoleText.PotionMaster": "Potion Master Wins!", - "WinnerRoleText.Pickpocket": "Pickpocket Wins!", - "WinnerRoleText.Traitor": "Traitor Wins!", - "WinnerRoleText.Vulture": "Vulture Wins!", - "WinnerRoleText.Medusa": "Medusa Wins!", - "WinnerRoleText.Famine": "Famine Wins!", - "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", - "WinnerRoleText.Glitch": "Glitch Wins!", - "WinnerRoleText.Pestilence": "Pestilence Wins!", - "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", - "WinnerRoleText.Masochist": "Masochist Wins!", - "WinnerRoleText.Doomsayer": "Doomsayer Wins!", - "WinnerRoleText.Pirate": "Pirate Wins!", - "WinnerRoleText.Shroud": "Shroud Wins!", - "WinnerRoleText.Werewolf": "Werewolf Wins!", - "WinnerRoleText.Seeker": "Seeker Wins!", - "WinnerRoleText.Agitater": "Agitator Wins!", - "WinnerRoleText.Occultist": "Occultist Wins!", - "WinnerRoleText.SoulCollector": "Soul Collector Wins!", - "WinnerRoleText.NiceMini": "Nice Mini Wins!", - "WinnerRoleText.Mini": "Nice Mini was killed", - "WinnerRoleText.Bandit": "Bandit Wins!", - "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", - "WinnerRoleText.Solsticer": "Solsticer Wins!", - "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", - "WinnerRoleText.Doppelganger": "Doppelganger Wins!", - "AdditionalWinnerRoleText.Sidekick": "Sidekick", - "AdditionalWinnerRoleText.Taskinator": "Taskinator", - "AdditionalWinnerRoleText.Opportunist": "Opportunist", - "AdditionalWinnerRoleText.Lawyer": "Lawyer", - "AdditionalWinnerRoleText.Hater": "Hater", - "AdditionalWinnerRoleText.Provocateur": "Provocateur", - "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", - "AdditionalWinnerRoleText.Follower": "Follower", - "AdditionalWinnerRoleText.Pursuer": "Pursuer", - "AdditionalWinnerRoleText.Jester": "Jester", - "AdditionalWinnerRoleText.Lovers": "Lovers", - "AdditionalWinnerRoleText.Executioner": "Executioner", - "AdditionalWinnerRoleText.Phantom": "Phantom", - "AdditionalWinnerRoleText.Maverick": "Maverick", - "AdditionalWinnerRoleText.Shaman": "Shaman", - "AdditionalWinnerRoleText.Pixie": "Pixie", - "AdditionalWinnerRoleText.NiceMini": "Nice Mini", - "AdditionalWinnerRoleText.Romantic": "Romantic", - "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", - "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", - "ErrorEndText": "An error occurred", - "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", - "ForceEnd": "Aborted", - "EveryoneDied": "Everyone died", - "ForceEndText": "Host has aborted the game", - "NiceMiniDied": "Nice Mini was killed", - "HaterMisFireKillTarget": "Hater kills target when misfire", - "HaterChooseConverted": "Select addons that Hater can kill", - "HaterCanKillMadmate": "Can kill madmate", - "HaterCanKillCharmed": "Can kill charmed", - "HaterCanKillLovers": "Can kill lovers", - "HaterCanKillSidekick": "Can kill jackal team", - "HaterCanKillEgoist": "Can kill egoist", - "HaterCanKillInfected": "Can kill infected team", - "HaterCanKillContagious": "Can kill virus team", - "HaterCanKillAdmired": "Can kill admirer", - "AutoMuteUs": "Enable it if you use AutoMuteUs", - "HorseMode": "Enable to become a horse", - "LongMode": "Enable to have a long neck", - "InfluencedChangeVote": "oops!You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", - - - "FFA": "Free For All", - "ModeFFA": "Gamemode: FFA", - "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the mean time!", - "FFA_GameTime": "Maximum Game Length", - "FFA_KCD": "Kill Cooldown", - "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", - "FFA_EnableRandomAbilities": "Enable Random Events", - "FFA_ShieldDuration": "Shield Duration", - "FFA_IncreasedSpeed": "Increased Speed", - "FFA_DecreasedSpeed": "Decreased Speed", - "FFA_ModifiedSpeedDuration": "Modified Speed Duration", - "FFA_LowerVision": "Lowered Vision", - "FFA_ModifiedVisionDuration": "Lowered Vision Duration", - "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", - "FFA-Event-GetShield": "You have a temporary shield!", - "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", - "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", - "FFA-Event-GetHighKCD": "You got a higher kill cooldown", - "FFA-Event-GetLowVision": "You have lower vision temporarily", - "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", - "FFA-Event-GetTP": "You got teleported to a random vent!", - "FFA-Event-RandomTP": "Everyone was swapped with someone", - "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", - "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", - "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", - "FFA_TargetIsShielded": "The player you tried to kill is shielded!", - "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", - "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", - "Killer": "FREE FOR ALL", - "KillerInfo": "Kill Everyone to Win", - - "Hide&SeekTOHE": "Hide & Seek", - "MenuTitle.Hide&Seek": "Hide & Seek Settings", - "NumImpostorsHnS": "Num Impostors", - - "EveryOneKnowSolsticer": "Every One Know who is Solsticer", - "SolsticerKnowItsKiller": "Solsticer knows the role of whom used kill button on it", - "SolsticerSpeed": "Movement speed of Solsticer", - "SolsticerRemainingTaskWarned": "Remaining tasks to be known", - "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", - "SolsticerMurdered": "{0} attempted to murder you!", - "MurderSolsticer": "You stopped Solsticer this round!", - "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", - "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", - "SolsticerTitle": "Solsticer", - "GuessSolsticer": "Sorry, but you can not guess Solsticer!", - "VoteSolsticer": "Sorry, but you can not vote Solsticer!", - "SolsticerTasksReset": "Your tasks get reset!", - "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", - "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", - - "VoteDead": "The player you voted for was exhiled before the meeting concluded. Your vote was rescinded.", - - "ImpCanBeSilent": "Impostors can become Silent", - "CrewCanBeSilent": "Crewmates can become Silent", - "NeutralCanBeSilent": "Neutrals can become Silent", - "LastMessageReplay": "Last System Message Replay", - "Contributor": "Contributor", - - "dbConnect.InitFailure": "Error while connecting to TOHE api, pls check your network connection and retry login!", - "dbConnect.nullFriendCode": "This build of TOHE is not aviliable to users with no friendcode!", - - "ImpCanBeSusceptible": "Impostors can become Susceptible", - "CrewCanBeSusceptible": "Crewmates can become Susceptible", - "NeutralCanBeSusceptible": "Neutrals can become Susceptible", - - "Quizmaster": "Quizmaster", - "QuizmasterInfo": "Quiz people to kill them in meetings", - "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will \"?!\" next to their name. If the player answers a question wrongly, or doesn't answer, they will die. If the Quizmaster was killed/ejected in the same meeting, the player will live.\nThe Quizmaster cannot mark multiple people in the same round", - "QuizmasterKillButtonText": "Quiz", - - "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", - "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", - "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", - "QuizmasterChat.CorrectTarget": "Correct", - "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", - "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", - "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", - "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", - "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", - "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die", - "QuizmasterChat.Title": "Quizmaster Information", - "QuizmasterChat.CantAnswer": "As the quizmaster you can't answer questions", - "QuizmasterChat.AnswerNotValid": "Your answer must be A, B or C", - "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", - - "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", - "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", - "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", - "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", - "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", - - "Quizmaster.None": "None", - - "QuizmasterSabotages.Lights": "Lights", - "QuizmasterSabotages.Reactor": "Reactor", - "QuizmasterSabotages.Communications": "Communications", - "QuizmasterSabotages.O2": "O2", - "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", - "QuizmasterAnswers.One": "One", - "QuizmasterAnswers.Two": "Two", - "QuizmasterAnswers.Three": "Three", - "QuizmasterAnswers.Four": "Four", - "QuizmasterAnswers.Five": "Five", - "QuizmasterAnswers.Pacifist": "Pacifist", - "QuizmasterAnswers.Vampire": "Vampire", - "QuizmasterAnswers.Snitch": "Snitch", - "QuizmasterAnswers.Vigilante": "Vigilante", - "QuizmasterAnswers.Jackal": "Jackal", - "QuizmasterAnswers.Mole": "Mole", - "QuizmasterAnswers.Sniper": "Sniper", - "QuizmasterAnswers.Coven": "Coven", - "QuizmasterAnswers.Sabotuer": "Sabotuer", - "QuizmasterAnswers.Sorcerers": "Sorcerers", - "QuizmasterAnswers.Killer": "Killer", - "QuizmasterAnswers.Edition": "Edition", - "QuizmasterAnswers.Experimental": "Experimental", - "QuizmasterAnswers.Enhanced": "Enhanced", - "QuizmasterAnswers.Edited": "Edited", - - "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", - "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", - "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", - "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", - "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called last meeting before this meeting?", - "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", - "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", - "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", - "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", - "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed an update later?", - "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", - "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", - "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", - "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", - "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", - "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", - "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", - - "DeathReason.WrongAnswer": "Wrong Quiz Answer", - - "TPCooldown": "Teleport Cooldown", - "RiftsTooClose": "Location too close to the first rift", - "RiftCreated": "Rift made successfully", - "RiftsDestroyed": "All rifts Destroyed", - "RiftRadius": "Rift Radius", - - "TiredVision": "Vision When Tired", - "TiredSpeed": "Speed When Tired", - "TiredDur": "Tired Duration", - "ImpCanBeTired": "Impostors can become Tired", - "CrewCanBeTired": "Crewmates can become Tired", - "NeutralCanBeTired": "Neutrals can become Tired", - - "TiredNotify": "Zzz..", - - "PlagueDoctorInfectLimit": "Infect Limit", - "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", - "PlagueDoctorInfectTime": "Infect Time", - "PlagueDoctorInfectDistance": "Infect Distance", - "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", - "PlagueDoctorCanInfectSelf": "Can Infect Self", - "PlagueDoctorCanInfectVent": "Can Infect While In Vent", - "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", - - "StatueSlow": "Statue Slowness", - "StatuePeopleToSlow": "People Needed To Slow", - - "ImpCanBeStatue": "Impostors can become Statue", - "CrewCanBeStatue": "Crewmates can become Statue", - "NeutralCanBeStatue": "Neutrals can become Statue", - - "WardenIncreaseSpeed": "Increase Speed By", - "WardenWarn": "DANGER! RUN!", - - "MinionAbilityTime": "Ability Duration" + "LanguageID": "0", + "HostText": "Host", + "HostColor": "#902efd", + "IconColor": "#4bf4ff", + "Icon": "♥", + "HideHostText": "Hide 'Host♥' Text", + "NameColor": "#ffc0cb", + "kofi": "Ko-Fi", + "update": "Update", + "GitHub": "GitHub", + "Discord": "Discord", + "Website": "Website", + "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", + + "SubText.Crewmate": "Find and exile the Impostors", + "SubText.Impostor": "Sabotage and kill everyone", + "SubText.Neutral": "Work alone to achieve your victory", + "SubText.Madmate": "Help the Impostors", + + "TypeImpostor": "Impostors", + "TypeCrewmate": "Crewmates", + "TypeNeutral": "Neutrals", + "TypeAddon": "Add-ons", + "GuesserMode": "Guesser Mode", + + "TeamImpostor": "Impostor", + "TeamNeutral": "Neutral", + "TeamCrewmate": "Crewmate", + "TeamMadmate": "Madmate", + + "YouAreCrewmate": "You are a Crewmate", + "YouAreImpostor": "You are an Impostor", + "YouAreNeutral": "You are a Neutral", + "YouAreMadmate": "You are a Madmate", + + + "Role_Crewmate": "Crewmate", + "Role_Jester": "Jester", + "Role_Opportunist": "Opportunist", + "Role_Celebrity": "Celebrity", + "Role_Bodyguard": "Bodyguard", + "Role_Dictator": "Dictator", + "Role_Mayor": "Mayor", + "Role_Doctor": "Doctor", + "Role_Maverick": "Maverick", + "Role_Pursuer": "Pursuer", + "Role_Follower": "Follower", + "Role_Amnesiac": "Amnesiac", + "Role_Imitator": "Imitator", + "Role_Sheriff": "Sheriff", + "Role_Knight": "Knight", + "Role_Deputy": "Deputy", + "Role_NoChange": "Don't change the role", + + "CrewmatesCanGuess": "Crewmates can guess", + "ImpostorsCanGuess": "Impostors can guess", + "NeutralKillersCanGuess": "Neutral Killers can guess", + "PassiveNeutralsCanGuess": "Passive Neutrals can guess", + + "CanGuessAddons": "Can Guess Add-ons", + "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", + "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", + "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", + "GuessImmune": "Sorry, but target is immune to being guessed!", + + + "GM": "Game Master", + "Sunnyboy": "Sunnyboy", + "Bard": "Bard", + "Nuker": "Nuker", + "Crewmate": "Crewmate", + "CrewmateTOHE": "Crewmate", + "Engineer": "Engineer", + "EngineerTOHE": "Engineer", + "Scientist": "Scientist", + "ScientistTOHE": "Scientist", + "GuardianAngel": "Guardian Angel", + "GuardianAngelTOHE": "Guardian Angel", + "Impostor": "Impostor", + "ImpostorTOHE": "Impostor", + "Shapeshifter": "Shapeshifter", + "ShapeshifterTOHE": "Shapeshifter", + + "BountyHunter": "Bounty Hunter", + "Fireworker": "Fireworker", + "Mercenary": "Mercenary", + "ShapeMaster": "Shapemaster", + "Vampire": "Vampire", + "Vampiress": "Vampiress", + "Warlock": "Warlock", + "Ninja": "Ninja", + "Zombie": "Zombie", + "Anonymous": "Anonymous", + "Miner": "Miner", + "KillingMachine": "Killing Machine", + "Escapist": "Escapist", + "Witch": "Witch", + "Nemesis": "Nemesis", + "Bloodmoon": "Bloodmoon", + "Puppeteer": "Puppeteer", + "Mastermind": "Mastermind", + "TimeThief": "Time Thief", + "Sniper": "Sniper", + "Undertaker": "Undertaker", + "RiftMaker": "Rift Maker", + "EvilTracker": "Evil Tracker", + "EvilGuesser": "Evil Guesser", + "AntiAdminer": "Anti Adminer", + "Arrogance": "Arrogance", + "Bomber": "Bomber", + "Scavenger": "Scavenger", + "Trapster": "Trapster", + "Gangster": "Gangster", + "Cleaner": "Cleaner", + "Lightning": "Lightning", + "Greedy": "Greedy", + "CursedWolf": "Cursed Wolf", + "SoulCatcher": "Soul Catcher", + "QuickShooter": "Quick Shooter", + "Camouflager": "Camouflager", + "Eraser": "Eraser", + "Butcher": "Butcher", + "Hangman": "Hangman", + "Swooper": "Swooper", + "Crewpostor": "Crewpostor", + "Wildling": "Wildling", + "Trickster": "Trickster", + "Vindicator": "Vindicator", + "Parasite": "Parasite", + "Disperser": "Disperser", + "Inhibitor": "Inhibitor", + "Saboteur": "Saboteur", + "Councillor": "Councillor", + "Dazzler": "Dazzler", + "Deathpact": "Deathpact", + "Devourer": "Devourer", + "Consigliere": "Consigliere", + "Morphling": "Morphling", + "Twister": "Twister", + "Lurker": "Lurker", + "Visionary": "Visionary", + "Refugee": "Refugee", + "Underdog": "Underdog", + "Ludopath": "Ludopath", + "Godfather": "Godfather", + "Chronomancer": "Chronomancer", + "Pitfall": "Pitfall", + "EvilMini": "Evil Mini", + "Blackmailer": "Blackmailer", + "Instigator": "Instigator", + "LazyGuy": "Lazy Guy", + "SuperStar": "Super Star", + "Celebrity": "Celebrity", + "Cleanser": "Cleanser", + "Keeper": "Keeper", + "Knight": "Knight", + "Mayor": "Mayor", + "Psychic": "Psychic", + "Mechanic": "Mechanic", + "Sheriff": "Sheriff", + "Vigilante": "Vigilante", + "Jailer": "Jailer", + "CopyCat": "Copycat", + "Snitch": "Snitch", + "Marshall": "Marshall", + "SpeedBooster": "Speed Booster", + "Doctor": "Doctor", + "Dictator": "Dictator", + "Detective": "Detective", + "NiceGuesser": "Nice Guesser", + "GuessMaster": "Guess Master", + "Transporter": "Transporter", + "TimeManager": "Time Manager", + "Veteran": "Veteran", + "Bastion": "Bastion", + "Bodyguard": "Bodyguard", + "Deceiver": "Deceiver", + "Grenadier": "Grenadier", + "Medic": "Medic", + "FortuneTeller": "Fortune Teller", + "Judge": "Judge", + "Mortician": "Mortician", + "Medium": "Medium", + "Pacifist": "Pacifist", + "Observer": "Observer", + "Monarch": "Monarch", + "Overseer": "Overseer", + "Coroner": "Coroner", + "Tracker": "Tracker", + "Merchant": "Merchant", + "President": "President", + "Hawk": "Hawk", + "Retributionist": "Retributionist", + "Deputy": "Deputy", + "Investigator": "Investigator", + "Guardian": "Guardian", + "Addict": "Addict", + "Mole": "Mole", + "Alchemist": "Alchemist", + "Tracefinder": "Tracefinder", + "Oracle": "Oracle", + "Spiritualist": "Spiritualist", + "Chameleon": "Chameleon", + "Inspector": "Inspector", + "Captain": "Captain", + "Admirer": "Admirer", + "TimeMaster": "Time Master", + "Crusader": "Crusader", + "Reverie": "Reverie", + "Lookout": "Lookout", + "Telecommunication": "Telecommunication", + "Lighter": "Lighter", + "TaskManager": "Task Manager", + "Witness": "Witness", + "Swapper": "Swapper", + "ChiefOfPolice": "Police Commissioner", + "NiceMini": "Nice Mini", + "Mini": "Mini", + "Spy": "Spy", + "Randomizer": "Randomizer", + "Enigma": "Enigma", + "Jester": "Jester", + "Arsonist": "Arsonist", + "Pyromaniac": "Pyromaniac", + "Kamikaze": "Kamikaze", + "Huntsman": "Huntsman", + "Terrorist": "Terrorist", + "Executioner": "Executioner", + "Lawyer": "Lawyer", + "Opportunist": "Opportunist", + "Vector": "Vector", + "Jackal": "Jackal", + "God": "God", + "Innocent": "Innocent", + "Stealth": "Stealth", + "Penguin": "Penguin", + "Pelican": "Pelican", + "PlagueDoctor": "Plague Scientist", + "Revolutionist": "Revolutionist", + "Hater": "Hater", + "Demon": "Demon", + "Stalker": "Stalker", + "Workaholic": "Workaholic", + "Solsticer": "Solsticer", + "Collector": "Collector", + "Provocateur": "Provocateur", + "BloodKnight": "Blood Knight", + "Apocalypse": "Apocalypse Team", + "PlagueBearer": "Plaguebearer", + "Pestilence": "Pestilence", + "SoulCollector": "Soul Collector", + "Death": "Death", + "Baker": "Baker", + "Famine": "Famine", + "Berserker": "Berserker", + "War": "War", + "Glitch": "Glitch", + "Sidekick": "Sidekick", + "Follower": "Follower", + "Cultist": "Cultist", + "SerialKiller": "Serial Killer", + "Juggernaut": "Juggernaut", + "Infectious": "Infectious", + "Virus": "Virus", + "Pursuer": "Pursuer", + "Phantom": "Phantom", + "Pirate": "Pirate", + "Agitater": "Agitator", + "Maverick": "Maverick", + "CursedSoul": "Cursed Soul", + "Pickpocket": "Pickpocket", + "Traitor": "Traitor", + "Vulture": "Vulture", + "Taskinator": "Taskinator", + "Benefactor": "Benefactor", + "Medusa": "Medusa", + "Spiritcaller": "Spiritcaller", + "Amnesiac": "Amnesiac", + "Imitator": "Imitator", + "Bandit": "Bandit", + "Doppelganger": "Doppelganger", + "Masochist": "Masochist", + "Doomsayer": "Doomsayer", + "Shroud": "Shroud", + "Werewolf": "Werewolf", + "Shaman": "Shaman", + "Seeker": "Seeker", + "Pixie": "Pixie", + "Occultist": "Occultist", + "SchrodingersCat": "Schrodingers Cat", + "Romantic": "Romantic", + "VengefulRomantic": "Vengeful Romantic", + "RuthlessRomantic": "Ruthless Romantic", + "Poisoner": "Poisoner", + "HexMaster": "Hex Master", + "Wraith": "Wraith", + "Jinx": "Jinx", + "PotionMaster": "Potion Master", + "Necromancer": "Necromancer", + "Warden": "Warden", + "Minion": "Minion", + "LastImpostor": "Last Impostor", + "Overclocked": "Overclocked", + "Lovers": "Lovers", + "Madmate": "Madmate", + "Ntr": "Neptune", + "Watcher": "Watcher", + "Flash": "Flash", + "Torch": "Torch", + "Seer": "Seer", + "Tiebreaker": "Tiebreaker", + "Oblivious": "Oblivious", + "Bewilder": "Bewilder", + "Sunglasses": "Sunglasses", + "Workhorse": "Workhorse", + "Fool": "Fool", + "Avanger": "Avenger", + "Youtuber": "YouTuber", + "Egoist": "Egoist", + "TicketsStealer": "Stealer", + "Schizophrenic": "Schizophrenic", + "Mimic": "Mimic", + "Guesser": "Guesser", + "Necroview": "Necroview", + "Reach": "Reach", + "Charmed": "Charmed", + "Cleansed": "Cleansed", + "Bait": "Bait", + "Trapper": "Beartrap", + "Infected": "Infected", + "Onbound": "Onbound", + "Rebound": "Rebound", + "Mundane": "Mundane", + "Knighted": "Knighted", + "Unreportable": "Disregarded", + "Contagious": "Contagious", + "Lucky": "Lucky", + "Unlucky": "Unlucky", + "VoidBallot": "Void Ballot", + "Aware": "Aware", + "Fragile": "Fragile", + "DoubleShot": "Double Shot", + "Rascal": "Rascal", + "Soulless": "Soulless", + "Gravestone": "Gravestone", + "Lazy": "Lazy", + "Autopsy": "Autopsy", + "Loyal": "Loyal", + "EvilSpirit": "Evil Spirit", + "Recruit": "Recruit", + "Admired": "Admired", + "Glow": "Glow", + "Diseased": "Diseased", + "Antidote": "Antidote", + "Stubborn": "Stubborn", + "Swift": "Swift", + "Ghoul": "Ghoul", + "Bloodlust": "Bloodlust", + "Mare": "Mare", + "Burst": "Burst", + "Sleuth": "Sleuth", + "Clumsy": "Clumsy", + "Nimble": "Nimble", + "Circumvent": "Circumvent", + "Cyber": "Cyber", + "Hurried": "Hurried", + "Oiiai": "OIIAI", + "Influenced": "Influenced", + "Silent": "Silent", + "Susceptible": "Susceptible", + "Tricky": "Tricky", + "Rainbow": "Rainbow", + "Tired": "Tired", + "Statue": "Statue", + "BracketAddons": "Add Brackets To Add-ons", + "EngineerTOHEInfo": "Use the vents to catch the Impostors", + "ScientistTOHEInfo": "Access portable vitals from anywhere", + "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", + "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", + "ImpostorTOHEInfo": "Kill and sabotage", + "CrewmateTOHEInfo": "Search for the Impostors", + "BountyHunterInfo": "Eliminate your target", + "FireworkerInfo": "Go out with a BANG", + "MercenaryInfo": "Keep killing, else you suicide", + "ShapeMasterInfo": "Swiftly kill with no shift cooldown", + "VampireInfo": "Your kills are delayed", + "VampiressInfo": "Your kills are delayed and direct", + "WarlockInfo": "Curse crewmates then shift to make them kill", + "NinjaInfo": "Mark a target, then shift to kill", + "ZombieInfo": "You are very slow", + "AnonymousInfo": "Force a player to report a body", + "MinerInfo": "Warp to your last used vent by shifting", + "KillingMachineInfo": "You can ONLY kill, but low cooldown", + "EscapistInfo": "Shift to mark places and warp back to them", + "WitchInfo": "Spell crewmates to kill them in meetings", + "NemesisInfo": "Kill when you're the last Impostor", + "BeforeNemesisInfo": "You can't kill yet", + "AfterNemesisInfo": "Now start killing", + "BloodmoonInfo": "Seek havoc upon the crewmates", + "PuppeteerInfo": "Make players kill for you", + "MastermindInfo": "Make others kill for you", + "TimeThiefInfo": "Lower meeting time by killing", + "SniperInfo": "Snipe players from a distance by shifting", + "UndertakerInfo": "Teleport dead body to a marked location", + "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", + "EvilTrackerInfo": "Track players by shifting", + "AntiAdminerInfo": "Know when players are near devices", + "ArroganceInfo": "With each kill you make, your cooldown decreases", + "BomberInfo": "Shapeshift to explode", + "TrapsterInfo": "Trap your kills", + "ScavengerInfo": "Your kills are unreportable", + "EvilGuesserInfo": "Guess crew roles in meetings to kill", + "GangsterInfo": "Convert players to your side", + "CleanerInfo": "Report bodies to make them unreportable", + "LightningInfo": "Convert players to Quantum Ghosts", + "GreedyInfo": "Your kill cooldown shifts", + "CursedWolfInfo": "You survive a few kill attempts", + "SoulCatcherInfo": "You swap places with your shift target", + "QuickShooterInfo": "Store ammo to offset kill cooldown", + "CamouflagerInfo": "Camouflage everyone for easy kills", + "EraserInfo": "Erase the role of your vote target", + "ButcherInfo": "Enjoy my beautiful work", + "HangmanInfo": "I will decide when your life will end", + "SwooperInfo": "Turn invisible temporarily", + "CrewpostorInfo": "Kill by completing tasks", + "WildlingInfo": "Kill with strength and disguise", + "TricksterInfo": "Kill and trick the crew", + "VindicatorInfo": "Use your extra votes to kill everyone", + "ParasiteInfo": "Help the Impostors kill the crew", + "DisperserInfo": "Teleport everyone to random vents", + "InhibitorInfo": "You cannot kill during sabotages", + "SaboteurInfo": "You can only kill during sabotages", + "CouncillorInfo": "Kill off crewmates during meetings", + "DazzlerInfo": "Reduce the vision of the crew", + "DeathpactInfo": "Assign players to a death pact", + "DevourerInfo": "Consume the skin of the crew", + "ConsigliereInfo": "Discover the roles of other players", + "MorphlingInfo": "You can only kill while shapeshifted", + "TwisterInfo": "Swap all player positions", + "LurkerInfo": "Reduce your kill cooldown by venting", + "ConvictInfo": "Your target died, now help the Impostors", + "VisionaryInfo": "You see the alignments of the living", + "RefugeeInfo": "Help the Impostors kill off the crew", + "UnderdogInfo": "Start killing on a low player count", + "LudopathInfo": "Your kill cooldown is random", + "GodfatherInfo": "Convert players to Refugees by voting", + "ChronomancerInfo": "Kill in bursts", + "PitfallInfo": "Setup traps around the map", + "EvilMiniInfo": "No one can hurt you until you grow up", + "BlackmailerInfo": "Silence other players", + "InstigatorInfo": "Sow discord among the crewmates", + "LazyGuyInfo": "You're too lazy", + "SuperStarInfo": "Everyone knows you", + "CleanserInfo": "Erase All Addons of your vote target", + "KeeperInfo": "Reject the Eject, Keeper Protect!", + "MayorInfo": "Your vote counts multiple times", + "PsychicInfo": "One of the red names are evil", + "MechanicInfo": "Vent around and fix sabotages", + "SheriffInfo": "Shoot the Impostors", + "VigilanteInfo": "Not the hero we deserved but the hero we needed", + "JailerInfo": "Jail suspicious players", + "CopyCatInfo": "Use kill button to copy target's role", + "SnitchInfo": "Finish your tasks to find the Impostors", + "MarshallInfo": "Finish your tasks to prove your innocence", + "SpeedBoosterInfo": "Boost your speed", + "DoctorInfo": "Know how each player died", + "DictatorInfo": "Exile a player based on your own judgement", + "DetectiveInfo": "Gain extra info from your body reports", + "UndercoverInfo": "Impostors see you as their partner", + "KnightInfo": "You can kill 1 player", + "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", + "GuessMasterInfo": "Whispers heard, every guessed word.", + "TransporterInfo": "Do tasks to swap 2 players' locations", + "TimeManagerInfo": "Increase meeting time by doing tasks", + "VeteranInfo": "Alert to kill anyone who interacts with you", + "BastionInfo": "Bomb vents", + "BodyguardInfo": "Prevent nearby kills", + "DeceiverInfo": "Try to fool the players", + "GrenadierInfo": "Reduce Impostors' vision by venting", + "MedicInfo": "Cast a shield onto a player", + "FortuneTellerInfo": "Get clues to people's roles", + "JudgeInfo": "Silence in the courtroom!", + "MorticianInfo": "Locate dead bodies", + "MediumInfo": "Talk with ghosts", + "ObserverInfo": "You can see all shield-animations", + "PacifistInfo": "Vent to reset kill cooldowns", + "MonarchInfo": "Give your crew extra voting power!", + "StealthInfo": "Killing Blinds Everyone in the Room", + "PenguinInfo": "Drag your victims", + "OverseerInfo": "Reveal roles of other players", + "CoronerInfo": "Find corpses and their killers", + "TrackerInfo": "Keep track of other players", + "PresidentInfo": "You are in charge of the meeting", + "MerchantInfo": "Sell add-ons and bribe killers", + "RetributionistInfo": "Help the crew after you die", + "HawkInfo": "Seek murdering the bad guys!", + "DeputyInfo": "Handcuff killers to increase their cooldowns", + "InvestigatorInfo": "Find potential evils", + "GuardianInfo": "Complete your tasks to become immortal", + "AddictInfo": "Vent to become invulnerable, or you'll die", + "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", + "AlchemistInfo": "Brew potions by completing tasks", + "TracefinderInfo": "Sense the location of dead bodies", + "OracleInfo": "Vote a player to see their alignment", + "SpiritualistInfo": "Be guided by the ghostly life", + "ChameleonInfo": "Vent to disguise into your surroundings", + "InspectorInfo": "Validate the alignments of two players", + "CaptainInfo": "Sail with the Captain, lest addons be abandoned.", + "AdmirerInfo": "Choose a player to side with you", + "TimeMasterInfo": "Rewind time!", + "CrusaderInfo": "Kill a player's attacker", + "ReverieInfo": "With each kill, your cooldown decreases", + "LookoutInfo": "See through disguises", + "TelecommunicationInfo": "Track device usage", + "LighterInfo": "Catch killers with your enhanced vision", + "TaskManagerInfo": "See the total tasks completed in real time", + "WitnessInfo": "Find out if someone killed recently", + "SwapperInfo": "Swap the votes of two players", + "ChiefOfPoliceInfo": "Recruit as a Sheriff by killing players with knives", + "NiceMiniInfo": "No one can hurt you until you grow up.", + "ArsonistInfo": "Douse everyone and ignite", + "PyromaniacInfo": "Douse and kill everyone", + "HuntsmanInfo": "Kill your targets for a low cooldown", + "SpyInfo": "You know who interacts with you", + "RandomizerInfo": "You're going to be someone's burden when you die?", + "EnigmaInfo": "Get Clues about Killers", + "JesterInfo": "Get voted out", + "OpportunistInfo": "Stay alive until the end", + "TerroristInfo": "Finish your tasks, THEN die", + "ExecutionerInfo": "Get your target voted out", + "LawyerInfo": "Help your target win!", + "VectorInfo": "Jump in! Jump out!", + "JackalInfo": "Murder everyone", + "GodInfo": "Everything is under your control", + "InnocentInfo": "Get someone ejected by making them kill you", + "PelicanInfo": "Eat all players", + "RevolutionistInfo": "Recruit players to win with you", + "HaterInfo": "Kill Lovers and Neptunes", + "DemonInfo": "Consume blood volumes", + "StalkerInfo": "Descend into the darkness, release fear!", + "WorkaholicInfo": "Finish all tasks to solo win!", + "SolsticerInfo": "Speed run all your tasks!", + "CollectorInfo": "Collect votes from players", + "ProvocateurInfo": "Victory with help target", + "BloodKnightInfo": "Killing gives you a temporary shield", + "PlagueBearerInfo": "Plague everyone to turn into Pestilence", + "PestilenceInfo": "Obliterate everyone!", + "SoulCollectorInfo": "Predict deaths to collect souls", + "DeathInfo": "Enact Armageddon", + "BakerInfo": "Feed Players Bread in order to become Famine", + "FamineInfo": "Starve Everyone", + "BerserkerInfo": "Kill to increase your level", + "WarInfo": "Destroy everything", + "GlitchInfo": "Hack and kill everyone", + "SidekickInfo": "Help the Jackal kill everyone", + "FollowerInfo": "Follow a player and help them", + "CultistInfo": "Charm everyone", + "SerialKillerInfo": "Kill off everyone to win!", + "JuggernautInfo": "With each kill, your cooldown decreases", + "InfectiousInfo": "Infect everyone", + "VirusInfo": "Kill and infect everyone", + "PursuerInfo": "Protect yourself and live to the end!", + "PlagueDoctorInfo": "Spread the infection!", + "PhantomInfo": "Get killed and finish your tasks to win!", + "PirateInfo": "Successfully plunder players to win", + "AgitaterInfo": "Pass a Bomb onto others", + "MaverickInfo": "Kill and survive to the end", + "CursedSoulInfo": "Snatch souls and steal the win", + "PickpocketInfo": "Steal votes from your kills", + "TraitorInfo": "Eliminate the Impostors, then win", + "VultureInfo": "Eat bodies by reporting to win", + "TaskinatorInfo": "Silent tasks, deadly blasts", + "BenefactorInfo": "Task complete, shield elite!", + "MedusaInfo": "Stone bodies by reporting them", + "SpiritcallerInfo": "Turn Players into Evil Spirits", + "AmnesiacInfo": "Remember the role of a dead body", + "ImitatorInfo": "Imitate a player's role", + "BanditInfo": "Rob a player's add-on", + "DoppelgangerInfo": "Steal your target's identity", + "MasochistInfo": "Get attacked a few times to win!", + "KamikazeInfo": "Kill players with a suicidal mission", + "DoomsayerInfo": "Successfully guess players to win", + "ShroudInfo": "Shroud players to make them kill", + "WerewolfInfo": "Kill crewmates in groups", + "ShamanInfo": "Deflect all the attacks on Voodoo doll", + "SeekerInfo": "Play Hide and Seek with your target", + "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", + "OccultistInfo": "Kill and curse your enemies", + "SchrodingersCatInfo": "The cat is both alive and dead until observed.", + "RomanticInfo": "Protect your partner to win together", + "VengefulRomanticInfo": "Revenge your partner to win together", + "RuthlessRomanticInfo": "Kill everyone to win with your partner", + "PoisonerInfo": "Kill everyone with delayed kills", + "HexMasterInfo": "Hex players to kill them in meetings", + "WraithInfo": "Vent to temporarily go invisible", + "JinxInfo": "Reflect attacks onto your attackers", + "PotionMasterInfo": "Use your potions to your advantage", + "NecromancerInfo": "Kill your killer to defy death", + "WardenInfo": "(Ghost) Alert about danger", + "MinionInfo": "(Ghost) Blind enemies", + "LoversInfo": "Stay alive and win together", + "MadmateInfo": "Help the Impostors", + "NtrInfo": "Everyone sees you as their Lover", + "WatcherInfo": "You see all the colors of votes", + "LastImpostorInfo": "Lower kill cooldown", + "OverclockedInfo": "Lower cooldown", + "FlashInfo": "You're faster", + "TorchInfo": "You have enhanced vision!", + "SeerInfo": "You are alerted when somebody is killed", + "TiebreakerInfo": "Break tied votes", + "ObliviousInfo": "You can't report bodies", + "BewilderInfo": "A twist of vision, a web of confusion", + "WorkhorseInfo": "Be the first to complete all tasks and get more", + "FoolInfo": "You can't fix sabotages", + "AvangerInfo": "You take someone with you upon death", + "YoutuberInfo": "Get killed first to win", + "EgoistInfo": "Win on your own", + "TicketsStealerInfo": "Gain votes with kills", + "SchizophrenicInfo": "You're dead and alive simultaneously", + "MimicInfo": "Reveal killed players' roles to impostors upon death", + "GuesserInfo": "Guess roles of players in meetings to kill", + "NecroviewInfo": "See the team of the dead", + "ReachInfo": "You have a longer kill range", + "BaitInfo": "Your killer self-reports your body", + "TrapperInfo": "Freeze your killer for a few seconds", + "OnboundInfo": "You can't be guessed", + "ReboundInfo": "Guess me right, and face your plight!", + "MundaneInfo": "Tasks all done, guessing's begun.", + "UnreportableInfo": "Your body can't be reported", + "LuckyInfo": "Dodge attackers", + "DoubleShotInfo": "You have an extra life when guessing", + "RascalInfo": "You appear evil in some cases", + "SoullessInfo": "You have no soul", + "GravestoneInfo": "Your role is revealed when you die", + "LazyInfo": "You're too lazy", + "AutopsyInfo": "You see how others died", + "LoyalInfo": "You cannot be recruited", + "EvilSpiritInfo": "You are an evil Spirit", + "RecruitInfo": "Help the Jackal", + "AdmiredInfo": "The Admirer chose you as their love", + "GlowInfo": "You glow in the dark", + "DiseasedInfo": "Increase the cooldown of player who interacts with you", + "AntidoteInfo": "Decrease the cooldown of player who interacts with you", + "StubbornInfo": "Protect your role and addons", + "SwiftInfo": "Your kills don't cause a lunge", + "UnluckyInfo": "Doing things has a chance to kill you", + "VoidBallotInfo": "Your vote count is 0", + "AwareInfo": "Know who revealed your role", + "FragileInfo": "Die instantly if someone uses kill button on you", + "GhoulInfo": "Kill your killer after dying", + "BloodlustInfo": "Unleash your bloodlust and kill", + "SunglassesInfo": "You have reduced vision!", + "MareInfo": "Kill in the darkness", + "BurstInfo": "Make your killer burst!", + "SleuthInfo": "Gain info from dead bodies", + "ClumsyInfo": "You have a chance to miss your kill", + "NimbleInfo": "You can vent!", + "CircumventInfo": "You can no longer vent", + "OiiaiInfo": "OIIAIOIIIAI", + "CyberInfo": "You're popular!", + "HurriedInfo": "God I got too much stuffs!", + "InfluencedInfo": "You lack decisiveness!", + "SilentInfo": "Vote like a Ghost!", + "SusceptibleInfo": "Deathreason lotto!", + "TrickyInfo": "Tricky slays, in mysterious ways.", + "TiredInfo": "Labor makes you rest Zzz..", + "StatueInfo": "You're still as a rock nearby people", + "GMInfo": "Spectate the chaos!", + "NotAssignedInfo": "No assigned role", + "SunnyboyInfo": "Shine, shine my sunshine!", + "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in dark embrace.", + "NukerInfo": "Shapeshift to nuke everyone", + "RainbowInfo": "Colorful melodies! You don't even know your own color.", + "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", + "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time which shows you who is alive and who is dead.", + "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", + "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", + "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", + "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", + "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow, if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased.The Target swaps after a certain amount of time.", + "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworker, up to the max amount set by host.\nWhen you are the last Impostor and all Fireworker have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworker, it's considered an Impostor victory.", + "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", + "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", + "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. If a meeting is called first, your target still dies. If you bite a Bait, you kill normally and report the body.", + "VampiressInfoLong": "(Impostors):\nAs the Vampiress, you can Bite players like a Vampire (single click) or kill normally (double click).", + "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", + "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown, but moves very slowly and has very little vision. Zombie will not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", + "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", + "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", + "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", + "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown, but cannot vent, have Crewmate vision, cannot sabotage, cannot report, and cannot call emergency meetings.\n\nNote: You will bypass any and all shield, killing bait and beartrap won't take any effect", + "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport, be careful).", + "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", + "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID is typed. Use /id to show the IDs of all players, or look next to their names.", + "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon attack the enemies to make them drip blood, this means they will die in a time set by host, and will be aware of it.", + "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", + "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button. But if the target has a kill button of any time, they'll be told after a delay that they were manipulated and they must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", + "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", + "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill normally until you use up all of your ammo.", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", + "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out and all the rifts will be destroyed.\n\nNote: Up to two rifts can be placed at a time, if you try to place a third, it removes the first one.", + "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other people, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", + "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies, and if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog and/or other devices. Note: Anti Adminer does not know for sure if the player is using the device while near it, they only know that someone is near the device.", + "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", + "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill-flash when the Bomber explodes.", + "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", + "TrapsterInfoLong": "(Impostors):\nAs the Trapster, your main method of killing is by body reports.\nWhen someone tries to report a body you killed, they'll die.", + "GangsterInfoLong": "(Impostors):\nThe Gangster can attempt to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", + "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned up body cannot be reported (including bait's).", + "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they come into contact with to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", + "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always an odd kill.", + "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The max of times you can counterattack is set by the host)", + "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", + "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets he can use one to bypass kill cooldown, he will kill even if it's still on cooldown, and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (Number is set by the host).", + "CamouflagerInfoLong": "(Impostors):\nWhen Camouflager uses Shapeshift, all players start to look exactly the same. This state ends when Camouflager reverts its shape-shifting. Note: the skills of communication sabotage camouflage and skills of Camouflager can be superimposed.\nSkill will be invalid if a meeting is held during the skill activation of the Camouflager", + "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players whose skills are erased will always be considered a vanilla role, including the game result page.\nA player can only be erased once(include Oiiai)", + "ButcherInfoLong": "(Impostors):\nButcher kills (including passive kills) have multiple dead bodies on targets, making it impossible to accurately identify other dead body when reporting. Note: Due to the principle of implementation, the killed target has to repeatedly display the animation of being killed. This animation cannot be skipped and cannot participate in the meeting normally during this period. In addition, if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", + "HangmanInfoLong": "(Impostors):\nThe killing method of the Hangman during the shapeshifting is strangling. Strangling ignores any status of the target, such as the shield of the Medic, the protection of the Bodyguard, the skills of the Super Star, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), in addition, Seer will not be prompted.", + "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", + "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you complete a task.", + "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but lack the ability to vent.\nWhen you kill, you temporarily become immune to attacks.", + "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", + "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", + "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", + "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain target by pressing the kill button, and drag around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period of time.\nPress the kill button twice for a direct kill.", + "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", + "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not be teleported with shapeshift and players who are in the vent cannot be teleported.", + "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (eg Lights or Reactor), you cannot kill.", + "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (eg Comms or O2), then you can kill.", + "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", + "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, the targets of your shapeshifting are marked for a deathpact.\nIf enough players are marked for a death pact, the marked players must meet within a defined period of time; if they fail to do so, they die.\nIf a marked player dies before the death pact is completed, the pact is withdrawn.", + "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to permanently change the appearance of the target of the shapeshift. Additionally, for each player's appearance changed, your kill cooldown is reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", + "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshift.", + "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shape-shifting to randomly swap the position of all players. The swap happens twice, once when you start your shape shift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone and players who are in vents cannot be teleported.", + "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown is reset to its original value.", + "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following info will be displayed on the player.:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in range of the infected player becomes infected themselves.\nInfection progress is cumulative, and does not reset with distance or after meetings.", + "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor, or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", + "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", + "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", + "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", + "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round if someone kills the target, the killer will turn into a Refugee.", + "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you can charge up your kill button. Once activated the Chronomancer can use their kill button infinitely until they run out of charge.", + "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized for a short period of time and their vision will be affected.", + "EvilMiniInfoLong": "(Impostors):\nAs an Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which is drastically shortened as you grow up.", + "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target you will blackmail that player, and the blackmailed player cannot speak.\n\nSpeaking by the blackmailed player will trigger the confusion command, please do not speak when the blackmailed player sees his icon", + "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate is voted out in a meeting, as long as you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The number of additional players dying is determined by the host.", + "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task In addition, Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for the Anonymous, marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", + "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only be killed when the Murderer is alone with the Super Star (regular kills only). In addition, the Super Star cannot be guessed by Guessers.", + "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", + "CleanserInfoLong": "(Crewmates):\nCleanser can vote for any target at the meeting to erase the target's Add-ons, and the erasure will take effect after the meeting ends. Depending on the settings cleansed player may never get add on in future", + "KeeperInfoLong": "(Crewmates):\nAs keeper you can vote someone to protect them from being ejected. You can only do this a configurable amount of times.", + "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. As a setting, these votes can be hidden, you can vent to call a meeting at any time, and you are revealed as Mayor upon tasks completion.", + "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting, at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", + "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, Communications by using only one side. Lights can be fixed by flicking only one switch. Opening a door will open all doors in the map.", + "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", + "VigilanteInfoLong": "(Crewmates):\nThe Vigilante is tasked with eliminating potential threats to the crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster can not convert Vigilante into madmate.", + "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or be voted (vote count will be 0). The Jailer may choose to execute the prisoner by voting them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote : Jailed players cannot be guessed or judged and jailed players can only guess Jailer.", + "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see Impostors names being displayed in red on meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", + "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", + "SpeedBoosterInfoLong": "(Crewmates):\nSpeed Booster increase their movement speed every time they complete a task. Note: due to technical limitations, the Speed Booster appears to be at a normal speed to others, so they look like glitch.", + "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, Doctor can access vitals wherever you are while he still have battery.", + "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes someone, the meeting will end on the spot and the player they voted will be ejected. The moment the Dictator vote someone out, Dictator will also die.", + "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", + "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", + "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", + "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role guesser tried to guess, and you will also be notified in case of a misguess.", + "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill any person but they can only do it once the whole game.", + "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", + "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", + "VeteranInfoLong": "(Crewmates):\nVeteran can enter the alert state by venting. If a player tries to kill the veteran in the alert state, the veteran will kill the murderer instead. Veteran will see a shield-animation on their body and a text displayed above their head as a reminder when they enter and exit the alert state.", + "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though, crewmates can also be killed with the bombs.", + "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil that has a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to copycat after every meeting", + "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate and the murderer is an Impostor, the Bodyguard will not activate the skill.", + "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the kill ability has the counterfeit, he will suicide when he tries to kill someone next time.", + "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", + "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game, when the Medic dies, the target's shield will be removed. The Medic can also see if someone is trying to break the target's shield.\nDepending on the host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", + "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote:- If the setting to give random active players as hint is on, you will not be able to check same player multiple times", + "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the host), and if it is wrong, the judge commits suicide.\nThe judgment command is: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", + "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", + "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after their dead body is reported. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", + "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. This typically indicates the use of some role ability, so look out for this.", + "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has extra votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exhiled.", + "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", + "OverseerInfoLong": "(Crewmates):\nAs a Overseer you have very limited vision but you can use your kill button to reveal the role of a nearby player. Use the kill button to start the reveal, a 「○」 will be displayed next to the reveal target. Stay near the target for a defined time to reveal his role, if you move too far away from the target the reveal will be aborted.", + "CoronerInfoLong": "(Crewmates):\nAs a Coroner you can't report corpses, instead after trying to report the corpse you will see an arrow leading you to the killer. If a meeting is called, the arrows disappear. Depending on the setting, the body you found cannot be reported.", + "TrackerInfoLong": "(Crewmates):\nAs a Tracker, you can vote for a player in the meeting, which will mark their position for you in the game with arrows. In addition, at the beginning of a meeting you will be shown in which room the player was last, if the option is activated.", + "PresidentInfoLong": "(Crewmates):\nThe President has 2 abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", + "MerchantInfoLong": "(Crewmates):\\As a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can avert the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The bribe money used is lost and is not available for additional bribes.", + "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", + "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk you can kill a limited amount of players decided by host, tough there's a chance you miss, slicing someone multiple times increases the chances.", + "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", + "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in either red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when a meeting is called.", + "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon tasks completion. You can't even be guessed in meetings.", + "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is at 0 seconds, you still have a short time to vent.\nIf you don't make it you die, if you make it the suicide timer is reset.\nAlso, after you are ventilated, no one can interact with you for a defined period of time.\nAfter this period is over, you are immobilized for another defined period of time and cannot report any bodies.", + "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you come out of the vent, you will spawn near a random vent in the map (Except the one you just used).", + "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", + "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by host.", + "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", + "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability, if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", + "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", + "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message If they are in the same team, or a denial message if they are not in the same team.\n\nAll neutrals and converted playes are counted in the same team. Trickster is counted as crew and Rascal is counted as Impostor.\nChecking command : /cmp [player id 1] [player id 2]", + "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non crew role. Crewmates can see ☆ besides captain's name.\n\nIf anyone betrays the captain's trust by voting captain out, they will lose an addon.", + "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", + "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will be rewinded back to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", + "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", + "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the host's setting you may misfire on reaching the max kill cooldown and your target dies with you. \n\nYou win with other crewmates.", + "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", + "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, doorlogs, or admin.", + "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", + "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real time.", + "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings)", + "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", + "ChiefOfPoliceInfoLong": "(Crewmates):\nPlayers with swords can be recruited to join the sheriff's team to serve the crew, but players without swords cannot be recruited.\n note: only one recruitment opportunity", + "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, you can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses.", + "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability that is used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you have no ability uses left, you won't see orange names at all!\nNote: If the kill button interaction is blocked the player's cooldown will reset to 10s'", + "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player", + "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse by clicking the kill button on the player and following them for a few seconds. When the dousing starts and it's successful, a shield animation will be displayed as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he failed to kill everyone, he loses.", + "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting, depending on the setting, you may have to report the body to receive a clue. The more tasks you complete the more precise the clues get.", + "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", + "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double click to kill normally. When you die all marked also die, with death reason Targeted.", + "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you have a certain amount of targets that reset every meeting. If you kill one of your targets, your kill cooldown decreases by the set amount permanately. If you kill someone else other than any of your targets, your kill cooldown permanately increases by the set amount. You see your targets with a colored name.", + "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini is two roles. Either a Nice Mini or an Evil Mini is chosen.\n\nUse '/r nicemini' and '/r evilmini' respectively for more details.", + "JesterInfoLong": "(Neutrals):\nIf the Jester get voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", + "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", + "ExecutionerInfoLong": "(Neutrals):\nExecutioner has an execution target, which will be indicated by a diamond 「♦」 next to their name. If the execution target is killed, the Executioner will be changed to Crewmate, Jester or Opportunist according to the settings. If the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", + "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", + "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", + "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", + "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill normally (i.e. don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, then they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", + "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you snatch the win, i.e. everyone else loses and you win.", + "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target is voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", + "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those who are swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", + "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by host, to kill players (though they are still recruited). When the required number of players are recruited, (displayed next to your name) you must vent within the specified time in order to win the game immediately with all of your recruits. If you do not vent in time, you lose and die.", + "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, you can only kill Lovers, and other recruiting roles and add-ons, depending on the settings. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", + "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", + "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause electricity sabotage (if electricity is already sabotaged, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker win alone.", + "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on host's settings, you can only win if alive and/or you are revealed to everyone at the beginning (these settings are almost never both on).", + "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting is finished, your tasks get reset, and you need to start all over again.\nVotes on the Solsticer will be directly cancelled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in game.", + "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required number of votes, the game ends and you win alone, even if you voted a Jester or Executioner's target out.", + "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect, and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", + "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", + "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", + "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill that makes them immortal for a few seconds.", + "ApocalypseInfoLong": "(Apocalypse):\nEvery role of the Apocalypse Team has their own objective to carry out in order to transform.\nTransformed Apocalypse members are immortal, but everyone will be notified that they have transformed.\n\nRoles: Plaguebearer, Soul Collector, Baker, Berserker\nTransformed: Pestilence, Death, Famine, War\n\n(if you got this as a role, you have bugged the game, good job)Your presence is announced to everyone the meeting after you transform.", + "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you vote players to predict their death. If the prediction is correct and the target dies in the next round you collect their soul. \n\nOnce you collect the configurable amount of souls, you become Death.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Depending on the host's settings, a meeting may or may not be called immediately. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", + "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", + "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone the meeting after you transform.", + "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", + "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", + "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive. Depending on settings, you will not be impacted by any harmful interactions and may have a teammate.", + "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", + "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you then can simply outnumber the crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", + "VirusInfoLong": "(Neutrals):\nThe task of the virus is to kill or infect all other players. When the virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected and joins the virus team or dies at the end of the meeting if the virus won't get voted out, dependent on the settings. If there are more players on the Virus team than on the Crewmate team, the Virus team wins.", + "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, just survive to the end of the game.", + "PhantomInfoLong": "(Neutrals):\nAs the Phantom, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", + "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both Pirate and the target chooses same number, Pirate wins.\nAdditionally, if Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command:- /duel X (where X can be 0, 1 or 2)\n\nYou win after winning a certain number of duels set by the host.\n\nNote: If target did not participate in duel, the kill will not count towards pirate victory", + "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", + "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", + "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you snatch the victory if you survive to the end of the game.\n\nYou can snatch the win from a Jester or Executioner.\n\nAdditionally, you can snatch the souls of other players.\nSoulless players win with you and count as dead.", + "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\nThese votes are hidden.\n\nKill everyone to win.", + "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors but they don't know you.\nThe twist? They can kill you but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", + "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", + "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you complete a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", + "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you complete a task, the task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks", + "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", + "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players for a short time and/or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", + "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", + "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee or some Neutral", + "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button once to steal a player's addon and twice to kill. Depending on the settings, you may instantly steal the addon or after the meeting starts. After the max number of steals are reached you will kill normally. Additionally, if there are no stealable addons present on the target or the target is stubborn you will kill the target.\n\nKill everyone to win.\n\nNote:- Cleansed, Last Impostor and Lovers can not be stolen.\nNote:- If Bandit can vent is on, Nimble will become unstealable", + "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote:- You can not steal the identity of the target when Camouflage is active.", + "MasochistInfoLong": "(Neutrals):\nAs the Masochist, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", + "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings) then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themself after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", + "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", + "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.", + "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If seeker tags wrong player a point is deducted and if seeker tags correct player a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target\n\n The seeker needs to collect certain number of points set by the host to win", + "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark upto x amount of targets each round by using kill button on them. When the meeting starts, your job is to have one of the marked targets ejected. If unsuccessful you will suicide, except if you didn't mark any targets or all the targets are dead. The selected targets resets to 0 after the meeting ends. If you succeed you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the host.", + "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use their kill button on you, the interaction is not blocked, and you will die.", + "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield which protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote : If your role changes your win condition will be changed accordingly", + "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As Ruthless Romantic, you win if you kill everyone and be the last one standing. If you win your dead partner also wins with you.", + "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non neutral killer) is killed. As a Vengeful Romantic, Your goal is to avenge your partner, which means you have to kill the killer of your partner. If you succeed to do so, then both you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", + "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", + "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", + "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", + "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you are attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", + "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons defaults to killing.", + "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally, when someone tries to kill you, the kill will be blocked and you will be teleported to a random vent. You will have a limited time to kill your killer. If you succeed to do so, you live. If the time runs out before you kill your killer, you die permanately. If you try to kill someone else other than your killer, you will die.", + "LastImpostorInfoLong": "(Add-ons):\nThis effect is given to the last surviving Impostor. Reduces their kill cooldown.", + "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nOnly assigned to roles with a kill button.", + "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when only the Lovers are left. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 mark next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", + "MadmateInfoLong": "(Add-ons):\nOnly Crewmate can become Madmate. Madmate's task is to help the Impostors win the game, Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone.", + "NtrInfoLong": "(Add-ons):\nWhen there is Neptune, all players will see that they are Lovers with Neptune, and they will not die in love together and will not change the win conditions. Note: Lovers won't become Neptune, and Neptune won't become Lovers.", + "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", + "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the host)", + "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", + "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", + "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreaker choose different tie targets at the same time, the skills of the Tiebreaker will not take effect.", + "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still be reported automatically, and Oblivious can still be used as a scapegoat for the Anonymous.", + "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder is killed, the murderer's vision may become the same as the Bewilder's vision depending on the settings.", + "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, Workhorse will give the player extra tasks. The amount of additional tasks are set by the host.", + "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", + "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out and unconventional kills are not counted), the Avenger will revenge a random player.", + "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to be killed in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc. will not trigger the skills of the YouTuber.", + "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", + "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the vote number is set by the host, and the decimal is rounded down). Also, extra votes from the Stealer are hidden during meeting.", + "SchizophrenicInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Schizophrenic, you will be considered as two players in the game for the purpose of determining when the game ends due to killers having majority. Additionally, this grants you an extra vote, depending on options.", + "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called, this message will include information on roles who were killed by the Mimic.", + "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess roles of players in meetings to kill them.\nGuessing incorrectly kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. You have the longest kill range possible in the game, unlike everyone else.", + "BaitInfoLong": "(Add-ons):\nWhen the Bait is killed, the murderer who killed the Bait will be forced to self-report the Bait's body. However, this won't happen when the Bait is killed by a Scavenger, Cleaner, Swooper, Wraith, or Killing Machine. The report may have a delay according to Host's settings.", + "TrapperInfoLong": "(Add-ons):\nWhen Beartrap is killed, Beartrap immobilize killer for a configurable amount of time.", + "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", + "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", + "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", + "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", + "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", + "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess after all your tasks has been finished.", + "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", + "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse cannot be reported.", + "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", + "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on there is a probability for you to evade the kill, the specific probability is set by the host. When the evasion takes effect, the killer will see the shield-animation, but you not know anything.", + "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", + "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", + "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul snatches your soul, you get this add-on.\n\nYou are not counted as alive.", + "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", + "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", + "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", + "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", + "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to temporarily give the Spiritcaller a shield against a kill attempt.", + "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", + "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", + "GlowInfoLong": "(Add-ons):\nAs the Glow, your name is colored during a lights sabotage.", + "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be increased by configurable amount of time", + "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be decreased by configurable amount of time", + "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add on, Eraser can’t erase your role, Cleanser can't cleanse you, Bandit can't steal from you and Monarch can't knight you.\nAdditionally, you can’t gain any new addons from the merchant", + "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.", + "UnluckyInfoLong": "(Add-ons):\nAs the Unlucky, doing tasks, killing, or venting as a chance to kill you.", + "VoidBallotInfoLong": "(Add-ons):\nHolder of this addon will have 0 vote count", + "AwareInfoLong": "(Add-ons):\nAs the Aware, you will be notified in the next meeting if a revealing role had interacted with you", + "FragileInfoLong": "(Add-ons):\nAs Fragile, you will die instantly if someone tries to use kill button on you (even if the role can not directly kill)", + "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on tasks completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nOnly assigned to crewmates, and not crewmates with no tasks or are task based.", + "BloodlustInfoLong": "(Add-ons):\nAs the Bloodlust, doing tasks allows you to kill.\nWhen you complete a task, the next player you come in contact with dies.\n\nYour bloodlust remains after a meeting.\nUpon making a kill, your bloodlust clears till the next task you complete.\nBloodlusts do not stack.\n\nOnly assigned to crewmates with tasks.", + "SunglassesInfoLong": "(Add-ons):\nAs the Sunglasses, your vision is reduced.", + "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", + "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", + "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", + "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset and the target remains untouched.\n\nOnly assigned to killers.", + "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", + "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", + "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced,then the vote result won't shift\nCollector cannot become influenced.", + "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", + "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", + "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", + "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they complete a task, they will temporarily get lower vision & lower speed.", + "StatueInfoLong": "(Add-ons):\nWhenever many people are near Statue, the Statue is completely frozen or slowed down dependand on settings.", + "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot be killed while in a group.\nAdditionally, your death will be known.", + "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you failed with your tasks, you lose.\nHurried hurries to his goal so it won't get madmate,charmed or so.", + "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", + "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy", + "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence has no effect on the game, and all players know who the Game Master is. The Game Master role will be assigned to the host, who will automatically become a ghost at the start of the game.", + "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. When you are alive, the game will not end due to killers gaining majority.\nAdditionally, you have access to portable vitals.", + "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown is permanently halved.", + "NukerInfoLong": "(Impostors):\nAs the Nuker, you're a stronger Bomber.\n\nShapeshift to nuke everyone.", + "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", + "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", + "ShowTextOverlay": "Text Overlay", + "Overlay.GuesserMode": "Guesser Mode", + "Overlay.NoGameEnd": "No Game End", + "Overlay.DebugMode": "Debug Mode", + "Overlay.LowLoadMode": "Low Load Mode", + "Overlay.AllowConsole": "Console", + "DisableShieldAnimations": "Disable Unnecessary Shield Animations", + "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", + "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", + "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", + "AbilityUseLimit": "Initial Ability Use Limit", + "ShowArrows": "Has Arrows pointing toward bodies", + "ArrowDelayMin": "Minimum Arrow show-up delay", + "ArrowDelayMax": "Maximum Arrow show-up delay", + "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", + "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", + + "AbilityCD": "Ability Cooldown", + "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", + "ShowSpecificRole": "Know specific roles on Task Completion", + "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", + "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", + "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", + "DisableMeeting": "Disable Meetings", + "DisableCloseDoor": "Disable Doors Sabotage", + "DisableSabotage": "Disable Sabotages", + "NoGameEnd": "No Game End", + "AllowConsole": "BepInEx Console", + "DebugMode": "Debug Mode", + "SyncButtonMode": "Sync Buttons Mode", + "RandomMapsMode": "Random Maps Mode", + "SyncedButtonCount": "Max Number of Emergency Meetings Allowed", + "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", + "HHFailureKCDIncrease": "Kill cooldown increase on killing others", + "HHNumOfTargets": "Number of targets", + "Targets": "Targets: ", + "HHMaxKCD": "Maximum kill cooldown", + "HHMinKCD": "Minimum kill cooldown", + "AllAliveMeeting": "Meeting When No One is Dead", + "AllAliveMeetingTime": "Meeting Time When No One is Dead", + "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", + "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", + "AdditionalEmergencyCooldownTime": "Additional Cooldown", + "LadderDeath": "Fall From Ladders", + "LadderDeathChance": "Fall To Death Chance", + "DisableSwipeCardTask": "Disable Swipe Card Task", + "DisableSubmitScanTask": "Disable Submit Scan Task", + "DisableUnlockSafeTask": "Disable Unlock Safe Task", + "DisableUploadDataTask": "Disable Upload Data Task", + "DisableStartReactorTask": "Disable Start Reactor Task", + "DisableResetBreakerTask": "Disable Reset Breakers Task", + "DisableShortTasks": "Disable Short Tasks", + "DisableCleanVent": "Disable Clean Vent Task", + "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", + "DisableChartCourse": "Disable Chart Course Task", + "DisableStabilizeSteering": "Disable Stabilize Steering Task", + "DisableCleanO2Filter": "Disable Clean O2 Filter Task", + "DisableUnlockManifolds": "Disable Unlock Manifolds Task", + "DisablePrimeShields": "Disable Prime Shields Task", + "DisableMeasureWeather": "Disable Measure Weather", + "DisableBuyBeverage": "Disable Buy Beverage", + "DisableAssembleArtifact": "Disable Assemble Artifact Task", + "DisableSortSamples": "Disable Sort Samples Task", + "DisableProcessData": "Disable Process Data Task", + "DisableRunDiagnostics": "Disable Run Diagnostics Task", + "DisableRepairDrill": "Disable Repair Drill Task", + "DisableAlignTelescope": "Disable Align Telescope Task", + "DisableRecordTemperature": "Disable Record Temperature Task", + "DisableFillCanisters": "Disable Fill Canisters Task", + "DisableMonitorTree": "Disable Monitor Tree Task", + "DisableStoreArtifacts": "Disable Store Artifacts Task", + "DisablePutAwayPistols": "Disable Put Away Pistols Task", + "DisablePutAwayRifles": "Disable Put Away Rifles Task", + "DisableMakeBurger": "Disable Make Burger Task", + "DisableCleanToilet": "Disable Clean Toilet Task", + "DisableDecontaminate": "Disable Decontaminate Task", + "DisableSortRecords": "Disable Sort Records Task", + "DisableFixShower": "Disable Fix Shower Task", + "DisablePickUpTowels": "Disable Pick Up Towels Task", + "DisablePolishRuby": "Disable Polish Ruby Task", + "DisableDressMannequin": "Disable Dress Mannequin Task", + "DisableCommonTasks": "Disable Common Tasks", + "DisableFixWiring": "Disable Fix Wiring Task", + "DisableEnterIdCode": "Disable Enter ID Code Task", + "DisableInsertKeys": "Disable Insert Keys Task", + "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", + "DisableLongTasks": "Disable Long Tasks", + "DisableAlignEngineOutput": "Disable Align Engine Output Task", + "DisableInspectSample": "Disable Inspect Sample Task", + "DisableEmptyChute": "Disable Empty Chute Task", + "DisableClearAsteroids": "Disable Clear Asteroids Task", + "DisableWaterPlants": "Disable Water Plants Task", + "DisableOpenWaterways": "Disable Open Waterways Task", + "DisableReplaceWaterJug": "Disable Replace Water Jug Task", + "DisableRebootWifi": "Disable Reboot Wifi Task", + "DisableDevelopPhotos": "Disable Develop Photos Task", + "DisableRewindTapes": "Disable Rewind Tapes Task", + "DisableStartFans": "Disable Start Fans Task", + "DisableOtherTasks": "Disable Situational Tasks", + "DisableEmptyGarbage": "Disable Empty Garbage Task", + "DisableFuelEngines": "Disable Fuel Engines Task", + "DisableDivertPower": "Disable Divert Power Task", + "DisableActivateWeatherNodes": "Disable Weather Nodes Task", + "DisableRoastMarshmallow": "Disable Roast Marshmallow", + "DisableCollectSamples": "Disable Collect Samples", + "DisableReplaceParts": "Disable Replace Parts", + "DisableCollectVegetables": "Disable Collect Vegetables", + "DisableMineOres": "Disable Mine Ores", + "DisableExtractFuel": "Disable Extract Fuel", + "DisableCatchFish": "Disable Catch Fish", + "DisablePolishGem": "Disable Polish Gem", + "DisableHelpCritter": "Disable Help Critter", + "DisableHoistSupplies": "Disable Hoist Supplies", + "DisableFixAntenna": "Disable Fix Antenna", + "DisableBuildSandcastle": "Disable Build Sandcastle", + "DisableCrankGenerator": "Disable Crank Generator", + "DisableMonitorMushroom": "Disable Monitor Mushroom", + "DisablePlayVideoGame": "Disable Play Video Game", + "DisableFindSignal": "Disable Find Signal", + "DisableThrowFisbee": "Disable Throw Frisbee", + "DisableLiftWeights": "Disable Lift Weights", + "DisableCollectShells": "Disable Collect Shells", + "SuffixMode": "Suffix", + "SuffixMode.None": "None", + "SuffixMode.Version": "Version", + "SuffixMode.Streaming": "Streaming", + "SuffixMode.Recording": "Recording", + "SuffixMode.RoomHost": "Room Host", + "SuffixMode.OriginalName": "Original Name", + "SuffixMode.DoNotKillMe": "Don't kill me", + "SuffixMode.NoAndroidPlz": "No phones", + "SuffixMode.AutoHost": "Auto-Host", + "SuffixModeText.DoNotKillMe": "Don't kill me", + "SuffixModeText.NoAndroidPlz": "No phones please", + "SuffixModeText.AutoHost": "Auto-hosting", + "FormatNameMode": "Player Name Mode", + "FormatNameModes.None": "Disable", + "FormatNameModes.Color": "Color", + "FormatNameModes.Snacks": "Random", + "DisableEmojiName": "Disable Emoji in names", + "FixFirstKillCooldown": "Override Starting Kill Cooldown", + "FixKillCooldownValue": "Starting Kill Cooldown", + "OverclockedReduction": "Kill Cooldown Reduction", + "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", + "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", + "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", + "GhostIgnoreTasks": "Ghosts Exempt From Tasks", + "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", + "MaxImpGhostRole": "Max Impostor Ghost-Roles", + "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", + "DefaultAngelCooldown": "Default Ability Cooldown", + "DisableTaskWin": "Disable Task Win", + "HideGameSettings": "Hide Game Settings", + "DIYGameSettings": "Enable only custom /n messages", + "Settings:": "Settings:", + "PlayerCanSetColor": "Players can use the /color command", + "PlayerCanSetName": "Players can use the /rn command", + "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", + "PlayerCanUseTP": "Players can use the /tpin and /tpout command", + "CanPlayMiniGames": "Players can play mini games", + "KPDCamouflageMode": "Camouflage Appearance", + "RoleOptions": "Role Options", + "DarkTheme": "Enable Dark Theme", + "AutoStart": "Auto start", + "EnableCustomButton": "Enable Custom Button Images", + "EnableCustomSoundEffect": "Enable Custom Sound Effects", + "SwitchVanilla": "Switch Vanilla", + "ModeForSmallScreen": "Small Screen Mode", + "UnlockFPS": "Unlock FPS", + "ForceOwnLanguage": "Force mod to use your language if possible", + "ForceOwnLanguageRoleName": "Force role names in your language if possible", + "VersionCheat": "Bypass version synchronization check", + "GodMode": "God Mode", + + "AutoDisplayKillLog": "Display Kill-log", + "AutoDisplayLastRoles": "Display Last Roles", + "AutoDisplayLastResult": "Auto Display Last Result", + "RevertOldKillLog": "Revert to old kill-log", + + "HideExileChat": "Hide exile (lava) chat", + "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", + + "EnableYTPlan": "Enable Youtuber Plan", + "KickLowLevelPlayer": "Kick players whose level is lower than", + "TempBanLowLevelPlayer": "Temporarily ban low-level players", + "ApplyWhiteList": "Turn on Whitelist to bypass level kick", + "AllowOnlyWhiteList": "Allow only whitelisted players to join", + "AutoKickStart": "Kick players that say start", + "AutoKickStartTimes": "Number of warnings before kick", + "AutoKickStartAsBan": "Block a player after they're kicked", + "AutoKickStopWords": "Kick players who write banned words", + "AutoKickStopWordsTimes": "Number of warnings for banned words", + "AutoKickStopWordsAsBan": "Block a player after they're kicked", + "AutoWarnStopWords": "Warning to those who write banned words", + "TempBanPlayersWhoKeepQuitting": "Temporarily ban players who leave and join repeatedly", + "QuitTimesTillTempBan": "The quit frequency needed for temp ban", + "KickOtherPlatformPlayer": "Kick Non-PC players", + "OptKickAndroidPlayer": "Kick Android players", + "OptKickIphonePlayer": "Kick iOS players", + "OptKickXboxPlayer": "Kick Xbox players", + "OptKickPlayStationPlayer": "Kick PlayStation players", + "OptKickNintendoPlayer": "Kick Nintendo Switch players", + "ShareLobby": "Allow TOHE-Chan Shares Lobby Code To Discord", + "ShareLobbyMinPlayer": "Share Lobby Code When The Number Of Players Reaches", + "DisableVanillaRoles": "Disable Vanilla Roles", + "VoteMode": "Voting Mode", + "WhenSkipVote": "If the Player Skipped", + "WhenSkipVoteIgnoreFirstMeeting": "Ignore the First Meeting", + "WhenSkipVoteIgnoreNoDeadBody": "Ignore When No Dead Body", + "WhenSkipVoteIgnoreEmergency": "Ignore at Emergency Meetings", + "WhenNonVote": "If the Player didn't vote", + "Default": "No vote", + "Suicide": "Suicide", + "SelfVote": "Self Vote", + "Skip": "Skip", + "WhenTie": "When Tied Vote", + "TieMode.Default": "No ejects", + "TieMode.All": "Eject All", + "TieMode.Random": "Eject Random", + "DisableDevices": "Disable Devices", + "DisableSkeldDevices": "Disable Skeld Devices", + "DisableMiraHQDevices": "Disable MIRA HQ Devices", + "DisablePolusDevices": "Disable Polus Devices", + "DisableAirshipDevices": "Disable Airship Devices", + "DisableFungleDevices": "Disable Fungle Devices", + "DisableSkeldAdmin": "Disable Admin", + "DisableMiraHQAdmin": "Disable Admin", + "DisablePolusAdmin": "Disable Admin", + "DisableAirshipCockpitAdmin": "Disable Cockpit Admin", + "DisableAirshipRecordsAdmin": "Disable Records Admin", + "DisableSkeldCamera": "Disable Cameras", + "DisablePolusCamera": "Disable Cameras", + "DisableAirshipCamera": "Disable Cameras", + "DisableMiraHQDoorLog": "Disable DoorLog", + "DisablePolusVital": "Disable Vitals", + "DisableAirshipVital": "Disable Vitals", + "DisableFungleVital": "Disable Vitals", + "DisableFungleBinoculars": "Disable Binoculars (Not work for vanilla)", + "IgnoreConditions": "Ignore Conditions", + "IgnoreImpostors": "Ignore Impostors", + "IgnoreNeutrals": "Ignore Neutrals", + "IgnoreCrewmates": "Ignore Crewmates", + "IgnoreAfterAnyoneDied": "Ignore After First Death", + "LightsOutSpecialSettings": "Fix Lights Special Settings", + "BlockDisturbancesToSwitches": "Block Switches When They Are Up", + "DisableAirshipViewingDeckLightsPanel": "Disable Viewing Deck Lights Panel (Airship)", + "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", + "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", + "RandomSpawnMode": "Random Spawns Mode", + "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", + "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", + "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", + "CommsCamouflage": "Camouflage during Comms Sabotage", + "DisableOnSomeMaps": "Disable comms camouflage on some maps", + "DisableOnSkeld": "Disable on The Skeld", + "DisableOnMira": "Disable on MIRA HQ", + "DisableOnPolus": "Disable on Polus", + "DisableOnDleks": "Disable on dlekS ehT", + "DisableOnAirship": "Disable on Airship", + "DisableOnFungle": "Disable on The Fungle", + "DisableReportWhenCC": "Disable body reporting while camouflaged", + "EnableDebugMode": "Enable Debug Mode", + "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", + "SendRoleDescriptionFirstMeeting": "Show Role Descriptions to Unmodded Clients at First Meeting", + "RoleAssigningAlgorithm": "Role Assigning Algorithm", + "RoleAssigningAlgorithm.Default": "Default", + "RoleAssigningAlgorithm.NetRandom": ".NET System.Random", + "RoleAssigningAlgorithm.HashRandom": "HashRandom", + "RoleAssigningAlgorithm.Xorshift": "Xorshift", + "RoleAssigningAlgorithm.MersenneTwister": "MersenneTwister", + "MapModification": "Map Modifications", + "DisableAirshipMovingPlatform": "Disable Moving Platform (Airship)", + "AirshipVariableElectrical": "Variable Electrical (Airship)", + "DisableSporeTriggerOnFungle": "Disable Spore Trigger (Fungle)", + "DisableZiplineOnFungle": "Disable Zipline (Fungle)", + "DisableZiplineFromTop": "Disable Use From Top", + "DisableZiplineFromUnder": "Disable Use From Under", + "ResetDoorsEveryTurns": "Reset Doors After Meeting (Airship/Polus/Fungle)", + "DoorsResetMode": "Reset Doors Mode", + "AllOpen": "All Open", + "AllClosed": "All Closed", + "RandomByDoor": "Closed Random", + "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", + "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", + "DecontaminationTimeOnPolus": "Decontamination Time On Polus", + "ApplyDenyNameList": "Apply DenyName List", + "KickPlayerFriendCodeNotExist": "Kick players without a friend code", + "TempBanPlayerFriendCodeNotExist": "Temp Ban players without a friend code", + "ApplyBanList": "Apply BanList", + "EndWhenPlayerBug": "End the game when a player has a critical error", + "RemovePetsAtDeadPlayers": "Remove pets at dead players", + "KillFlashDuration": "Kill-Flash Duration", + "ConfirmEjectionsMode": "Confirm Ejections Mode", + "ConfirmEjections.None": "None", + "ConfirmEjections.Team": "Team", + "ConfirmEjections.Role": "Role", + "ShowImpRemainOnEject": "Show remaining Impostors on ejects", + "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", + "ConfirmEgoistOnEject": "Confirm Egoists on ejection", + "ConfirmLoversOnEject": "Confirm Lovers on ejection", + "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", + "HideBittenRolesOnEject": "Hide roles of bitten players on ejection", + "ShowTeamNextToRoleNameOnEject": "Show what team the ejected player's role is on", + "Ban": "Ban", + "Kick": "Kick", + "NoticeMe": "Notify me", + "NoticeEveryone": "Notify everyone", + "TempBan": "Temporary Ban", + "OnlyCancel": "Only Cancel the cheat actions", + "CheatResponses": "When a cheating player is found", + "NeutralRoleWinTogether": "Neutrals win together", + "NeutralWinTogether": "All Neutrals win together", + "MenuTitle.Disable": "★ Disable ★", + "MenuTitle.MapsSettings": "★ Maps ★", + "MenuTitle.Sabotage": "★ Sabotage ★", + "MenuTitle.Meeting": "★ Meeting ★", + "MenuTitle.Ghost": "★ Ghost ★", + "MenuTitle.Other": "★ Different ★", + "MenuTitle.Ejections": "★ Ejection ★", + "MenuTitle.Settings": "★ Settings ★", + "MenuTitle.TaskSettings": "★ Task Management ★", + "MenuTitle.Guessers": "★ Guesser Mode ★", + "MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★", + "ShieldPersonDiedFirst": "Shield player who dead first in the last game", + "LegacyNemesis": "Use Legacy Version", + "ArsonistKeepsGameGoing": "Arsonist keeps the game going", + "ArsonistCanIgniteAnytime": "Can Ignite Anytime", + "ArsonistMinPlayersToIgnite": "Minimum doused needed for ignite", + "ArsonistMaxPlayersToIgnite": "Maximum doused needed for ignite", + "PuppeteerDoubleKills": "Puppet dies alongside victim", + "MastermindCD": "Manipulate Cooldown", + "MastermindTimeLimit": "Time limit to kill someone", + "MastermindDelay": "Manipulation notification delay", + "ManipulateNotify": "Kill someone in {0}s or die!", + "ManipulatedKilled": "{0} has killed someone", + "SurvivedManipulation": "You survived the Mastermind's manipulation!", + "Glitch_HackCooldown": "Hack Cooldown", + "Glitch_HackDuration": "Hack Duration", + "Glitch_MimicCooldown": "Mimic Cooldown", + "Glitch_MimicDuration": "Mimic Duration", + "Glitch_MimicButtonText": "Mimic", + "Glitch_MimicDur": "Mimic Duration: {0}s", + "Glitch_HackCD": "Hack Cooldown: {0}s", + "Glitch_KCD": "Kill Cooldown: {0}s", + "Glitch_MimicCD": "Mimic Cooldown: {0}s", + "HackedByGlitch": "You are hacked by the Glitch, you can't {0}.", + "GlitchKill": "kill", + "GlitchReport": "report", + "GlitchVent": "vent", + "ShowFPS": "Show FPS", + "FPSGame": "FPS: ", + "Cooldown": "Cooldown", + "KillCooldown": "Kill Cooldown", + "AbilityCooldown": "Ability Cooldown", + "VentCooldown": "Vent Cooldown", + "ControlCooldown": "Control Cooldown", + "PoisonCooldown": "Poison Cooldown", + "PoisonerKillDelay": "Poison Kill Delay", + "WardenNotifyLimit": "Max number of alerts", + "CanVent": "Can Vent", + "CanKill": "Can Kill", + "CanGuess": "Can Guess in Guesser Mode or as Guesser", + "BombCooldown": "Bomb Cooldown", + "ImpostorVision": "Has Impostor Vision", + "CanUseSabotage": "Can Sabotage", + "CanKillAllies": "Can Kill Impostors", + "CanKillSelf": "Can Kill Themself", + "CrewpostorKnowsAllies": "Knows Impostors", + "AlliesKnowCrewpostor": "Known to Impostors", + "CrewpostorLungeKill": "Crewpostor lunges on kill", + "CrewpostorKillAfterTask": "Number of tasks completed to make 1 kill", + + "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", + "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", + "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", + "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", + "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", + "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", + "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", + "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", + "ImpKnowWhosMadmate": "Impostors know Madmates", + "MadmateKnowWhosImp": "Madmates know Impostors", + "MadmateKnowWhosMadmate": "Madmates know each other", + "ImpCanKillMadmate": "Impostors can kill Madmates", + "MadmateCanKillImp": "Madmates can kill Impostors", + "MadmateHasImpostorVision": "Madmates Have Impostor Vision", + "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", + "EGCanGuessImp": "Can Guess Impostor Roles", + "GGCanGuessCrew": "Can Guess Crewmate Roles", + "EGCanGuessAdt": "Can Guess Add-Ons", + "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "GGCanGuessAdt": "Can Guess Add-Ons", + "GuesserCanGuessTimes": "Maximum number of guesses", + "GuesserTryHideMsg": "Try to hide guesser's command", + "GCanGuessImp": "Impostor can guess Impostor roles", + "GCanGuessCrew": "Crewmate can guess Crewmate roles", + "GCanGuessAdt": "Can guess Add-ons", + "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "BountyTargetChangeTime": "Time Until Target Swaps", + "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", + "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", + "BountyShowTargetArrow": "Show arrow pointing towards target", + "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", + "ShapeshiftDuration": "Shapeshift Duration", + "ShapeshiftCooldown": "Shapeshift Cooldown", + "VitalsDuration": "Vitals Duration", + "VitalsCooldown": "Vitals Cooldown", + "DeadImpCantSabotage": "Impostors can't sabotage after they've died", + "VampireKillDelay": "Bite Kill Delay", + + "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", + "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", + + "MechanicSkillLimit": "Initial repair use limit", + "MechanicFixesDoors": "Can open all doors in the same building", + "MechanicFixesReactors": "Can Fix Both Reactors Alone", + "MechanicFixesOxygens": "Can Fix Both O2 Alone", + "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", + "MechanicFixesElectrical": "Can Fix Lights With One Switch", + + "SheriffShowShotLimit": "Display Shot Limit next to Role Name", + "SheriffCanKill%role%": "Can Kill %role%", + "SheriffCanKillNeutrals": "Can Kill Neutrals", + "SheriffCanKillNeutralsMode": "Neutral Configuration", + "SheriffCanKillAll": "All ON", + "SheriffCanKillSeparately": "Individual Settings", + "In%team%": "(Team %team%)", + "SheriffMisfireKillsTarget": "Misfire Kills Target", + "SheriffShotLimit": "Max number of Kills", + "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", + "SheriffCanKillCharmed": "Can kill Charmed players", + "SheriffCanKillEgoist": "Can Kill Egoists", + "SheriffCanKillSidekick": "Can Kill Sidekicks", + "SheriffCanKillLovers": "Can Kill Lovers", + "SheriffCanKillMadmate": "Can Kill Madmates", + "SheriffCanKillInfected": "Can Kill Infected players", + "SheriffCanKillContagious": "Can Kill Contagious players", + "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", + "SheriffMadCanKillImp": "Can kill Impostors", + "SheriffMadCanKillNeutral": "Can kill Neutrals", + "SheriffMadCanKillCrew": "Can kill Crewmates", + + "ReverieIncreaseKillCooldown": "Increase kill cooldown", + "ReverieMaxKillCooldown": "Max kill cooldown", + "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", + "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", + "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", + + "VigilanteNotify": "You have become the very thing you swore to destroy", + + "DoctorTaskCompletedBatteryCharge": "Battery Duration", + "SnitchEnableTargetArrow": "See Arrow Towards Target", + "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", + "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", + "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", + "SnitchCanFindMadmate": "Can Find Madmates", + "SnitchRemainingTaskFound": "Remaining tasks to be known", + "SpeedBoosterUpSpeed": "Increase Speed by", + "SpeedBoosterTimes": "Max Boosts", + "MayorAdditionalVote": "Additional Votes Count", + "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", + "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", + "MayorHideVote": "Hide additional vote(s)", + "HideJesterVote": "Hide Jester's vote", + "MeetingsNeededForWin": "Meetings needed to win", + "ExecutionerCanTargetImpostor": "Can Target Impostors", + "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", + "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", + "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", + "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", + "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", + "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", + "LawyerCanTargetImpostor": "Can Target Impostors", + "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", + "LawyerCanTargetCrewmate": "Can Target Crewmates", + "LawyerCanTargetJester": "Can Target Jester", + "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", + "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", + "LawyerTargetDeadInMeeting": "Your target was killed while meeting./nYour role may change depending on the settings.", + + "MercenaryLimit": "Time Until Suicide", + "ArsonistDouseTime": "Douse Duration", + "CanTerroristSuicideWin": "Can Win By Suicide", + "FireworkerMaxCount": "Fireworker Count", + "FireworkerRadius": "Firework Explosion Radius", + "SniperCanKill": "Sniper can kill with bullets remaining", + "SniperBulletCount": "Ammo", + "SniperPrecisionShooting": "Precise Shooting", + "SniperAimAssist": "Aim Assist", + "SniperAimAssistOneshot": "One shot Assist", + + "PyroDouseCooldown": "Douse cooldown", + "PyroBurnCooldown": "Kill cooldown after killing a doused player", + + "UndertakerFreezeDuration": "Freeze Duration", + + "NameDisplayAddons": "Display Add-Ons next to the role name", + "YourAddon": "Your Addons:", + "NoLimitAddonsNumMax": "Max Add-ons Per Player", + "LoverSpawnChances": "Spawn Chance of Lovers", + "AdditionRolesSpawnRate": "Spawn Chance", + "TorchVision": "Torch Vision", + "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", + "BewilderVision": "Bewilder Vision", + "JesterVision": "Jester Vision", + "LawyerVision": "Lawyer Vision", + "FlashSpeed": "Flash Speed", + "LoverSuicide": "Lovers die together", + "NumberOfLovers": "Number of Lover Pairs (x2 members)", + "LoverKnowRoles": "Lovers know the roles of each other", + "TrapperBlockMoveTime": "Freeze time", + "BecomeTrapperBlockMoveTime": "Freeze time", + "ImpCanBeTrapper": "Impostors can become Beartrap", + "CrewCanBeTrapper": "Crewmates can become Beartrap", + "NeutralCanBeTrapper": "Neutrals can become Beartrap", + "ImpCanBeGravestone": "Impostors can become Gravestone", + "CrewCanBeGravestone": "Crewmates can become Gravestone", + "NeutralCanBeGravestone": "Neutrals can become Gravestone", + "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", + "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", + "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", + "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", + "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", + "EvilTrackerTargetMode": "Can Set Target", + "EvilTrackerTargetMode.Never": "Never", + "EvilTrackerTargetMode.OnceInGame": "Once in game", + "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", + "EvilTrackerTargetMode.Always": "Any time", + "WitchModeSwitchAction": "Switch Action via", + "NBareRed": "Neutral Benign can be red", + "NEareRed": "Neutral Evil can be red", + "NCareRed": "Neutral Chaos can be red", + "NAareRed": "Neutral Apocalypse can be red", + "CrewKillingRed": "Crewmate Killings can be red", + "PsychicCanSeeNum": "Max number of red names", + "PsychicFresh": "New red names every meeting", + "DetectiveCanknowKiller": "Can find the killer's role", + "EveryOneKnowSuperStar": "Everyone knows the Super Star", + "HackLimit": "Ability Use Count", + "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", + "NemesisCanKillNum": "Max number of revenges", + "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", + "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", + "JesterCanUseButton": "Can call emergency meetings", + "VectorVentNumWin": "Number of Vents to win", + "CanCheckCamera": "Can track camera usage", + "Arrogance/Juggernaut___DefaultKillCooldown": "Starting kill cooldown", + "Arrogance/Juggernaut___ReduceKillCooldown": "Reduce kill cooldown by", + "Arrogance/Juggernaut___MinKillCooldown": "Minimum kill cooldown", + "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", + "NotifyGodAlive": "Inform players at meetings that God is still alive", + "TransporterTeleportMax": "Max number of teleports", + "TriggerKill": "Kill", + "TriggerVent": "Vent", + "TriggerDouble": "Double Click", + "TimeManagerIncreaseMeetingTime": "Increase voting time by", + "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", + "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", + "AssignOnlyToCrewmate": "Assign only to Crewmates", + "WorkhorseNumLongTasks": "Additional Long Tasks", + "WorkhorseNumShortTasks": "Additional Short Tasks", + "SnitchCanBeWorkhorse": "Snitch can become Workhorse", + "InnocentCanWinByImp": "If their target was an Impostor then they win with them", + "ImpCanBeSchizophrenic": "Impostors can become Schizophrenic", + "CrewCanBeSchizophrenic": "Crewmates can become Schizophrenic", + "DualVotes": "Duplicate votes", + "ProtectCooldown": "Protect Cooldown", + "ProtectDur": "Protection Duration", + "ProtectVisToImp": "Protect Visible To Impostors", + "VeteranSkillCooldown": "Alert Cooldown", + "VeteranSkillDuration": "Alert Duration", + "BodyguardProtectRadius": "Protect Radius", + "ImpCanBeEgoist": "An Impostor can become Egoist", + "CrewCanBeEgoist": "Crewmates can become Egoist", + "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", + "EgoistCountAsConverted": "Egoist count as converted neutral", + "ImpCanBeSeer": "Impostors can become Seer", + "CrewCanBeSeer": "Crewmates can become Seer", + "NeutralCanBeSeer": "Neutrals can become Seer", + "ImpCanBeGuesser": "Impostors can become Guesser", + "CrewCanBeGuesser": "Crewmates can become Guesser", + "NeutralCanBeGuesser": "Neutrals can become Guesser", + "ImpCanBeWatcher": "Impostors can become Watcher", + "CrewCanBeWatcher": "Crewmates can become Watcher", + "NeutralCanBeWatcher": "Neutrals can become Watcher", + "ImpCanBeBait": "Impostors can become Bait", + "CrewCanBeBait": "Crewmates can become Bait", + "NeutralCanBeBait": "Neutrals can become Bait", + "ImpCanBeRainbow": "Impostors can become Rainbow", + "NeutralCanBeRainbow": "Neutrals can become Rainbow", + "CrewCanBeRainbow": "Crewmates can become Rainbow", + "GuessRainbow": "He seems too obvious, doesn't he?", + "RainbowColorChangeCoolDown": "The cooldown for changing colors", + "RainbowInCamouflage": "Rainbow color changes during Camouflage", + "BaitDelayMin": "Minimum Report Delay", + "BaitDelayMax": "Maximum Report Delay", + "BaitDelayNotify": "Warn the killer about the upcoming self-report", + "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", + "BaitNotification": "Reveal Bait at the first meeting", + "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self report.", + "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if meeting is disabled during comms sabotage", + "DeceiverSkillCooldown": "Ability cooldown", + "DeceiverSkillLimitTimes": "Max number of uses", + "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", + "PursuerSkillCooldown": "Ability cooldown", + "PursuerSkillLimitTimes": "Max number of uses", + "AddictSuicideTimer": "Time Until Suicide", + "GrenadierSkillCooldown": "Grenade Cooldown", + "GrenadierSkillDuration": "Grenade Duration", + "GrenadierCauseVision": "Lowered vision", + "GrenadierCanAffectNeutral": "Can affect Neutrals", + "TicketsPerKill": "Votes Increase Amount Per Kill", + "GangsterRecruitCooldown": "Recruit cooldown", + "GangsterRecruitLimit": "Recruit limit", + "KamikazeMaxMarked": "Max Marked", + "RevolutionistDrawTime": "Tag Duration", + "RevolutionistCooldown": "Tag Cooldown", + "RevolutionistDrawCount": "Amount of Players needed to Tag", + "RevolutionistKillProbability": "Tagged player sacrifice probability", + "RevolutionistVentCountDown": "Time to Vent", + "PelicanKillCooldown": "Eat Cooldown", + "Pelican.TargetCannotBeEaten": "Target cannot be eaten", + "MadSnitchTasks": "Snitch Tasks", + "MedicWhoCanSeeProtect": "Who can see shield", + "MedicKnowShieldBroken": "Who sees kill attempt", + "Medic_SeeMedicAndTarget": "Medic+Shielded", + "Medic_SeeMedic": "Medic", + "Medic_SeeTarget": "Shielded", + "Medic_SeeNoOne": "Nothing", + "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", + "MedicShielDeactivationIsVisible": "Shield deactivation is visible", + "MedicShieldDeactivationIsVisible_DeactivationImmediately": "Immediately", + "MedicShieldDeactivationIsVisible_DeactivationAfterMeeting": "After Meeting", + "MedicShieldDeactivationIsVisible_DeactivationIsVisibleOFF": "OFF", + "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", + "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", + "FortuneTellerSkillLimit": "Max number of ability uses", + "MadmateSpawnMode": "Madmate spawning mode", + "MadmateSpawnMode.Assign": "Assign", + "MadmateSpawnMode.FirstKill": "First Kill", + "MadmateSpawnMode.SelfVote": "Self Vote", + "MadmateCountMode": "Madmates count as", + "MadmateCountMode.None": "Nothing", + "MadmateCountMode.Imp": "Impostors", + "MadmateCountMode.Original": "Original Team", + + "SnatchesWin": "Snatches victory", + "DemonKillCooldown": "Attack Cooldown", + "DemonHealthMax": "Player max health", + "DemonDamage": "Damage ", + "DemonSelfHealthMax": "Demon max health", + "DemonSelfDamage": "Demon damage received", + "LightningConvertTime": "Duration of the transformation to Quantum Ghost", + "LightningKillCooldown": "Lightning Cooldown", + "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", + "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", + "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", + "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", + "WorkaholicCannotWinAtDeath": "Can't win after they died", + "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", + "WorkaholicGiveAdviceAlive": "Advice at first meeting if alive, can win after death, ghost tasks ON", + "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", + "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", + "Jinx/CursedWolf___KillAttacker": "Kill attacker when ability is remaining", + "JinxSpellTimes": "Amount of Jinx Spells", + "CollectorCollectAmount": "Required number of votes", + "GlitchCanVote": "Can vote", + "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", + "MeetingReserved": "Max Bullets reserved for a meeting", + "AccurateCheckMode": "Can know specific role when tasks are not done", + "RandomActiveRoles": "Show random active roles in Fortune Teller hints", + "CamouflageCooldown": "Camouflage Cooldown", + "CamouflageDuration": "Camouflage Duration", + "EraseLimit": "Max Erases", + "EraserHideVote": "Hide Eraser Votes", + "NinjaMarkCooldown": "Mark Cooldown", + "NinjaAssassinateCooldown": "Ass​assinate Cooldown", + "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", + "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", + "JudgeCanTrialNeutralB": "Can trial Neutral Benign", + "JudgeCanTrialNeutralK": "Can trial Neutral Killing", + "JudgeCanTrialNeutralE": "Can trial Neutral Evil", + "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", + "JudgeCanTrialSidekick": "Can trial Sidekick", + "JudgeCanTrialInfected": "Can trial Infected", + "JudgeCanTrialContagious": "Can trial Contagious", + "JudgeTryHideMsg": "Hide Judge's commands", + "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", + "JudgeCanTrialMadmate": "Can trial Madmates", + "JudgeCanTrialCharmed": "Can trial Charmed players", + "JudgeDead": "Sorry, you can't trial after death.", + "JudgeTrialMax": "\nNo more trials left!", + "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", + "Judge_TrialKill": "{0} was judged.", + "Judge_TrialKillTitle": "COURT", + "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Judge_TrialNull": "Please choose a living player for the trial", + "VeteranSkillMaxOfUseage": "Max number of Alerts", + "SwooperCooldown": "Swoop Cooldown", + "SwooperDuration": "Swoop Duration", + "WraithCooldown": "Vanish Cooldown", + "WraithDuration": "Vanish Duration", + "BastionNotify": "A bomb was set off", + "EnteredBombedVent": "That vent was bombed!", + "BastionVentButtonText": "Bomb", + "BombsClearAfterMeeting": "Bombs clear after meetings", + "BastionMaxBombs": "(Initial) Maximum bombs", + "VentBombSuccess": "Bomb has been planted", + "LowLoadMode": "Low Load Mode", + "ShowLobbyCode": "Show lobby code in Discord status", + "BKProtectDuration": "Protection Duration", + "FollowerMaxBetTimes": "Maximum Number of Follows", + "FollowerBetCooldown": "Follow Cooldown", + "FollowerMaxBetCooldown": "Maximum Follow Cooldown", + "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", + "FollowerKnowTargetRole": "Follower knows their target's role", + "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", + "FortuneTellerHideVote": "Hide Fortune Teller's Votes", + "CultistCharmCooldown": "Charm Cooldown", + "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", + "CultistCharmMax": "Maximum Number Of Charm", + "CultistKnowTargetRole": "Know Charmed Player's Role", + "CultistTargetKnowOtherTarget": "Charmed players know each other", + "CultistCanCharmNeutral": "Neutral Roles can be Charmed", + "InfectiousBiteCooldown": "Infect Cooldown", + "KnowTargetRole": "Knows role of target", + "TargetKnowsLawyer": "Target knows their Lawyer", + "InfectiousBiteMax": "Maximum Infections", + "InfectiousKnowTargetRole": "Know infected player's role", + "InfectiousTargetKnowOtherTarget": "Infected players know each other", + "DoubleClickKill": "Double click to kill the target", + + "VirusInfectMax": "Maximum Number Of Spreads", + "VirusKnowTargetRole": "Know Contagious Player's Role", + "VirusTargetKnowOtherTarget": "Contagious players know each other", + "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", + "Virus_ContagiousCountMode": "Contagious players count as", + "Virus_ContagiousCountMode_None": "Nothing", + "Virus_ContagiousCountMode_Virus": "Virus", + "Virus_ContagiousCountMode_Original": "Original Team", + "VirusNoticeTitle": "[ Infected Corpse! ]", + "VirusNoticeMessage": "The body your reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", + "VirusNoticeMessage2": "The body your reported was infected by the Virus! Vote the Virus out this meeting or you will die.", + + "Cultist_CharmedCountMode": "Charmed players count as", + "Cultist_CharmedCountMode_None": "Nothing", + "Cultist_CharmedCountMode_Cultist": "Cultist", + "Cultist_CharmedCountMode_Original": "Original Team", + + "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", + "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", + "JackalResetKillCooldownOn": "Kill Cooldown On Reset", + "JackalCanRecruitSidekick": "Can recruit Sidekick", + "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", + "Jackal_SidekickCountMode": "Sidekicks count as", + "Jackal_SidekickCountMode_None": "Nothing", + "Jackal_SidekickCountMode_Jackal": "Jackal", + "Jackal_SidekickCountMode_Original": "Original Team", + "Jackal_SidekickAssignMode": "Sidekick Assign Mode", + "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", + "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", + "Jackal_SidekickAssignMode_Recruit": "Recruit Only", + "JackalWinWithSidekick": "Jackal can win with Sidekick's team", + "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", + "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", + "JackalCanKillSidekick": "Jackal can kill Sidekick", + + "ImpCanBeNecroview": "Impostors can become Necroview", + "CrewCanBeNecroview": "Crewmates can become Necroview", + "NeutralCanBeNecroview": "Neutrals can become Necroview", + "ImpCanBeInLove": "Impostors can be in love", + "CrewCanBeInLove": "Crewmates can be in love", + "NeutralCanBeInLove": "Neutrals can be in love", + "ImpCanBeOblivious": "Impostors can become Oblivious", + "CrewCanBeOblivious": "Crewmates can become Oblivious", + "NeutralCanBeOblivious": "Neutrals can become Oblivious", + "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", + "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", + "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", + "HexesLookLikeSpells": "Hexes appear as spells", + "HexButtonText": "Hex", + "ObliviousBaitImmune": "Immune to Bait", + "ImpCanBeOnbound": "Impostors can become Onbound", + "CrewCanBeOnbound": "Crewmates can become Onbound", + "NeutralCanBeOnbound": "Neutrals can become Onbound", + + "ImpCanBeRebound": "Impostors can become Rebound", + "CrewCanBeRebound": "Crewmates can become Rebound", + "NeutralCanBeRebound": "Neutrals can become Rebound", + + "CrewCanBeMundane": "Crewmates can become Mundane", + "NeutralCanBeMundane": "Neutrals can become Mundane", + "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", + + "ImpCanBeUnreportable": "Impostors can become Disregarded", + "CrewCanBeUnreportable": "Crewmates can become Disregarded", + "NeutralCanBeUnreportable": "Neutrals can become Disregarded", + "PacifistCooldown": "Ability Cooldown", + "PacifistMaxOfUseage": "Max Number of Ability Uses", + "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", + "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", + "TrackerHideVote": "Hide Tracker Votes", + "TrackerCanGetArrowColor": "Can See Colored Arrows", + + "PresidentAbilityUses": "Max Number of Ability Uses", + "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", + "HidePresidentEndCommand": "Hide President's commands", + "NeutralsSeePresident": "Neutrals can see revealed President", + "MadmatesSeePresident": "Madmates can see revealed President", + "ImpsSeePresident": "Impostors can see revealed President", + "PresidentDead": "Sorry, you can't force end the meeting after death.", + "PresidentEndMax": "No more force end meeting uses left!", + "PresidentRevealMax": "You have already revealed yourself...", + "PresidentRevealed": "[{0}] has chose to reveal themselves as President!", + "GuessPresident": "President has revealed themselves, you can't guess them.", + "PresidentRevealTitle": "PRESIDENT REVEAL", + + "LuckyProbability": "Probability of surviving a kill", + "ImpCanBeLucky": "Impostors can become Lucky", + "CrewCanBeLucky": "Crewmates can become Lucky", + "NeutralCanBeLucky": "Neutrals can become Lucky", + "ImpCanBeFool": "Impostors can become Fool", + "CrewCanBeFool": "Crewmates can become Fool", + "NeutralCanBeFool": "Neutrals can become Fool", + "ImpCanBeDoubleShot": "Impostors can have Double Shot", + "CrewCanBeDoubleShot": "Crewmates can have Double Shot", + "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", + "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", + "DisableReportWhenCamouflageIsActive": "Disable body reporting when сamouflage is active", + "CanUseCommsSabotage": "Can use comms sabotage", + "ModTag": "Moderator♥", + "ApplyModeratorList": "Apply Moderator List", + "VipTag": "VIP★", + "ApplyVipList": "Apply VIP List", + "AllowSayCommand": "Allow moderators to use /say command", + "KickCommandDisabled": "The kick command is currently disabled.", + "KickCommandNoAccess": "You do not have access to the kick command.", + "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reseaon]' to kick a player.\nExample :- /kick 5 not following rules", + "KickCommandKickHost": "You are not permitted to kick the host.", + "KickCommandKickMod": "You are not permitted to kick other moderators.", + "KickCommandKicked": "was kicked from the game by ", + "KickCommandKickedRole": "Their role was", + "BanCommandDisabled": "The ban command is currently disabled.", + "BanCommandNoAccess": "You do not have access to the ban command.", + "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", + "BanCommandBanHost": "You are not permitted to ban the host.", + "BanCommandBanMod": "You are not permitted to ban other moderators.", + "BanCommandBanned": "was banned from the game by ", + "BanCommandBannedRole": "Their role was", + "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", + "ColorCommandDisabled": "The modcolor command is currently disabled.", + "ColorCommandNoAccess": "You do not have access to the modcolor command.", + "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff", + "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", + "VipColorCommandDisabled": "The vipcolor command is currently disabled.", + "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", + "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff", + "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", + "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change color of your tag.\nExample :- /tagcolor ff00ff", + "midCommandDisabled": "The mid command is currently disabled.", + "midCommandNoAccess": "You do not have access to the mid command.", + "DisableVoteBan": "Disable VoteKick System", + "WarnCommandDisabled": "The warn command is currently disabled.", + "WarnCommandNoAccess": "You do not have access to the warn command.", + "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", + "WarnCommandWarnHost": "You are not permitted to warn the host.", + "WarnCommandWarnMod": "You are not permitted to warn other moderators.", + "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", + "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", + "SayCommandDisabled": "The say command is currently disabled.", + "MessageFromModerator": "MODERATOR", + "DeathReason.Kill": "Kill", + "DeathReason.Vote": "Ejected", + "DeathReason.Suicide": "Suicide", + "DeathReason.Spell": "Spelled", + "DeathReason.Cursed": "Cursed", + "DeathReason.Hex": "Hexed", + "DeathReason.Bite": "Bitten", + "DeathReason.Poison": "Poisoned", + "DeathReason.Gambled": "Guessed", + "DeathReason.FollowingSuicide": "Heartbroken", + "DeathReason.Bombed": "Exploded", + "DeathReason.Misfire": "Misfire", + "DeathReason.Torched": "Burned", + "DeathReason.Sniped": "Sniped", + "DeathReason.Execution": "Executed", + "DeathReason.Disconnected": "Disconnected", + "DeathReason.Fall": "Fall", + "DeathReason.Revenge": "Revenge", + "DeathReason.Eaten": "Eaten", + "DeathReason.Sacrifice": "Victim", + "DeathReason.Quantization": "Quantization", + "DeathReason.Overtired": "Overtired", + "DeathReason.Ashamed": "Ashamed", + "DeathReason.PissedOff": "Destroyed", + "DeathReason.Dismembered": "Dismembered", + "DeathReason.LossOfHead": "Strangled", + "DeathReason.Trialed": "Judged", + "DeathReason.Infected": "Infected", + "DeathReason.Jinx": "Jinxed", + "DeathReason.Pirate": "Plundered", + "DeathReason.Shrouded": "Shrouded", + "DeathReason.etc": "Other", + "DeathReason.Mauled": "Mauled", + "DeathReason.Hack": "Hacked", + "DeathReason.Curse": "Cursed", + "DeathReason.Drained": "Drained", + "DeathReason.Shattered": "Shattered", + "DeathReason.Trap": "Trapped", + "DeathReason.Targeted": "Targeted", + "DeathReason.Retribution": "Retribution", + "DeathReason.Slice": "Sliced", + "DeathReason.BloodLet": "Bleed", + "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", + "Alive": "Alive", + "Win": " Wins!", + + "Last-": "Last ", + "Madmate-": "Madmate ", + "Recruit-": "Recruit ", + "Charmed-": "Charmed ", + "Soulless-": "Soulless ", + "Infected-": "Infected ", + "Contagious-": "Contagious ", + "Admired-": "Admired ", + + "DeputyHandcuffCooldown": "Handcuff Cooldown", + "DeputyHandcuffMax": "Maximum Handcuffs", + "DeputyHandcuffedPlayer": "Handcuffed target", + "HandcuffedByDeputy": "You were handcuffed!", + "DeputyInvalidTarget": "Target cannot be handcuffed", + "DeputyHandcuffText": "Handcuff", + "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", + + "RejectShapeshift.AbilityWasUsed": "Ability was used", + "ShowShapeshiftAnimations": "Show Shapeshift animations", + + "EscapisMtarkedPosition": "You marked self position", + + "InvestigateCooldown": "Investigate Cooldown", + "InvestigateMax": "Maximum Investigations", + "InvestigateRoundMax": "Maximum Investigations in one round", + + "Color.Red": "Red", + "Color.Green": "Green", + "Color.Gray": "Gray", + "InvestigatorInvestigatedPlayer": "Player Investigated", + "InvestigatorInvalidTarget": "Can not investigate", + "InvestigatorButtonText": "Check", + + "Investigator.Suspicion": "Suspicion", + "Investigator.Role": "Role", + "SabotageCooldownControl": "Sabotage Cooldown Control", + "SabotageCooldown": "Sabotage Cooldown", + "SabotageTimeControl": "Sabotage Duration Control", + "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", + "SkeldO2TimeLimit": "The Skeld O2 Time Limit", + "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", + "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", + "PolusReactorTimeLimit": "Polus Reactor Time Limit", + "AirshipReactorTimeLimit": "Airship Reactor Time Limit", + "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", + "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", + "CommandList": "★ Command list:", + "Command.now": "→ Display active Settings", + "Command.roles": "[RoleName] → Display Role description", + "Command.myrole": "→ Displays a description of your role", + "Command.lastresult": "→ Display match results", + "Command.winner": "→ Display winners", + "CommandOtherList": "● Other commands:", + "Command.color": "[Color] → Change your color", + "Command.rename": "[Name] → Change Host Name", + "Command.quit": "→ I don't want to enter this lobby anymore", + "CommandHostList": "▲ Host Commands:", + "Command.say": "[Content] → Send message as Host", + "Command.mw": "[Seconds] → Set the message waiting duration", + "Command.solvecover": "→ Fix an issue where role names overlap the messages", + "Command.kill": "[Player ID] → Kill assigned player", + "Command.exe": "[Player ID] → Eject assigned player", + "Command.level": "[Level] → Change your in-game level", + "Command.idlist": "→ Display a list of player IDs", + "Command.qq": "→ Lobby will be posted on QQ website (China only)", + "Command.dump": "→ Output Log to Desktop", + "Command.death": "→ Display info on how you died", + "Command.icons": "Icon Meanings\n† - This player was spelled by a Witch and will die if the Witch is not killed by the end of the meeting\n乂 - This player was hexed by a Hex Master and will die if the Hex Master is not killed by the end of the meeting\n❖ - This player was hexed by an Occultist and will die if the Occultist is not killed by the end of the meeting\n◈ - This player was shrouded by a Shroud and will die if the Shroud is not killed by the end of the meeting\n⦿ - This player is being dueled by a Pirate\n♥ - This player is a Lover\n⚠ - This player is a Snitch who has finished their tasks", + "Command.iconinfo": "→ Display info on in-meeting icons", + "Command.iconhelp": "→ Display info on in-meeting icons to everyone", + "Remaining.ImpostorCount": "Impostors left: ", + "Remaining.NeutralCount": "Neutral Killers left: ", + "EnableKillerLeftCommand": "Enable use of /kcount command", + "SeeEjectedRolesInMeeting": "See ejected roles in meetings", + + "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", + "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", + "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", + "NemesisKillDead": "Choose a living player to take revenge", + "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", + "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", + + "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period of time!", + "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", + "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", + "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", + "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer, this is most likely suicide.", + "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", + "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", + "GuessDead": "Sorry, but it's impossible to guess roles after your death", + "GuessSuperStar": "The Super Star can't be guessed.... you thought it would be that easy, right?", + "GuessNotifiedBait": "Bait can't be guessed because it was announced, you thought it would be that easy, right?", + "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", + "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", + "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", + "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", + "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", + "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", + "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", + "GuessKill": "{0} was guessed", + "GuessNull": "Please select an ID of a living player to guess their role", + "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "GGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", + "EGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", + "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all of their tasks are done? Nice try.... You're not getting out of this that easily.", + "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot so you get another chance!", + "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", + "GuessDisabled": "Sorry, the host restricted guessing for your role.", + "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", + "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", + "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", + "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", + "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", + "GuessShielded": "Sorry, you can't guess the player who is shielded by Medic", + "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", + "MimicDeadMsg": "Mimic's hint: ", + "FortuneTellerCheck": "According to your fortune...", + "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", + "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", + "FortuneTellerCheckReachLimit": "You've run out of fortunes.", + "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", + "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", + "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", + "EraserEraseSelf": "Unfortunately, you can't erase yourself.... Wait, why would you do that in the first place?!", + "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", + "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", + + "MediumContactLimit": "Max number of contacts (ability uses)", + "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", + "MediumTitle": "MEDIUM", + "MediumHelp": "/ms yes to agree\n/ms no to disagree", + "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", + "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", + "MediumDone": "You successfully responded to the Medium.", + "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", + "MediumNotifySelf": "You established contact with {0}, please ask questions to them and wait for them to respond.\n\nRemaining ability uses: {1}", + "MediumKnowPlayerDead": "Someone died somewhere", + + "ByBard": "by Bard", + "ByBardGetFailed": "oops, I seem to be out of inspiration.", + "GangsterSuccessfullyRecruited": "You successfully recruited a player", + "GangsterRecruitmentFailure": "Target cannot be recruited", + "BeRecruitedByGangster": "You have been recruited by the Gangster", + "KamikazeHostage": "Can't hold target hostage", + "VeteranOnGuard": "Ability in use", + "VeteranOffGuard": "Ability expired, {0} uses remain", + "VeteranMaxUsage": "Ability use limit reached", + "GrenadierSkillInUse": "Ability in use", + "GrenadierSkillStop": "Ability expired", + "TicketsStealerGetTicket": "You've got {0} votes", + "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", + "SpeedBoosterTaskDone": "Your speed is {0} now", + "SpeedBoosterSpeedLimit": "You reached your maximum speed (3x)", + "CleanerCleanBody": "The body has been cleaned", + "QuickShooterStoraging": "Bullets stored successfully", + "VampireTargetDead": "Target died", + "PoisonerTargetDead": "Target died", + "BloodlustAdded": "Your bloodlust is now active!", + "WarlockNoTarget": "Manipulation failed due to no target", + "WarlockNoTargetYet": "You haven't mark a target.", + "WarlockTargetDead": "Manipulation failed due to target dead", + "WarlockControlKill": "Target died", + "OnCelebrityDead": "Warning: Celebrity death!", + "OnCyberDead": "Warning: Cyber died!", + "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", + "TeleportedByTransporter": "Swapping places with: {0}", + "ErrorTeleport": "Teleport failed", + "LostRoleByEraser": "You lost your role because of the Eraser", + "KilledByScavenger": "You were killed by the Scavenger and thus teleported off-map", + "SnitchDoneTasks": "Call a meeting to find the impostors", + "SwooperCanVent": "Vent to turn invisible", + "SwooperInvisState": "You're invisible", + "SwooperInvisStateOut": "You're now visible", + "SwooperInvisInCooldown": "Swoop cooldown isn't up yet, swooping failed", + "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", + "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", + "WraithCanVent": "Vent to turn invisible", + "WraithInvisState": "You are invisible", + "WraithInvisStateOut": "You are visible again", + "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", + "WraithInvisStateCountdown": "Invisibility will expire in {0}s", + "WraithInvisCooldownRemain": "{0}s left in invisibility", + "BKInProtect": "Currently immortal", + "BKProtectOut": "Shield expired", + "BKSkillTimeRemain": "You're immune for {0} seconds", + "BKSkillNotice": "Kill a player to enter immune status", + "BKOffsetKill": "Someone tried killing you", + "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", + "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", + "FollowerBetPlayer": "You're now following your target", + "FollowerBetOnYou": "The Follower is now following you", + "CultistCharmedPlayer": "You successfully charmed a player", + "CharmedByCultist": "You have been charmed by the Cultist", + "CultistInvalidTarget": "Target cannot be charmed", + "KillBaitNotify": "You'll self-report in {0}s", + "InfectiousInvalidTarget": "Target cannot be infected", + "BittenByInfectious": "You were infected by the Infectious!", + "InfectiousBittenPlayer": "You successfully infected a player", + "GuessNotAllowed": "Sorry, your role does not have access to guessing.", + "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", + "GuessPhantom": "You can't guess a Phantom, that allows them to win!", + "PacifistOnGuard": "Ability used, {0} uses remain", + "PacifistMaxUsage": "Ability use limit reached", + "PacifistSkillNotify": "Pacifist reset your kill cooldown", + "BeRecruitedByJackal": "You have been recruited by the Jackal", + "CoronerTrackRecorded": "Track recorded", + "CoronerNoTrack": "Nothing to track", + "CoronerIsTrackingYou": "The Coroner is tracking you!", + "TrackerLastRoomMessage": "The evaluation of your tracking showed that the last room in which your target was located was:[{0}]", + "MerchantAddonDelivered": "Add-on sold", + "MerchantAddonSell": "The Merchant sold you a new Add-on", + "MerchantAddonSellFail": "Could not sell an Add-on", + "BribedByMerchant": "The Merchant bribed you, you can't kill him", + "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", + "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", + "TrapTrapsterBody": "Trap Trapster's body", + "TrapConsecutiveBodies": "Trap consecutive bodies", + "HauntedByEvilSpirit": "Haunted by an Evil Spirit", + "MonarchKnightCooldown": "Knight Cooldown", + "MonarchKnightMax": "Maximum Knights", + "MonarchKnightedPlayer": "You successfully knighted a player!", + "KnightedByMonarch": "You have been knighted by a Monarch!", + "MonarchInvalidTarget": "Target cannot be knighted", + "GhostTransformTitle": "Your Role Has Transformed!", + "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", + "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", + "OverseerRevealCooldown": "Reveal Cooldown", + "OverseerRevealTime": "Reveal Time", + "OverseerVision": "Overseer Vision", + "MerchantMaxSell": "Max number of Add-ons to sell", + "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", + "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", + "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", + "MerchantTargetCrew": "Can sell to Crewmates", + "MerchantTargetImpostor": "Can sell to Impostors", + + "MerchantTargetNeutral": "Can sell to Neutrals", + "MerchantSellHelpful": "Can sell Helpful Add-ons", + "MerchantSellHarmful": "Can sell Harmful Add-ons", + "MerchantSellMixed": "Can sell Mixed Add-ons", + "MerchantSellExperimental": "Can sell experimental Add-ons", + "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", + "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", + "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", + + "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", + "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", + "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", + "SpiritcallerProtectTime": "Evil Spirit ability protect time", + "SpiritcallerCauseVision": "Evil Spirit ability caused vision", + "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", + "Message.SetToSeconds": "Set to [{0}] seconds.", + "Message.MessageWaitHelp": "Specify the first argument in seconds.", + "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", + "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", + "Message.SyncButtonLeft": "There are {0} more emergency buttons left", + "Message.Executed": "{0} was executed", + "Message.HideGameSettings": "Game settings have been hidden by the host.", + "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", + "Message.NoDescription": "No description", + "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", + "Message.BannedByBanList": "{0} was banned because they were banned in the past.", + "Message.BannedByEACList": "{0} has been banned because he is in EAC list of Banned people.", + "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", + "Message.DumpcmdUsed": "{0} used /dump command.", + "Message.KickedByNoFriendCode": "{0} was kicked because their friend code does not exist.", + "Message.TempBannedByNoFriendCode": "{0} was temporary banned because their friend code does not exist.", + "Message.AddedPlayerToBanList": "Added {0} to the ban list", + "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", + "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", + "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", + "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", + "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", + "Message.NoticeByEAC": "[{0}]Detected:{1}", + "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", + "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", + "Message.KickedByWhiteList": "{0} kicked because friendcode not found in WhiteList.txt", + "Message.SetLevel": "Your game level is set to: {0}", + "Message.SetColor": "Your color is set to: {0}", + "Message.SetName": "Your name is set to: {0}", + "Message.AllowLevelRange": "The game level can be set in the range: 0-100", + "Message.AllowNameLength": "Nickname can be set length: 1-10", + "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", + "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", + "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", + "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", + "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", + "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", + "Message.HostLeftGameInGame": "★Warning★ Host left the game, in next time game wouldn't start normally. Please, exit the lobby or wait until new Host opens a lobby.", + "Message.HostLeftGameInLobby": "★Warning★ Host left the game, in next time game wouldn't start normally. If new host has TOHE, you need to re-enter the lobby to play normally.", + "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, just start a game and end it immediately to reset the lobby!", + "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please, exit the lobby or wait until new Host opens a lobby.", + "Message.LobbyShared": "The lobby has successfully been shared!", + "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", + "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", + "Message.YTPlanSelected": "In the next game, your role will be {0}", + "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", + "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. In case the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", + "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", + "Message.MaxPlayers": "Maximum players set to ", + "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little bit about ghost roles...\n\nGhost roles drastically impact the game, so not recommended for smaller lobbies, if you're unfamiliar.\n\nSpawning:\nGhost-roles only spawn after death, the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g sheriff), your tasks as a ghost-role aren't needed for task-win", + + "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", + "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", + "WarningTitle": "Warning!", + "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", + "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", + + "AntiBlackoutProtectionTitle": "Anti Blackout", + "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normaly", + "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in last meeting.\n", + "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", + + "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE installed.", + "Warning.NoModHost": "TOHE is not installed on the host", + "Warning.MismatchedVersion": "{0} has a different version of {1}", + "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", + "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", + "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", + "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", + "Error.InvalidColor": "Error: Only default colors are available", + "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors, otherwise it will result in a serious error", + "ErrorLevel1": "Bugs may occur.", + "ErrorLevel2": "This may be a bug.", + "ErrorLevel3": "This version shouldn't have been released.", + "TerminateCommand": "Abort Command", + "ERR-000-000-0": "No Error", + "ERR-000-900-0": "Test Error Lv.0", + "ERR-000-910-1": "Test Error Lv.1", + "ERR-000-920-2": "Test Error Lv.2", + "ERR-000-930-3": "Test Error Lv.3", + "ERR-000-804-1": "TownofHost-Enhanced does not support the Vanilla HnS, so unloaded.", + "ERR-001-000-3": "Main dictionary has duplicated keys.", + "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", + "DefaultSystemMessageTitle": "SYSTEM MESSAGE", + "MessageFromTheHost": "HOST MESSAGE", + "MessageFromEAC": "EAC", + "DetectiveNoticeTitle": "INVESTIGATION", + "SleuthNoticeTitle": "SLEUTH", + "GuessKillTitle": "GUESSING INFO", + "CelebrityNewsTitle": "CELEBRITY", + "CyberNewsTitle": "CYBER", + "GodAliveTitle": "GOD ", + "WorkaholicAliveTitle": "WORKAHOLIC", + "BaitAliveTitle": "BAIT", + "MessageFromKPD": "KARPED1EM ", + "MessageFromSponsor": "SPONSOR MESSAGE ", + "MessageFromDev": "DEVELOPER MESSAGE ", + "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", + "MimicMsgTitle": "MIMIC", + "EraserEraseMsgTitle": "ERASER", + "MorticianCheckTitle": "CORPSE EXAMINATION", + "NemesisRevengeTitle": "NEMESIS", + "RetributionistRevengeTitle": "RETRIBUTIONIST", + "TabGroup.SystemSettings": "System Settings", + "TabGroup.GameSettings": "Game Settings", + "TabGroup.CrewmateRoles": "Crewmate Roles", + "TabGroup.NeutralRoles": "Neutral Roles", + "TabGroup.ImpostorRoles": "Impostor Roles", + "TabGroup.Addons": "Add-Ons", + "TabGroup.OtherRoles": "Experimental Roles (Not recommended)", + "TabGroup.TaskSettings": "Game Modifiers", + "OtherRoles.CrewmateRoles": "★ Crewmate Roles", + "OtherRoles.NeutralRoles": "★ Neutral Roles", + "OtherRoles.ImpostorRoles": "★ Impostor Roles", + "OtherRoles.Addons": "★ Add-Ons", + "ActiveRolesList": "Active Roles List", + "ForExample": "Example Use", + "updateButton": "Update", + "updatePleaseWait": "Please Wait...", + "updateManually": "Update failed.\nPlease Update Manually.", + "updateRestart": "Update Finished!\nPlease restart the game.", + "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease update.", + "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", + "UnsupportedVersion": "Unsupported Among Us version.\nPlease update Among Us", + "DisabledByProgram": "Public rooms have been disabled by the program", + "EnterVentToWin": "Enter Vent to Win!!", + "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", + "FireworkerPutPhase": "{0} Fireworker Left", + "FireworkerWaitPhase": "Wait for it...", + "FireworkerReadyFirePhase": "Fire!", + "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", + "On": "ON", + "Off": "OFF", + "ColoredOn": "ON", + "ColoredOff": "OFF", + "CurrentActiveSettingsHelp": "Current Active Settings Help", + "WitchCurrentMode": "Current Mode", + "WitchModeKill": "Kill", + "WitchModeSpell": "Spell", + "HexMasterModeHex": "Hex", + "HexMasterModeKill": "Kill", + "PoisonerPoisonButtonText": "Poison", + "WitchModeDouble": "Double Click = Kill, Single Click = Spell", + "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", + "BountyCurrentTarget": "Current Target", + "Roles": "Roles", + "Settings": "Settings", + "Addons": "Add-Ons", + "LastResult": "★ Match Results", + "LastEndReason": "★ End Reason", + "KillLog": "Kill Log", + "Maximum": "Max", + "RoleRate": "ON", + "RoleOn": "ALWAYS", + "RoleOff": "OFF", + "Chance0": "0%", + "Chance5": "5%", + "Chance10": "10%", + "Chance15": "15%", + "Chance20": "20%", + "Chance25": "25%", + "Chance30": "30%", + "Chance35": "35%", + "Chance40": "40%", + "Chance45": "45%", + "Chance50": "50%", + "Chance55": "55%", + "Chance60": "60%", + "Chance65": "65%", + "Chance70": "70%", + "Chance75": "75%", + "Chance80": "80%", + "Chance85": "85%", + "Chance90": "90%", + "Chance95": "95%", + "Chance100": "100%", + "Preset": "Preset", + "Preset_1": "Preset 1", + "Preset_2": "Preset 2", + "Preset_3": "Preset 3", + "Preset_4": "Preset 4", + "Preset_5": "Preset 5", + "Standard": "Standard", + "GameMode": "Game Mode", + "PressTabToNextPage": "Press Tab or Number for Next Page...", + "RoleSummaryText": "Role Summary:", + "doOverride": "Override %role%'s Tasks", + "assignCommonTasks": "%role% has Common Tasks", + "roleLongTasksNum": "Amount of Long Tasks for %role%", + "roleShortTasksNum": "Amount of Short Tasks for %role%", + "Format.Players": "{0}", + "Format.Seconds": "{0}s", + "Format.Percent": "{0}%", + "Format.Times": "{0}", + "Format.Multiplier": "{0}x", + "Format.Votes": "{0}", + "Format.Pieces": "{0}", + "Format.Health": "{0}", + "Format.Level": "{0}", + "KillButtonText": "Kill", + "ReportButtonText": "Report", + "VentButtonText": "Vent", + "SabotageButtonText": "Sabotage", + "SniperSnipeButtonText": "Snipe", + "FireworkerExplosionButtonText": "Detonate", + "FireworkerInstallAtionButtonText": "Install", + "MercenarySuicideButtonText": "Suicide Timer", + "WarlockCurseButtonText": "Curse", + "NinjaShapeshiftText": "Kill", + "NinjaMarkButtonText": "Mark", + "WitchSpellButtonText": "Spell", + "VampireBiteButtonText": "Bite", + "MinerTeleButtonText": "Warp", + "ArsonistDouseButtonText": "Douse", + "PuppeteerOperateButtonText": "Manipulate", + "WarlockShapeshiftButtonText": "Spell", + "BountyHunterChangeButtonText": "Swap", + "EvilTrackerChangeButtonText": "Track", + "InnocentButtonText": "Frame", + "PelicanButtonText": "Eat", + "DeceiverButtonText": "Cheat", + "PursuerButtonText": "Trick", + "GangsterButtonText": "Recruit", + "RevolutionistDrawButtonText": "Win over", + "HaterButtonText": "Hatred", + "MedicalerButtonText": "Protect", + "DemonButtonText": "Attack", + "SoulCatcherButtonText": "Teleport", + "LightningButtonText": "Evaporate", + "ProvocateurButtonText": "Greet", + "ButcherButtonText": "Dismember", + "BomberShapeshiftText": "Explode", + "QuickShooterShapeshiftText": "Keep", + "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", + "CamouflagerShapeshiftTextAfterDisguise": "Duration", + "AnonymousShapeshiftText": "Hack", + "DefaultShapeshiftText": "Shift", + "CleanerReportButtonText": "Clean", + "SwooperVentButtonText": "Swoop", + "SwooperRevertVentButtonText": "Expose", + "WraithVentButtonText": "Vanish", + "WraithRevertVentButtonText": "Expose", + "VectorVentButtonText": "Hop", + "VeteranVentButtonText": "Alert", + "GrenadierVentButtonText": "Flash", + "MayorVentButtonText": "Button", + "SheriffKillButtonText": "Shoot", + "UndertakerButtonText": "Mark", + "ArsonistVentButtonText": "Ignite", + "RevolutionistVentButtonText": "Revolution", + "FollowerKillButtonText": "Follow", + "PacifistVentButtonText": "Reset", + "CultistKillButtonText": "Charm", + "InfectiousKillButtonText": "Infect", + "MonarchKillButtonText": "Knight", + "OverseerKillButtonText": "Reveal", + "DisabledBySettings": "Disabled by Settings", + "Disabled": "Disabled", + "FailToTrack": "Failed To Track", + "KillCount": "Kills: {0}", + "CantUse.lastroles": "Unable to use /lastroles during a game.", + "CantUse.killlog": "Unable to use /killlog during a game.", + "CantUse.lastresult": "Unable to use /lastresult during a game.", + "IllegalColor": "Please enter the correct color", + "DisableUseCommand": "The Host's settings do not allow this command to be used.", + "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", + "PlayerIdList": "List of player IDs: ", + "CancelStartCountDown": "The starting countdown was cancelled", + "RestTOHESetting": "TOHE settings have been restored to default", + "FPSSetTo": "FPS Set To: {0}", + "HostKillSelfByCommand": "The lobby Host decided to commit suicide", + "SyncCustomSettingsRPC": "Synchronized RPC", + "Mode": "Mode", + "Target": "Target", + "PlayerInfo": "Player Info", + "NoInfoExists": "No Info Exists", + "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", + "PlayerLeftByError": "Game will auto-end to prevent black screens.", + "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", + "KickBecauseLowLevel": "{0} was kicked because their level was too low", + "TempBannedBecauseLowLevel": "{0} was temporary banned because their level was too low", + "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", + + "FFADisplayScore": "Ranking: {0} Score: {1}", + "FFATimeRemain": "Time Remaining: {0} second(s)", + + "GameOver": "Game Over", + "TOHEOptions": "TOHE Options", + "Cancel": "Cancel", + "Back": "Back", + "Yes": "Yes", + "No": "No", + "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", + "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, game will end to prevent black screen.", + "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred, to prevent black screen, turn off [{1}] in settings.", + "AntiBlackOutNotifyInLobby": "An unknown error occurred, to prevent black screen. Sadly, this error exists in all Town of Host versions. Automatic ending game is required, or the game will not start.", + "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent black screen.", + "AntiBlackOutRequestHostToForceEnd": "You were the reason of the black screen, you are asking the host to stop the game...", + "AntiBlackOutHostRejectForceEnd": "You were the reason of the black screen, and the host is not going to end the game, we will disconnect you soon.", + "NextPage": "Next Page", + "PreviousPage": "Previous Page", + "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", + "PressF1ShowMainRoleDes": "Press F1: Show Role Description", + "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", + "FakeTask": "Fake Tasks:", + "PVP.ATK": "Attack", + "PVP.DF": "Defend", + "PVP.RCO": "Recover", + "SettingsAreLoading": "Loading\nsettings...", + "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", + "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", + "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", + "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", + "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", + "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", + "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", + "IsGood": "{0} was a good guy", + "BelongTo": "{0} belongs to {1}", + "PlayerIsRole": "{0} was The {1}", + "PlayerExiled": "{0} was ejected", + "NoImpRemain": "0 Impostors remain", + "OneImpRemain": "1 Impostor remains", + "TwoImpRemain": "2 Impostors remain", + "ThreeImpRemain": "3 Impostors remain", + "ImpRemain": "{0} Impostors remaining", + "NeutralRemain": "\n{0} Neutral Killers remain", + "OneNeutralRemain": "\n{0} Neutral Killer remains", + "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", + "GameOverReason.HumansByTask": "The Crewmates completed all tasks", + "GameOverReason.HumansDisconnect": "Crewmates disconnected", + "GameOverReason.ImpostorByVote": "The Crewmates were ejected", + "GameOverReason.ImpostorByKill": "The Impostors killed everyone", + "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", + "GameOverReason.ImpostorDisconnect": "Impostors disconnected", + "FortuneTellerCheck.Honest": "Looks like [{0}] is being honest.", + "FortuneTellerCheck.Impulse": "Looks like [{0}] is suppressing some kind of impulse.", + "FortuneTellerCheck.Weirdo": "Looks like [{0}] is mixed in crowd.", + "FortuneTellerCheck.Blockbuster": "Looks like [{0}] got big desires to change something.", + "FortuneTellerCheck.Strong": "Looks like [{0}] has a strong power but is dim in society.", + "FortuneTellerCheck.Incomprehensible": "Looks like [{0}] is being misunderstood.", + "FortuneTellerCheck.Enthusiasm": "Looks like [{0}] speaks with everyone very enthusiastic.", + "FortuneTellerCheck.Disturbed": "Looks like [{0}] is disturbed by something.", + "FortuneTellerCheck.None": "Looks like [{0}] has just an ordinary life.", + "FortuneTellerCheck.Glitch": "TV2gPZ4wUJWYr{0}05gA6T5PKzBG17", + "FortuneTellerCheck.HideMsg": "Looks like [{0}] hides secrets.", + "FortuneTellerCheck.Love": "♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥", + "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", + "DevAndSpnTitle": "TOHE family", + "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", + "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", + "SunnyboyChance": "Sunnyboy Chance", + "BardChance": "Bard Chance", + "VampiressChance": "Vampiress Chance", + "NukerChance": "Nuker Chance", + "SkeldChance": "Chance that the map is The Skeld", + "MiraChance": "Chance that the map is MIRA HQ", + "PolusChance": "Chance that the map is Polus", + "DleksChance": "Chance that the map is dlekS ehT", + "AirshipChance": "Chance that the map is Airship", + "FungleChance": "Chance that the map is The Fungle", + "UseMoreRandomMapSelection": "Use a more random map selection", + "CamouflageMode.Default": "Default", + "CamouflageMode.Host": "Host", + "CamouflageMode.Random": "Random", + "CamouflageMode.OnlyRandomColor": "Only Random Color", + "CamouflageMode.Karpe": "KARPED1EM", + "CamouflageMode.Lauryn": "Lauryn", + "CamouflageMode.Moe": "Moe", + "CamouflageMode.Pyro": "Pyro", + "CamouflageMode.ryuk": "ryuk", + "CamouflageMode.Gurge44": "Gurge44", + "CamouflageMode.TommyXL": "TommyXL", + "DeathCmd.HeyPlayer": "Hey ", + "DeathCmd.YouAreRole": ", looks like you're the ", + "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", + "DeathCmd.KillerName": "You were killed by ", + "DeathCmd.KillerRole": "Their role is ", + "DeathCmd.DeathReason": "Your cause of death was ", + "DeathCmd.YourName": "You are ", + "DeathCmd.YourRole": "Your role is ", + "DeathCmd.Ejected": "You were ejected during a meeting", + "DeathCmd.Misfired": "You misfired.", + "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", + "DeathCmd.Lovers": "Your lover had died.", + + "RpsCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", + "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", + "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", + "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I just have the world's worst luck algorithm.", + + "CoinFlipCommandInfo": "This Command can only be used when in lobby or after you die.", + "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", + + "GNoCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", + "GNoLost": "Oh, you were so close! Just one more guess, and you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", + "GNoLow": "Oh, you're really nailing this! It's so low, I almost need a shovel to dig it up!\nYou have {0} guesses left!", + "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high, I need a telescope to see it from here! \nYou have {0} guesses left!", + "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", + + "RandCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", + "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", + + "ChanceToMiss": "Chance to miss a kill", + + "SoulCollectorPointsToWin": "Required number of souls", + "SoulCollectorTarget": "You have predicted the death of {0}", + "SoulCollectorTitle": "SOUL COLLECTOR", + "CollectOwnSoulOpt": "Can collect their own soul", + "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", + "SoulCollectorToDeath": "You have become Death!!!", + "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", + "CallMeetingIfDeath": "Call a meeting immediately after Death transforms", + "GetPassiveSouls": "Gain a passive soul every round", + "PassiveSoulGained": "You have gained a passive soul from the underworld.", + + "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", + "BakerToFamine": "You have become Famine!!!", + "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", + "BakerAlreadyBreaded": "That player already has bread!", + "BakerBreadUsedAlready": "You've already given a player bread this round!", + "BakerBreaded": "Player given bread", + "BakerBreadNeededToTransform": "Required number of bread to become Famine", + "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", + "BakerKillButtonText": "Bread", + + "ChronomancerKillCooldown": "Time to fully charge the kill button", + + "ShamanButtonText": "Voodoo", + "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", + "VoodooCooldown": "Voodoo Cooldown", + + "AdminWarning": "Admin Table in use!", + "VitalsWarning": "Vitals in use!", + "DoorlogWarning": "Doorlogs in use!", + "CameraWarning": "Cameras in use!", + "MinWaitAutoStart": "Minutes to wait before auto-starting", + "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", + "PlayerAutoStart": "Minimum Player Threshold to auto-start", + "AutoStartTimer": "Initial countdown for auto-starting", + "AutoPlayAgainCountdown": "Delay before re-entering lobby", + "AutoPlayAgain": "Auto Play Again", + "AutoRehost": "Auto Re-Host on Bad Disconnect", + "CountdownText": "Rejoining lobby in {0}s", + "TimeMasterSkillDuration": "Time Shield Duration", + "TimeMasterSkillCooldown": "Time Shield Cooldown", + "TimeMasterOnGuard": "Time Shield is active!", + "TimeMasterSkillStop": "Time Shield has ended!", + "TimeMasterVentButtonText": "Time Shield", + "BodyCannotBeReported": "Body could not be reported", + "BurstKillDelay": "Burst Kill Delay", + "BurstNotify": "That was a Burst! Get in a vent or die.", + "ImpCanBeBurst": "Impostors can become Burst", + "CrewCanBeBurst": "Crewmates can become Burst", + "NeutralCanBeBurst": "Neutrals can become Burst", + "BurstFailed": "Burst failed to bomb you", + "ShroudButtonText": "Shroud", + "ShroudCooldown": "Shroud Cooldown", + "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", + "LudopathRandomKillCD": "Maximum kill cooldown", + "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", + "GodfatherTargetCountMode": "Killer turns into", + "GodfatherCount_Refugee": "Refugee", + "GodfatherCount_Madmate": "Madmate", + "MissChance": "Chance To Miss", + "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", + "HawkMissed": "Missed!", + "HawkCanKillNum": "Max Slices", + "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", + "BloodMoonCanKillNum": "Max BloodLettings", + "BloodMoonTimeTilDie": "Time Until Death", + "DeathTimer": "Death In: {DeathTimer}s", + "BerserkerKillCooldown": "Berserker kill cooldown", + "BerserkerMax": "Max level that Berserker can reach", + "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", + "WarHasImpostorVision": "War Has Impostor Vision", + "BerserkerCanVent": "Berserker Can Vent", + "WarCanVent": "War Can Vent", + "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", + "BerserkerOneKillCooldown": "Kill cooldown after unlocking", + "BerserkerTwoCanScavenger": "Unlock scavenged kills", + "BerserkerThreeCanBomber": "Unlock bombed kills", + "BerserkerFourCanNotKill": "Become War", + "BerserkerMaxReached": "Maximum level reached!", + "BerserkerLevelChanged": "Increased level to {0}", + "BerserkerLevelRequirement": "Level requirement for unlock", + "KilledByBerserker": "Killed by Berserker", + "BerserkerToWar": "You have become War!!!", + "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", + "WarKillCooldown": "War kill cooldown", + + "ImpCanBeUnlucky": "Impostors can become Unlucky", + "CrewCanBeUnlucky": "Crewmates can become Unlucky", + "NeutralCanBeUnlucky": "Neutrals can become Unlucky", + "BlackmailerSkillCooldown": "Blackmail Cooldown", + "BlackmailerMax": "Maximum times blackmailed players may speak", + "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", + "BlackmaileKillTitle": "BLACKMAILER", + "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", + "UnluckyKillSuicideChance": "Chance to suicide from killing", + "UnluckyVentSuicideChance": "Chance to suicide from venting", + "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", + "UnluckySabotageSuicideChance": "Chance to suicide from opening a door", + "ImpCanBeVoidBallot": "Impostors can become VoidBallot", + "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", + "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", + "ImpCanBeAware": "Impostors can become Aware", + "NeutralCanBeAware": "Neutrals can become Aware", + "CrewCanBeAware": "Crewmates can become Aware", + "AwareKnowRole": "Knows the role of player", + "AwareInteracted": "{0} tried to reveal your role.", + "AwareTitle": "AWARE MESSAGE", + "LighterVentButtonText": "Light", + "LighterSkillCooldown": "Light Cooldown", + "LighterSkillDuration": "Light Duration", + "LighterVisionNormal": "Increased Vision", + "LighterVisionOnLightsOut": "Increased Vision During Lights Out", + "LighterSkillInUse": "Ability in use", + "LighterSkillStop": "Ability expired", + "StealthDarkened": "Darkened: {0}", + "StealthExcludeImpostors": "Ignore Impostors when Blinding", + "StealthDarkenDuration": "Blinding Duration", + "PenguinAbductTimerLimit": "Dragging Time", + "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", + "PenguinKillButtonText": "Drag", + "PenguinTimerText": "Drag Timer", + "PenguinTargetOnCheckMurder": "You are grabbed, Try escape that first!", + "WitnessTime": "Max Time after killing where killer appears red", + "WitnessButtonText": "Examine", + "WitnessFoundInnocent": "✓", + "WitnessFoundKiller": "⚠", + "SwapperMax": "Maximum swaps", + "CanSwapSelfVotes": "Can exchange your own votes.", + "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", + "CantSwapSelf": "Can't exchange of one's own vote", + "SwapVote": "The votes of {0} and {1} were swapped!", + "SwapDead": "Sorry, you can't swap votes after death.", + "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", + "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", + "Swap1": "Swap target 1 selected", + "Swap2": "Swap target 2 selected", + "CancelSwap": "Cleared your previous swap!", + "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your target is dead.", + "Swap1=Swap2": "The target you input is same as Swap target 1.\nPls input a different one", + "SwapTitle": "SWAPPER", + "SwapperTryHideMsg": "Try to hide Swapper's command", + "SwapperPreResult": "Currently you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", + "ImpCanBeFragile": "Impostors can become Fragile", + "NeutralCanBeFragile": "Neutrals can become Fragile", + "CrewCanBeFragile": "Crewmates can become Fragile", + "ImpCanKillFragile": "Impostors can force kill Fragile", + "NeutralCanKillFragile": "Neutrals can force kill Fragile", + "CrewCanKillFragile": "Crewmates can force kill Fragile", + "FragileKillerLunge": "Killer lunges on kill", + "CrusaderSkillLimit": "Maximum Crusades", + "CrusaderSkillCooldown": "Crusade Cooldown", + "CrusaderKillButtonText": "Crusade", + "JailorKillButtonText": "Jail", + "AgitaterKillButtonText": "Pass", + "HasSerialKillerBuddy": "Has Serial Killer buddy", + "ChanceToSpawn": "Chance to spawn", + "ChanceToSpawnAnother": "Chance to spawn another", + "BloodlustKillCD": "Bloodlust Kill Cooldown", + "BloodlustPlayerCount": "Max players alive for Bloodlust", + "ReflectHarmfulInteractions": "Reflect harmful interactions", + + "ImpCanBeDiseased": "Impostors can become Diseased", + "NeutralCanBeDiseased": "Neutrals can become Diseased", + "CrewCanBeDiseased": "Crewmates can become Diseased", + "DiseasedCDOpt": "Increase the cooldown by", + "DiseasedCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeAntidote": "Impostors can become Antidote", + "NeutralCanBeAntidote": "Neutrals can become Antidote", + "CrewCanBeAntidote": "Crewmates can become Antidote", + "AntidoteCDOpt": "Decrease the cooldown by", + "AntidoteCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeStubborn": "Impostors can become Stubborn", + "NeutralCanBeStubborn": "Neutrals can become Stubborn", + "CrewCanBeStubborn": "Crewmates can become Stubborn", + + "ImpCanBeAvanger": "Impostors can become Avenger", + "NeutralCanBeAvanger": "Neutrals can become Avenger", + "CrewCanBeAvanger": "Crewmates can become Avenger", + "ImpCanBeSleuth": "Impostors can become Sleuth", + "CrewCanBeSleuth": "Crewmates can become Sleuth", + "NeutralCanBeSleuth": "Neutrals can become Sleuth", + "SleuthCanKnowKillerRole": "Can find the role of the killer", + "SleuthNoticeKiller": "\nThe killer's role is {0}.", + "SleuthNoticeVictim": "{0}'s role is {1}.", + "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", + "BomberDiesInExplosion": "Bomber dies in their explosion", + "ImpostorsSurviveBombs": "Impostors survive bombs", + "NukeRadius": "Nuke radius (12x is very large)", + "NukeCooldown": "Nuke Cooldown", + + "MasochistKillMax": "Amount of attacks needed to win", + "GuessMasochist": "You just tried to guess a Masochist!\nThey're now one step closer to winning!\n\nDo not guess them again.", + "MasochistKill": "You were attacked!", + "SelfGuessMasochist": "You can't self guess as a Masochist, you cheater!", + "GuessMasochistBlocked": "Masochist cannot guess due to self guessing.", + + "RememberCooldown": "Imitate Cooldown", + "RefugeeKillCD": "Refugee's Kill Cooldown", + "RememberedNeutralKiller": "You remembered you were a neutral killer!", + "RememberedMaverick": "You remembered you were a Maverick!", + "RememberedPursuer": "You remembered you were a Pursuer!", + "RememberedFollower": "You remembered you were a Follower!", + "RememberedAmnesiac": "You failed to remember your role.", + "RememberedImitator": "You remmebered you were an Imitator.", + "RememberedImpostor": "You remembered you were an Impostor!", + "RememberedCrewmate": "You remembered you were a crewmate!", + "ImitatorImitated": "An Imitator imitated your role!", + "ImitatorInvalidTarget": "Imitation failed", + "RememberButtonText": "Remember", + "ImitatorKillButtonText": "Imitate", + "IncompatibleNeutralMode": "If neutral is incompatible, turn into", + "RememberedYourRole": "An Amnesiac rmembered your role!", + "YouRememberedRole": "You remembered who you were!", + + "BanditStealMode": "Steal Mode", + "BanditStealMode_OnMeeting": "On Meeting", + "BanditStealMode_Instantly": "Instantly", + "BanditMaxSteals": "Maximum Steals", + "BanditCanStealBetrayalAddon": "Can Steal Betrayal Addons", + "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", + "NoStealableAddons": "Could not steal addon from the player", + "StealCooldown": "Steal cooldown", + + "DoppelMaxSteals": "Maximum Steals", + + "NecromancerRevengeTime": "Necromancy time", + "NecromancerRevenge": "You have {0}s to kill {1}", + "NecromancerSuccess": "Necromancy complete! You live to see another day.", + "NecromancerHide": "Venting is disabled, hide from the Necromancer!", + "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", + "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", + "RetributionistKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", + "RetributionistKillDead": "Choose a living player to kill.", + "RetributionistKillSucceed": "{0} was killed by the Retributionist!", + "RetributionistKillDisable": "You can't retribute until your tasks are done.", + "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", + "RetributionistCanKillNum": "Max retributions", + "RetributionistKillTooManyDead": "Too many players are dead, you can't retribute.", + "MinimumPlayersAliveToRetri": "Maximum players needed to block retributions", + "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", + "BakerChangeChances": "Chance that Baker turns into Famine", + "BakerChange": "The Baker has turned into Famine!\nThe bread is now poisoned.\nIf the Famine is not exiled, all players with poisoned bread die.", + "BakerChangeNow": "A player has poisoned bread!\nExile the Famine or that player dies.", + "BakerChangeNONE": "The Famine has not given out poisoned bread.\nNobody will die of poison, but you should still exile the Famine.", + "PanAliveMessageTitle": "BAKER ", + "PanAlive": "The Baker has given out bread.", + "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", + + "TwisterCooldown": "Twist Cooldown", + "TwisterButtonText": "Twist", + "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", + "InstigatorAbilityLimit": "Ability Use Count", + "InstigatorKillsPerAbilityUse": "Kills per Ability use", + + "CrewCanFindCaptain": "Crewmates can find Captain", + "MadmateCanFindCaptain": "Madmates can find Captain", + "ReducedSpeed": "Reduced speed", + "ReducedSpeedTime": "Time duration for reduced speed", + "CaptainCanTargetNB": "Captain can target Neutral Benign", + "CaptainCanTargetNE": "Captain can target Neutral Evil", + "CaptainCanTargetNC": "Captain can target Neutral Chaos", + "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", + "CaptainCanTargetNK": "Captain can target Neutral Killer", + "CaptainSpeedReduced": "Captain reduced your speed", + "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", + "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", + + "InspectorTryHideMsg": "Hide Inspector's commands", + "MaxInspectCheckLimit": "Max inspections per game", + "InspectCheckLimitPerMeeting": "Max inspections per meeting", + "InspectCheckTargetKnow": "Targets know they were checked by Inspector", + "InspectCheckOtherTargetKnow": "Targets know who they were checked with", + "InspectorDead": "You can not use your power after death", + "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", + "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", + "InspectCheckSelf": "HA!! you thought it would be this easy. You can not check yourself", + "InspectCheckReveal": "HA! you thought it would be this easy. You can not check a role that is revealed", + "InspectCheckTitle": "INSPECTOR ", + "InspectCheckTrue": "{0} and {1} are in the same team!", + "InspectCheckFalse": "{0} and {1} are NOT in the same team!", + "InspectCheckTargetMsg": " were checked by Inspector.", + "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "InspectCheckNull": "Please select an ID of a living player to check their team", + "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", + "InspectCheckRevealTarget": "When tasks done, target knows team of other target", + "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", + + "EgoistCountMode.Original": "Original", + "EgoistCountMode.Neutral": "Neutral", + + "JailerJailCooldown": "Jail cooldown", + "JailerMaxExecution": "Maximum executions", + "JailerNBCanBeExe": "Can execute Neutral Benign", + "JailerNCCanBeExe": "Can execute Neutral Chaos", + "JailerNECanBeExe": "Can execute Neutral Evil", + "JailerNKCanBeExe": "Can execute Neutral Killing", + "JailerNACanBeExe": "Can execute Neutral Apocalypse", + "JailerCKCanBeExe": "Can execute Crew Killing", + "JailerTargetAlreadySelected": "You have already selected a target", + "SuccessfullyJailed": "Target successfully jailed", + "CantGuessJailed": "You can not guess the target", + "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", + "CanNotTrialJailed": "You can not trial the target.", + "notifyJailedOnMeeting": "Notify jailed player when meeting starts", + "JailedNotifyMsg": "You have been locked up in jail by the Jailer. No one can guess or judge you and you can only guess Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", + "JailerTitle": "Jailer", + + "CopyCatCopyCooldown": "Copy cooldown", + "CopyCatRoleChange": "Your role has been changed to {0}", + "CopyCatCanNotCopy": "You can not copy target's role", + "CopyButtonText": "Copy", + "CopyCrewVar": "Can copy evil variants of crew roles", + "CopyTeamChangingAddon": "Can copy team changing addon", + + "MaxCleanserUses": "Max cleanses", + "CleansedCanGetAddon": "Cleansed player can get Add-on", + "CleanserTitle": "CLEANSER", + "CleanserRemoveSelf": "You can not cleanse yourself", + "CleanserCantRemove": "Oops! the player can not be cleansed.", + "CleanserRemovedRole": "{0} has been cleansed. All their Addons will be removed after the meeting.", + "LostAddonByCleanser": "All your Addons were removed by the cleanser", + + "MaxProtections": "Max protections", + "KeeperHideVote": "Hide Keeper's vote", + "KeeperProtect": "You chose to protect {0}, your vote has been returned", + "KeeperTitle": "Keeper", + + "MaulRadius": "Maul Radius", + "ImpCanBeAutopsy": "Impostors can become Autopsy", + "CrewCanBeAutopsy": "Crewmates can become Autopsy", + "NeutralCanBeAutopsy": "Neutrals can become Autopsy", + "ImpCanBeCyber": "Impostors can become Cyber", + "CrewCanBeCyber": "Crewmates can become Cyber", + "NeutralCanBeCyber": "Neutrals can become Cyber", + "ImpKnowCyberDead": "Impostors know if Cyber died", + "CrewKnowCyberDead": "Crewmates know if Cyber died", + "NeutralKnowCyberDead": "Neutrals know if Cyber died", + "CyberKnown": "Everyone can see Cyber", + "ImpCanBeInfluenced": "Impostors can become Influenced", + "CrewCanBeInfluenced": "Crewmates can become Influenced", + "NeutralCanBeInfluenced": "Neutrals can become Influenced", + "ImpCanBeBewilder": "Impostors can become Bewilder", + "CrewCanBeBewilder": "Crewmates can become Bewilder", + "NeutralCanBeBewilder": "Neutrals can become Bewilder", + "KillerGetBewilderVision": "Killer gets Bewilder's vision", + "ImpCanBeOiiai": "Impostors can be OIIAI", + "CrewCanBeOiiai": "Crewmates can be OIIAI", + "NeutralCanBeOiiai": "Neutrals can be OIIAI", + "OiiaiCanPassOn": "OIIAI can pass on to the killer", + "NeutralChangeRolesForOiiai": "Neutrals turns to ", + "LostRoleByOiiai": "You got erased by OIIAI!", + "ImpCanBeSunglasses": "Impostors can become Sunglasses", + "CrewCanBeSunglasses": "Crewmates can become Sunglasses", + "NeutralCanBeSunglasses": "Neutrals can become Sunglasses", + "SunglassesVision": "Sunglasses Vision", + "ImpCanBeLoyal": "Impostors can become Loyal", + "CrewCanBeLoyal": "Crewmates can become Loyal", + "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", + "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", + "SheriffCanBeMadmate": "Sheriff can become Madmate", + "MayorCanBeMadmate": "Mayor can become Madmate", + "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", + "SnitchCanBeMadmate": "Snitch can become Madmate", + "JudgeCanBeMadmate": "Judge can become Madmate", + "MarshallCanBeMadmate": "Marshall can become Madmate", + "GanRetributionistCanBeMadmate": "Retributionist can be converted", + "RetributionistCanBeMadmate": "Retributionist can become Madmate", + "OverseerCanBeMadmate": "Overseer can become Madmate", + "GanSheriffCanBeMadmate": "Sheriff can be converted", + "GanMayorCanBeMadmate": "Mayor can be converted", + "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", + "GanJudgeCanBeMadmate": "Judge can be converted", + "GanMarshallCanBeMadmate": "Marshall can be converted", + "GanOverseerCanBeMadmate": "Overseer can be converted", + "RascalAppearAsMadmate": "Appear As Madmate On Ejection", + "CouncillorDead": "Sorry, you can't murder from the dead.", + "CouncillorMurderMax": "Sorry, you've reached the maximum amount of murders for the meeting.", + "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", + "Councillor_MurderKill": "{0} was judged.", + "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Councillor_MurderNull": "Please choose a living player to murder.", + "Councillor_MurderKillTitle": "COURT ", + "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", + "CouncillorCanMurderMadmate": "Can Murder Madmates", + "CouncillorCanMurderImpostor": "Can Murder Impostors", + "CouncillorTryHideMsg": "Try to hide Councillor's commands", + "DazzlerDazzled": "You were dazzled by the Dazzler!", + "DazzlerCauseVision": "Reduced vision", + "DazzlerDazzleLimit": "Max number of players affected by reduced vision", + "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", + "DazzleCooldown": "Dazzle Cooldown", + "DazzleButtonText": "Dazzle", + + "MoleVentButtonText": "Dig", + "MoleVentCooldown": "Dig cooldown", + + "AddictVentButtonText": "Get Fix", + "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", + "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerble", + + "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", + "AlchemistShieldDur": "Resistance Potion Duration", + "AlchemistInvisDur": "Invisibility Potion Duration", + "AlchemistVision": "Night Vision", + "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", + "AlchemistVisionDur": "Night Vision Potion Duration", + "AlchemistSpeed": "Speed Potion Boost", + "AlchemistVentButtonText": "Drink", + "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", + "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", + "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", + "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", + "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", + "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", + "AlchemistGotBloodlustPotion": "Potion of Harming: Kill the next player you touch", + "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", + "NoPotion": "You have no potions", + + "StoreShield": "Potion of Resistance", + "StoreSuicide": "Potion of Poison", + "StoreTP": "Potion of Warping", + "StoreSP": "Potion Of Speed", + "StoreQF": "Potion of Fixing", + "StoreBL": "Potion of Harming", + "StoreNS": "Potion of Night Vision", + "StoreNull": "None", + "PotionStore": "Potion in store: ", + "WaitQFPotion": "\nPotion of Fixing waiting for use", + + "AlchemistShielded": "Potion of Resistance started", + "AlchemistHasVision": "Potion of Night Vision started", + "AlchemistShieldOut": "Potion of Resistance ended", + "AlchemistVisionOut": "Potion of Night Vision ended", + "AlchemistPotionBloodlust": "You gained bloodlust", + "AlchemistHasSpeed": "Potion Of Speed started", + "AlchemistSpeedOut": "Potion Of Speed ended", + + "DeathpactDuration": "Death Pact duration", + "DeathPactCooldown": "Death Pact Assign Cooldown", + "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", + "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", + "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", + "DeathpactVisionWhileInPact": "Vision for players in Death Pact", + "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", + "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", + "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", + "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", + "DeathpactComplete": "Death Pact was concluded.", + "DeathpactExecuted": "Death Pact was executed.", + "DeathpactAverted": "Death Pact was averted.", + "DeathpactButtonText": "Assign", + "DevourerHideNameConsumed": "Hide the names of consumed players", + "DevourCooldown": "Devour Cooldown", + "DevourerButtonText": "Devour", + "EatenByDevourer": "Your skin was eaten by the Devourer", + "DevourerEatenSkin": "Target skin eaten", + "DevouredName": "Devoured", + "PitfallTrapCooldown": "Trap Cooldown", + "PitfallMaxTrapCount": "Number of Traps that can be set", + "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", + "PitfallTrapDuration": "Time the Trap remains active", + "PitfallTrapRadius": "Trap Radius", + "PitfallTrapFreezeTime": "Trap freeze time", + "PitfallTrapCauseVision": "Trap caused vision", + "PitfallTrapCauseVisionTime": "Trap caused vision time", + "PitfallTrap": "You have fallen into a trap!", + "ConsigliereDivinationMaxCount": "Maximum Reveals", + "RitualMaxCount": "Maximum Reveals", + "CleanserHideVote": "Hide Cleanser's vote", + "OracleSkillLimit": "Maximum Uses", + "OracleHideVote": "Hide Oracle's vote", + "OracleCheckReachLimit": "You're out of uses!", + "OracleCheckSelfMsg": "You can't even trust yourself, huh?", + "OracleCheckLimit": "Reminder: You have {0} uses left", + "OracleCheckMsgTitle": "ORACLE ", + "OracleCheck.NotCrewmate": "Appears to not be a crewmate", + "OracleCheck.Crewmate": "Appears to be a crewmate", + "OracleCheck.Neutral": "Appears to be a neutral", + "OracleCheck.Impostor": "Appears to be an Impostor", + "OracleCheck": "Target Results:", + "FailChance": "Chance of showing incorrect result", + "OracleCheckAddons": "Oracle checks add-ons", + "ChameleonCanVent": "Vent to disguise", + "ChameleonInvisState": "You are disguising!", + "ChameleonInvisStateOut": "Your disguise ended", + "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", + "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", + "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", + "ChameleonCooldown": "Disguise Cooldown", + "ChameleonDuration": "Disguise Duration", + "ChameleonRevertDisguise": "Expose", + "ChameleonDisguise": "Disguise", + "KillCooldownAfterCleaning": "Kill Cooldown On Clean", + "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", + "MedusaStoneBody": "Body stoned", + "MedusaReportButtonText": "Stone", + + "CursedSoulCurseCooldown": "Soul Snatch Cooldown", + "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", + "CursedSoulCurseMax": "Maximum Soul Snatches", + "CursedSoulKnowTargetRole": "Know the roles of Soulless players", + "CursedSoulCanCurseNeutral": "Neutral roles have souls", + "CursedSoulKillButtonText": "Snatch", + "SoullessByCursedSoul": "Your soul was snatched by a Cursed Soul", + "CursedSoulSoullessPlayer": "Soul snatched", + "CursedSoulInvalidTarget": "No soul found", + + "AdmireCooldown": "Admire Cooldown", + "AdmirerKnowTargetRole": "Know the roles of Admired players", + "AdmirerSkillLimit": "Skill Limit", + "AdmireButtonText": "Admire", + "AdmirerAdmired": "The Admirer admired you!", + "AdmiredPlayer": "Player admired", + "AdmirerInvalidTarget": "Target cannot be admired", + + "SpiritualistNoticeTitle": "SPIRITUALIST ", + "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", + "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", + "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", + "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", + "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", + "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", + "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", + "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", + "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", + "EnigmaClueHat1": "The Killer wears a Hat!", + "EnigmaClueHat2": "The Killer does not wear a Hat!", + "EnigmaClueHat3": "The Killer wears {0} as a Hat!", + "EnigmaClueSkin1": "The Killer wears a Skin!", + "EnigmaClueSkin2": "The Killer does not wear a Skin!", + "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", + "EnigmaClueVisor1": "The Killer wears a Visor!", + "EnigmaClueVisor2": "The Killer does not wear a Visor!", + "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", + "EnigmaCluePet1": "The Killer does have a Pet!", + "EnigmaCluePet2": "The Killer does not have a Pet!", + "EnigmaCluePet3": "The Killer has {0} as a Pet!", + "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", + "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", + "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", + "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", + "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", + "EnigmaClueColor1": "The Killer has a light color!", + "EnigmaClueColor2": "The Killer has a dark color!", + "EnigmaClueColor3": "The Killer's color is {0}!", + "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", + "EnigmaClueStatus1": "The Killer is currently inside a Vent!", + "EnigmaClueStatus2": "The Killer is currently on a Ladder!", + "EnigmaClueStatus3": "The Killer is already Dead!", + "EnigmaClueStatus4": "The Killer is still Alive!", + "EnigmaClueRole1": "The Killer is an Impostor!", + "EnigmaClueRole2": "The Killer is a Neutral!", + "EnigmaClueRole3": "The Killer is a Crewmate!", + "EnigmaClueRole4": "The Killer's Role is {0}!", + "EnigmaClueLevel1": "The Killer's Level is above 50!", + "EnigmaClueLevel2": "The Killer's Level is below 50!", + "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", + "EnigmaClueLevel4": "The Killer's Level is {0}!", + "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", + "EnigmaClueHatTitle": "Enigma Hat Clue!", + "EnigmaClueVisorTitle": "Enigma Visor Clue!", + "EnigmaClueSkinTitle": "Enigma Skin Clue!", + "EnigmaCluePetTitle": "Enigma Pet Clue!", + "EnigmaClueNameTitle": "Enigma Name Clue!", + "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", + "EnigmaClueColorTitle": "Enigma Color Clue!", + "EnigmaClueLocationTitle": "Enigma Location Clue!", + "EnigmaClueStatusTitle": "Enigma Status Clue!", + "EnigmaClueRoleTitle": "Enigma Role Clue!", + "EnigmaClueLevelTitle": "Enigma Level Clue!", + "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", + + "ChiefOfPoliceSkillCooldown": "Cooldown for recruiting sheriffs", + "PolicCanImpostorAndNeutarl": "You can recruit Impostor or Kill Neutral to become sheriffs", + "SheriffSuccessfullyRecruited": "You recruited a sheriff.", + "BeSheriffByPolice": "You've been recruited by the police chief! Serve the crew!", + "ChiefOfPoliceKillButtonText": "Recruitment", + "VotesPerKill": "Votes gained for each kill", + "PickpocketGetVote": "You've got {0} votes", + "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "VultureNumberOfReportsToWin": "Bodies needed to win", + "VultureReportBody": "Body eaten!", + "VultureEatButtonText": "Consume", + "VultureReportCooldown": "Eat Cooldown", + "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", + "VultureCooldownUp": "Eat Cooldown finished", + + "TasksMarkPerRound": "Number of tasks that can be marked in one round", + "TaskinatorBombPlanted": "Bomb has been planted", + + "ShieldDuration": "Shield duration", + "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", + "BenefactorTaskMarked": "Task marked successfully", + "BenefactorTargetGotShield": "You got shield by Benefactor", + + "PirateTryHideMsg": "Hide Pirate's commands", + "SuccessfulDuelsToWin": "Number of successful duels needed to win", + "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the duel if you choose the same option as the target", + "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", + "PirateTitle": "PIRATE ", + "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", + "PirateDead": "Ye be dead. Ye cannot duel anymore.", + "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", + "DuelDone": "Ye 'ave chosen yer option fer the duel.\nWait fer the meetin' to end to see the result.", + "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", + "PirateDuelButtonText": "Duel", + "DuelCooldown": "Duel Cooldown", + "Rock": "Rock", + "Paper": "Paper", + "Scissors": "Scissors", + "Heads": "Heads", + "Tails": "Tails", + "SpyRedNameDur": "Colored Name Duration", + "SpyInteractionBlocked": "Block kill button interaction", + "AgitaterBombCooldown": "Agitator bomb cooldown", + "AgitaterPassCooldown": "Bomb pass cooldown", + "BombExplodeCooldown": "Bomb explode cooldown", + "AgitaterPassNotify": "Bomb successfully passed", + "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", + "AgitaterCanGetBombed": "Agitator can get bomb", + "AgitaterAutoReportBait": "Agitator Auto Report Bait", + + "SeekerPointsToWin": "Number of points required to win", + "SeekerTagCooldown": "Tag Cooldown", + "SeekerNotify": "Your target is {0}", + "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", + + "PixiePointsToWin": "Number of points required to win", + "MaxTargets": "Maximum number of targets per round", + "MarkCooldown": "Mark cooldown", + "PixieSuicide": "Pixie suicides if target is not voted out", + "PixieMaxTargetReached": "You have already selected all the targets this round", + "PixieTargetAlreadySelected": "Target is already selected", + "PixieButtonText": "Mark", + + "PlagueBearerCD": "Plague cooldown", + "PestilenceCD": "Pestilence Kill cooldown", + "PlagueBearerAlreadyPlagued": "Player has already been plagued", + "PlagueBearerToPestilence": "You have turned into Pestilence!!", + "PestilenceCanVent": "Pestilence Can Vent", + "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", + "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", + "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", + "RomanticBetCooldown": "Pick Partner Cooldown", + "RomanticProtectCooldown": "Protect Cooldown", + "RomanticBetPlayer": "You picked your partner", + "RomanticBetOnYou": "The Romantic chose you as their Partner!", + "VengefulKCD": "Vengeful Romantic Kill Cooldown", + "VengefulCanVent": "Vengeful Romantic Can Vent", + "RuthlessKCD": "Ruthless Romantic Kill Cooldown", + "RuthlessCanVent": "Ruthless Romantic Can Vent", + "RomanticProtectPartner": "Your partner is under protection", + "RomanticIsProtectingYou": "The Romantic is protecting you", + "ProtectingOver": "Shield expired", + "RomanticProtectDuration": "Protect Duration", + "RomanticKnowTargetRole": "Romantic knows their target's role", + "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", + + "GuessMasterMisguess": "{0} misguessed", + "GuessMasterTargetRole": "Someone tried to guess {0}", + "GuessMasterTitle": "Guess Master ", + + "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", + "DCanGuessImpostors": "Can Guess Impostors", + "DCanGuessCrewmates": "Can Guess Crewmates", + "DCanGuessNeutrals": "Can Guess Neutrals", + "DCanGuessAdt": "Can Guess Add-Ons", + "DoomsayerAdvancedSettings": "Advanced Settings", + "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", + "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", + "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", + "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", + "DoomsayerTryHideMsg": "Hide Doomsayer's commands", + "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", + "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", + "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", + "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", + "DoomsayerGuessCountTitle": "DOOMSAYER", + "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", + + "EveryoneCanKnowMini": "Everyone can see the Mini", + "CanBeEvil": "Mini can be an Impostor", + "EvilMiniSpawnChances": "Probability of Mini being an Impostor", + "GuessMini": "Sorry, you can't hurt a kid Mini.", + "GrowUpDuration": "Time required to grow (s)", + "MajorCooldown": "Kill Cooldown when over 18", + "UpDateAge": "Display age change in real-time", + "Cantkillkid": "You can't kill a Mini that hasn't grown up.", + "CantEat": "You can't eat a Mini that hasn't grown up", + "CantShroud": "You can't control a Mini that hasn't grown up.", + "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", + "CantRecruit": "You can't recruit a Mini that hasn't grown up.", + "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", + "MiniUp": "You're a year older!", + "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilty while you can no longer guess.\nYou can guess again after you have grown up.", + "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", + "CountMeetingTime": "Meeting time can continue to grow", + "YouKillRandomizer1": "You kill Randomizer, Self report!", + "YouKillRandomizer2": "You kill Randomizer, Cannot move!", + "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", + "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", + "MadmateCanBeHurried": "Madmate can be Hurried on game start", + "TaskBasedCrewCanBeHurried": "Task based Crews can be Hurried", + "HurriedCanBeConverted": "Hurried can be recruited in game (excludes madmate)", + "Developer": "Developer", + "Sponsor": "Sponsor", + "Booster": "Server Booster", + "Translator": "Translator", + "NoAccess": "Unauthorized Access!!!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", + "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", + "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", + "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", + "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", + "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", + "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", + "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", + "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", + "DCNotify.Inactivity": "The lobby closed due to inactivity.", + "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", + "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", + "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", + "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", + "RoleType.VanillaRoles": "★ Vanilla Roles", + "RoleType.ImpKilling": "★ Impostor Killing Roles", + "RoleType.ImpSupport": "★ Impostor Support Roles", + "RoleType.ImpConcealing": "★ Impostor Concealing Roles", + "RoleType.ImpHindering": "★ Impostor Hindering Roles", + "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", + "RoleType.Madmate": "★ Madmate Roles", + "RoleType.CrewSupport": "★ Crewmate Support Roles", + "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", + "RoleType.CrewPower": "★ Crewmate Power Roles", + "RoleType.CrewKilling": "★ Crewmate Killing Roles", + "RoleType.CrewBasic": "★ Crewmate Basic Roles", + "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", + "RoleType.NeutralEvil": "★ Neutral Evil Roles", + "RoleType.NeutralBenign": "★ Neutral Benign Roles", + "RoleType.NeutralChaos": "★ Neutral Chaos Roles", + "RoleType.NeutralKilling": "★ Neutral Killing Roles", + "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", + "RoleType.Harmful": "★ Harmful Add-ons", + "RoleType.Support": "★ Supportive Add-ons", + "RoleType.Helpful": "★ Helpful Add-ons", + "RoleType.Mixed": "★ Mixed Add-ons", + "RoleType.Misc": "★ Miscellaneous Add-ons", + "RoleType.Impostor": "★ Impostor Add-ons", + "RoleType.Neut": "★ Neutral Add-ons", + "SubType.Impostor": "★ Impostors", + "SubType.Shapeshifter": "★ Shapeshifters", + "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", + "SubType.Madmate": "★ Madmates", + "SubType.CrewmateKilling": "★ Crewmate Killings", + "SubType.Crewmate": "★ Regular Crewmates", + "SubType.New": "★ New!", + "CrewmateRoles": "★ Crewmate Roles ★", + "ImpostorRoles": "★ Impostor Roles ★", + "NeutralRoles": "★ Neutral Roles ★", + "AddonRoles": "★ Add-ons ★", + "WinnerRoleText.Impostor": "Impostors Win!", + "WinnerRoleText.Crewmate": "Crewmates Win!", + "WinnerRoleText.Apocalypse": "Apocalypse Wins!", + "WinnerRoleText.Terrorist": "Terrorist Wins!", + "WinnerRoleText.Jester": "Jester Wins!", + "WinnerRoleText.Lovers": "Lovers Win!", + "WinnerRoleText.Executioner": "Executioner Wins!", + "WinnerRoleText.Arsonist": "Arsonist Wins!", + "WinnerRoleText.Revolutionist": "Revolutionist Wins!", + "WinnerRoleText.Jackal": "Jackals Win!", + "WinnerRoleText.God": "God Wins!", + "WinnerRoleText.Vector": "Vector Wins!", + "WinnerRoleText.Innocent": "Innocent Wins!", + "WinnerRoleText.Pelican": "Pelican Wins!", + "WinnerRoleText.Youtuber": "YouTuber Wins!", + "WinnerRoleText.Necromancer": "Necromancer Wins!", + "WinnerRoleText.Egoist": "Egoists Win!", + "WinnerRoleText.Demon": "Demon Wins!", + "WinnerRoleText.Stalker": "Stalker Wins!", + "WinnerRoleText.Workaholic": "Workaholic Wins!", + "WinnerRoleText.Collector": "Collector Wins!", + "WinnerRoleText.BloodKnight": "Blood Knight Wins!", + "WinnerRoleText.Poisoner": "Poisoner Wins!", + "WinnerRoleText.Huntsman": "Huntsman Wins!", + "WinnerRoleText.HexMaster": "Hex Master Wins!", + "WinnerRoleText.Cultist": "Cultist Wins!", + "WinnerRoleText.Wraith": "Wraith Wins!", + "WinnerRoleText.SerialKiller": "Serial Killers Win!", + "WinnerRoleText.Juggernaut": "Juggernaut Wins!", + "WinnerRoleText.Infectious": "Infectious Wins!", + "WinnerRoleText.Virus": "Virus Wins!", + "WinnerRoleText.Phantom": "Phantom Wins!", + "WinnerRoleText.Jinx": "Jinx Wins!", + "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", + "WinnerRoleText.PotionMaster": "Potion Master Wins!", + "WinnerRoleText.Pickpocket": "Pickpocket Wins!", + "WinnerRoleText.Traitor": "Traitor Wins!", + "WinnerRoleText.Vulture": "Vulture Wins!", + "WinnerRoleText.Medusa": "Medusa Wins!", + "WinnerRoleText.Famine": "Famine Wins!", + "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", + "WinnerRoleText.Glitch": "Glitch Wins!", + "WinnerRoleText.Pestilence": "Pestilence Wins!", + "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", + "WinnerRoleText.Masochist": "Masochist Wins!", + "WinnerRoleText.Doomsayer": "Doomsayer Wins!", + "WinnerRoleText.Pirate": "Pirate Wins!", + "WinnerRoleText.Shroud": "Shroud Wins!", + "WinnerRoleText.Werewolf": "Werewolf Wins!", + "WinnerRoleText.Seeker": "Seeker Wins!", + "WinnerRoleText.Agitater": "Agitator Wins!", + "WinnerRoleText.Occultist": "Occultist Wins!", + "WinnerRoleText.SoulCollector": "Soul Collector Wins!", + "WinnerRoleText.NiceMini": "Nice Mini Wins!", + "WinnerRoleText.Mini": "Nice Mini was killed", + "WinnerRoleText.Bandit": "Bandit Wins!", + "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", + "WinnerRoleText.Solsticer": "Solsticer Wins!", + "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", + "WinnerRoleText.Doppelganger": "Doppelganger Wins!", + "AdditionalWinnerRoleText.Sidekick": "Sidekick", + "AdditionalWinnerRoleText.Taskinator": "Taskinator", + "AdditionalWinnerRoleText.Opportunist": "Opportunist", + "AdditionalWinnerRoleText.Lawyer": "Lawyer", + "AdditionalWinnerRoleText.Hater": "Hater", + "AdditionalWinnerRoleText.Provocateur": "Provocateur", + "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", + "AdditionalWinnerRoleText.Follower": "Follower", + "AdditionalWinnerRoleText.Pursuer": "Pursuer", + "AdditionalWinnerRoleText.Jester": "Jester", + "AdditionalWinnerRoleText.Lovers": "Lovers", + "AdditionalWinnerRoleText.Executioner": "Executioner", + "AdditionalWinnerRoleText.Phantom": "Phantom", + "AdditionalWinnerRoleText.Maverick": "Maverick", + "AdditionalWinnerRoleText.Shaman": "Shaman", + "AdditionalWinnerRoleText.Pixie": "Pixie", + "AdditionalWinnerRoleText.NiceMini": "Nice Mini", + "AdditionalWinnerRoleText.Romantic": "Romantic", + "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", + "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", + "ErrorEndText": "An error occurred", + "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", + "ForceEnd": "Aborted", + "EveryoneDied": "Everyone died", + "ForceEndText": "Host has aborted the game", + "NiceMiniDied": "Nice Mini was killed", + "HaterMisFireKillTarget": "Hater kills target when misfire", + "HaterChooseConverted": "Select addons that Hater can kill", + "HaterCanKillMadmate": "Can kill madmate", + "HaterCanKillCharmed": "Can kill charmed", + "HaterCanKillLovers": "Can kill lovers", + "HaterCanKillSidekick": "Can kill jackal team", + "HaterCanKillEgoist": "Can kill egoist", + "HaterCanKillInfected": "Can kill infected team", + "HaterCanKillContagious": "Can kill virus team", + "HaterCanKillAdmired": "Can kill admirer", + "AutoMuteUs": "Enable it if you use AutoMuteUs", + "HorseMode": "Enable to become a horse", + "LongMode": "Enable to have a long neck", + "InfluencedChangeVote": "oops!You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", + + + "FFA": "Free For All", + "ModeFFA": "Gamemode: FFA", + "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the mean time!", + "FFA_GameTime": "Maximum Game Length", + "FFA_KCD": "Kill Cooldown", + "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", + "FFA_EnableRandomAbilities": "Enable Random Events", + "FFA_ShieldDuration": "Shield Duration", + "FFA_IncreasedSpeed": "Increased Speed", + "FFA_DecreasedSpeed": "Decreased Speed", + "FFA_ModifiedSpeedDuration": "Modified Speed Duration", + "FFA_LowerVision": "Lowered Vision", + "FFA_ModifiedVisionDuration": "Lowered Vision Duration", + "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", + "FFA-Event-GetShield": "You have a temporary shield!", + "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", + "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", + "FFA-Event-GetHighKCD": "You got a higher kill cooldown", + "FFA-Event-GetLowVision": "You have lower vision temporarily", + "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", + "FFA-Event-GetTP": "You got teleported to a random vent!", + "FFA-Event-RandomTP": "Everyone was swapped with someone", + "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", + "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", + "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", + "FFA_TargetIsShielded": "The player you tried to kill is shielded!", + "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", + "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", + "Killer": "FREE FOR ALL", + "KillerInfo": "Kill Everyone to Win", + + "Hide&SeekTOHE": "Hide & Seek", + "MenuTitle.Hide&Seek": "Hide & Seek Settings", + "NumImpostorsHnS": "Num Impostors", + + "EveryOneKnowSolsticer": "Every One Know who is Solsticer", + "SolsticerKnowItsKiller": "Solsticer knows the role of whom used kill button on it", + "SolsticerSpeed": "Movement speed of Solsticer", + "SolsticerRemainingTaskWarned": "Remaining tasks to be known", + "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", + "SolsticerMurdered": "{0} attempted to murder you!", + "MurderSolsticer": "You stopped Solsticer this round!", + "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", + "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", + "SolsticerTitle": "Solsticer", + "GuessSolsticer": "Sorry, but you can not guess Solsticer!", + "VoteSolsticer": "Sorry, but you can not vote Solsticer!", + "SolsticerTasksReset": "Your tasks get reset!", + "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", + "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", + + "VoteDead": "The player you voted for was exhiled before the meeting concluded. Your vote was rescinded.", + + "ImpCanBeSilent": "Impostors can become Silent", + "CrewCanBeSilent": "Crewmates can become Silent", + "NeutralCanBeSilent": "Neutrals can become Silent", + "LastMessageReplay": "Last System Message Replay", + "Contributor": "Contributor", + + "dbConnect.InitFailure": "Error while connecting to TOHE api, pls check your network connection and retry login!", + "dbConnect.nullFriendCode": "This build of TOHE is not aviliable to users with no friendcode!", + + "ImpCanBeSusceptible": "Impostors can become Susceptible", + "CrewCanBeSusceptible": "Crewmates can become Susceptible", + "NeutralCanBeSusceptible": "Neutrals can become Susceptible", + + "Quizmaster": "Quizmaster", + "QuizmasterInfo": "Quiz people to kill them in meetings", + "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will \"?!\" next to their name. If the player answers a question wrongly, or doesn't answer, they will die. If the Quizmaster was killed/ejected in the same meeting, the player will live.\nThe Quizmaster cannot mark multiple people in the same round", + "QuizmasterKillButtonText": "Quiz", + + "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", + "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", + "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", + "QuizmasterChat.CorrectTarget": "Correct", + "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", + "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", + "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", + "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", + "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", + "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die", + "QuizmasterChat.Title": "Quizmaster Information", + "QuizmasterChat.CantAnswer": "As the quizmaster you can't answer questions", + "QuizmasterChat.AnswerNotValid": "Your answer must be A, B or C", + "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", + + "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", + "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", + "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", + "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", + "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", + + "Quizmaster.None": "None", + + "QuizmasterSabotages.Lights": "Lights", + "QuizmasterSabotages.Reactor": "Reactor", + "QuizmasterSabotages.Communications": "Communications", + "QuizmasterSabotages.O2": "O2", + "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", + "QuizmasterAnswers.One": "One", + "QuizmasterAnswers.Two": "Two", + "QuizmasterAnswers.Three": "Three", + "QuizmasterAnswers.Four": "Four", + "QuizmasterAnswers.Five": "Five", + "QuizmasterAnswers.Pacifist": "Pacifist", + "QuizmasterAnswers.Vampire": "Vampire", + "QuizmasterAnswers.Snitch": "Snitch", + "QuizmasterAnswers.Vigilante": "Vigilante", + "QuizmasterAnswers.Jackal": "Jackal", + "QuizmasterAnswers.Mole": "Mole", + "QuizmasterAnswers.Sniper": "Sniper", + "QuizmasterAnswers.Coven": "Coven", + "QuizmasterAnswers.Sabotuer": "Sabotuer", + "QuizmasterAnswers.Sorcerers": "Sorcerers", + "QuizmasterAnswers.Killer": "Killer", + "QuizmasterAnswers.Edition": "Edition", + "QuizmasterAnswers.Experimental": "Experimental", + "QuizmasterAnswers.Enhanced": "Enhanced", + "QuizmasterAnswers.Edited": "Edited", + + "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", + "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", + "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", + "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", + "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called last meeting before this meeting?", + "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", + "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", + "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", + "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", + "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed an update later?", + "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", + "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", + "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", + "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", + "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", + "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", + "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", + + "DeathReason.WrongAnswer": "Wrong Quiz Answer", + + "TPCooldown": "Teleport Cooldown", + "RiftsTooClose": "Location too close to the first rift", + "RiftCreated": "Rift made successfully", + "RiftsDestroyed": "All rifts Destroyed", + "RiftRadius": "Rift Radius", + + "TiredVision": "Vision When Tired", + "TiredSpeed": "Speed When Tired", + "TiredDur": "Tired Duration", + "ImpCanBeTired": "Impostors can become Tired", + "CrewCanBeTired": "Crewmates can become Tired", + "NeutralCanBeTired": "Neutrals can become Tired", + + "TiredNotify": "Zzz..", + + "PlagueDoctorInfectLimit": "Infect Limit", + "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", + "PlagueDoctorInfectTime": "Infect Time", + "PlagueDoctorInfectDistance": "Infect Distance", + "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", + "PlagueDoctorCanInfectSelf": "Can Infect Self", + "PlagueDoctorCanInfectVent": "Can Infect While In Vent", + "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", + + "StatueSlow": "Statue Slowness", + "StatuePeopleToSlow": "People Needed To Slow", + + "ImpCanBeStatue": "Impostors can become Statue", + "CrewCanBeStatue": "Crewmates can become Statue", + "NeutralCanBeStatue": "Neutrals can become Statue", + + "WardenIncreaseSpeed": "Increase Speed By", + "WardenWarn": "DANGER! RUN!", + + "MinionAbilityTime": "Ability Duration" } diff --git a/Resources/roleColor.json b/Resources/roleColor.json index f7a8fe8f22..6983d1928c 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -80,8 +80,15 @@ "Pyromaniac": "#fc8a4c", "Agitater": "#F4A460", "Bandit": "#8B008B", + "Apocalypse": "#ff174f", "PlagueBearer": "#e5f6b4", "Pestilence": "#343136", + "SoulCollector": "#A675A1", + "Death": "#ff174f", + "Baker": "#bf9f7a", + "Famine": "#83461c", + "Berserker": "#FF0055", + "War": "#2B0804", "Jester": "#ec62a5", "Terrorist": "#00e600", "Executioner": "#c0c0c0", @@ -143,7 +150,6 @@ "Werewolf": "#191970", "Seeker": "#ffaa00", "Pixie": "#00FF00", - "SoulCollector": "#A675A1", "Imitator": "#B3D94C", "Doppelganger": "#f6f4a3", "GM": "#ff5b70", diff --git a/Roles/(Ghosts)/Impostor/Bloodmoon.cs b/Roles/(Ghosts)/Impostor/Bloodmoon.cs index 77eee26ebe..be61d40140 100644 --- a/Roles/(Ghosts)/Impostor/Bloodmoon.cs +++ b/Roles/(Ghosts)/Impostor/Bloodmoon.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using Hazel; using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; diff --git a/Roles/AddOns/Common/Susceptible.cs b/Roles/AddOns/Common/Susceptible.cs index d653975d32..5418aa0e10 100644 --- a/Roles/AddOns/Common/Susceptible.cs +++ b/Roles/AddOns/Common/Susceptible.cs @@ -349,6 +349,26 @@ public static void CallEnabledAndChange(PlayerControl victim) goto default; } break; + case PlayerState.DeathReason.Armageddon: + if (!CustomRoles.SoulCollector.IsEnable()) + { + Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; + } + else + { + goto default; + } + break; + case PlayerState.DeathReason.Starved: + if (!CustomRoles.Baker.IsEnable()) + { + Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; + } + else + { + goto default; + } + break; default: while (Main.PlayerStates[victim.PlayerId].deathReason != randomReason) diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index b1c8401678..442ce5fa13 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -17,6 +17,7 @@ enum RoleAssignType Impostor, NeutralKilling, NonKillingNeutral, + NeutralApocalypse, Crewmate } @@ -28,7 +29,7 @@ public class RoleAssignInfo(CustomRoles role, int spawnChance, int maxCount, int public int AssignedCount { get => assignedCount; set => assignedCount = value; } } - public static void GetNeutralCounts(int NKmaxOpt, int NKminOpt, int NNKmaxOpt, int NNKminOpt, ref int ResultNKnum, ref int ResultNNKnum) + public static void GetNeutralCounts(int NKmaxOpt, int NKminOpt, int NNKmaxOpt, int NNKminOpt, int NAmaxOpt, int NAminOpt, ref int ResultNKnum, ref int ResultNNKnum, ref int ResultNAnum) { var rd = IRandom.Instance; @@ -41,6 +42,11 @@ public static void GetNeutralCounts(int NKmaxOpt, int NKminOpt, int NNKmaxOpt, i { ResultNKnum = rd.Next(NKminOpt, NKmaxOpt + 1); } + + if (NAmaxOpt > 0 && NAmaxOpt >= NAminOpt) + { + ResultNAnum = rd.Next(NAminOpt, NAmaxOpt + 1); + } } public static void StartSelect() @@ -62,13 +68,15 @@ public static void StartSelect() int optImpNum = Main.RealOptionsData.GetInt(Int32OptionNames.NumImpostors); int optNonNeutralKillingNum = 0; int optNeutralKillingNum = 0; + int optNeutralApocalypseNum = 0; - GetNeutralCounts(Options.NeutralKillingRolesMaxPlayer.GetInt(), Options.NeutralKillingRolesMinPlayer.GetInt(), Options.NonNeutralKillingRolesMaxPlayer.GetInt(), Options.NonNeutralKillingRolesMinPlayer.GetInt(), ref optNeutralKillingNum, ref optNonNeutralKillingNum); + GetNeutralCounts(Options.NeutralKillingRolesMaxPlayer.GetInt(), Options.NeutralKillingRolesMinPlayer.GetInt(), Options.NonNeutralKillingRolesMaxPlayer.GetInt(), Options.NonNeutralKillingRolesMinPlayer.GetInt(), Options.NeutralApocalypseRolesMaxPlayer.GetInt(), Options.NeutralApocalypseRolesMinPlayer.GetInt(), ref optNeutralKillingNum, ref optNonNeutralKillingNum, ref optNeutralApocalypseNum); int readyRoleNum = 0; int readyImpNum = 0; int readyNonNeutralKillingNum = 0; int readyNeutralKillingNum = 0; + int readyNeutralApocalypseNum = 0; List FinalRolesList = []; @@ -77,6 +85,7 @@ public static void StartSelect() Roles[RoleAssignType.Impostor] = []; Roles[RoleAssignType.NeutralKilling] = []; Roles[RoleAssignType.NonKillingNeutral] = []; + Roles[RoleAssignType.NeutralApocalypse] = []; Roles[RoleAssignType.Crewmate] = []; foreach (var id in SetRoles.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) SetRoles.Remove(id); @@ -102,6 +111,7 @@ public static void StartSelect() if (role.IsImpostor()) Roles[RoleAssignType.Impostor].Add(info); else if (role.IsNK()) Roles[RoleAssignType.NeutralKilling].Add(info); + else if (role.IsNA()) Roles[RoleAssignType.NeutralApocalypse].Add(info); else if (role.IsNonNK()) Roles[RoleAssignType.NonKillingNeutral].Add(info); else Roles[RoleAssignType.Crewmate].Add(info); } @@ -116,12 +126,14 @@ public static void StartSelect() Logger.Msg("=====================================================", "AllActiveRoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.Impostor].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "ImpRoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NKRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralApocalypse].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NARoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NonNKRoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "CrewRoles"); Logger.Msg("=====================================================", "AllActiveRoles"); IEnumerable TempAlwaysImpRoles = Roles[RoleAssignType.Impostor].Where(x => x.SpawnChance == 100); IEnumerable TempAlwaysNKRoles = Roles[RoleAssignType.NeutralKilling].Where(x => x.SpawnChance == 100); + IEnumerable TempAlwaysNARoles = Roles[RoleAssignType.NeutralApocalypse].Where(x => x.SpawnChance == 100); IEnumerable TempAlwaysNNKRoles = Roles[RoleAssignType.NonKillingNeutral].Where(x => x.SpawnChance == 100); IEnumerable TempAlwaysCrewRoles = Roles[RoleAssignType.Crewmate].Where(x => x.SpawnChance == 100); @@ -131,22 +143,26 @@ public static void StartSelect() Roles[RoleAssignType.Impostor] = Roles[RoleAssignType.Impostor].Shuffle(rd).Take(optImpNum).ToList(); Roles[RoleAssignType.NeutralKilling] = Roles[RoleAssignType.NeutralKilling].Shuffle(rd).Take(optNeutralKillingNum).ToList(); + Roles[RoleAssignType.NeutralApocalypse] = Roles[RoleAssignType.NeutralApocalypse].Shuffle(rd).Take(optNeutralApocalypseNum).ToList(); Roles[RoleAssignType.NonKillingNeutral] = Roles[RoleAssignType.NonKillingNeutral].Shuffle(rd).Take(optNonNeutralKillingNum).ToList(); Roles[RoleAssignType.Crewmate] = Roles[RoleAssignType.Crewmate].Shuffle(rd).Take(playerCount).ToList(); Roles[RoleAssignType.Impostor].AddRange(TempAlwaysImpRoles); Roles[RoleAssignType.NeutralKilling].AddRange(TempAlwaysNKRoles); + Roles[RoleAssignType.NeutralApocalypse].AddRange(TempAlwaysNARoles); Roles[RoleAssignType.NonKillingNeutral].AddRange(TempAlwaysNNKRoles); Roles[RoleAssignType.Crewmate].AddRange(TempAlwaysCrewRoles); Roles[RoleAssignType.Impostor] = Roles[RoleAssignType.Impostor].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.NeutralKilling] = Roles[RoleAssignType.NeutralKilling].DistinctBy(x => x.Role).ToList(); + Roles[RoleAssignType.NeutralApocalypse] = Roles[RoleAssignType.NeutralApocalypse].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.NonKillingNeutral] = Roles[RoleAssignType.NonKillingNeutral].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.Crewmate] = Roles[RoleAssignType.Crewmate].DistinctBy(x => x.Role).ToList(); Logger.Msg("======================================================", "SelectedRoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.Impostor].Select(x => x.Role.ToString())), "Selected-Impostor-Roles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => x.Role.ToString())), "Selected-NK-Roles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralApocalypse].Select(x => x.Role.ToString())), "Selected-NA-Roles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => x.Role.ToString())), "Selected-NonNK-Roles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => x.Role.ToString())), "Selected-Crew-Roles"); Logger.Msg("======================================================", "SelectedRoles"); @@ -180,6 +196,11 @@ public static void StartSelect() Roles[RoleAssignType.NeutralKilling].Where(x => x.Role == item.Value).Do(x => x.AssignedCount++); readyNeutralKillingNum++; } + else if (item.Value.IsNA()) + { + Roles[RoleAssignType.NeutralApocalypse].Where(x => x.Role == item.Value).Do(x => x.AssignedCount++); + readyNeutralApocalypseNum++; + } else if (item.Value.IsNonNK()) { Roles[RoleAssignType.NonKillingNeutral].Where(x => x.Role == item.Value).Do(x => x.AssignedCount++); @@ -194,6 +215,7 @@ public static void StartSelect() RoleAssignInfo[] Imps = []; RoleAssignInfo[] NNKs = []; RoleAssignInfo[] NKs = []; + RoleAssignInfo[] NAs = []; RoleAssignInfo[] Crews = []; // Impostor Roles @@ -378,7 +400,7 @@ public static void StartSelect() var selectesItem = rd.Next(0, ChanceNonNKRoles.Count); var selected = ChanceNonNKRoles[selectesItem]; var info = NonNKRoleCounts.FirstOrDefault(x => x.Role == selected); - + // Remove 'x' times for (int j = 0; j < info.SpawnChance / 5; j++) ChanceNonNKRoles.Remove(selected); @@ -498,6 +520,104 @@ public static void StartSelect() } } } + // Neutral Apocalypse Roles + { + List AlwaysNARoles = []; + List ChanceNARoles = []; + for (int i = 0; i < Roles[RoleAssignType.NeutralApocalypse].Count; i++) + { + RoleAssignInfo item = Roles[RoleAssignType.NeutralApocalypse][i]; + + if (item.SpawnChance == 100) + { + for (int j = 0; j < item.MaxCount; j++) + { + // Don't add if Host has assigned this role by using '/up' + if (SetRoles.ContainsValue(item.Role)) + { + var playerId = SetRoles.FirstOrDefault(x => x.Value == item.Role).Key; + SetRoles.Remove(playerId); + continue; + } + + AlwaysNARoles.Add(item.Role); + } + } + else + { + // Add 'MaxCount' (1) times + for (int k = 0; k < item.MaxCount; k++) + { + // Don't add if Host has assigned this role by using '/up' + if (SetRoles.ContainsValue(item.Role)) + { + var playerId = SetRoles.FirstOrDefault(x => x.Value == item.Role).Key; + SetRoles.Remove(playerId); + continue; + } + + // Make "Spawn Chance ÷ 5 = x" (Example: 65 ÷ 5 = 13) + for (int j = 0; j < item.SpawnChance / 5; j++) + { + // Add NA roles 'x' times (13) + ChanceNARoles.Add(item.Role); + } + } + } + } + + RoleAssignInfo[] NARoleCounts = AlwaysNARoles.Distinct().Select(GetAssignInfo).ToArray().AddRangeToArray(ChanceNARoles.Distinct().Select(GetAssignInfo).ToArray()); + NAs = NARoleCounts; + + // Assign roles set to 100% + if (readyNeutralApocalypseNum < optNeutralApocalypseNum) + { + while (AlwaysNARoles.Any() && optNeutralApocalypseNum > 0) + { + var selected = AlwaysNARoles[rd.Next(0, AlwaysNARoles.Count)]; + var info = NARoleCounts.FirstOrDefault(x => x.Role == selected); + AlwaysNARoles.Remove(selected); + if (info.AssignedCount >= info.MaxCount) continue; + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyNeutralApocalypseNum++; + + NAs = NARoleCounts; + + if (readyRoleNum >= playerCount) goto EndOfAssign; + if (readyNeutralApocalypseNum >= optNeutralApocalypseNum) break; + } + } + + // Assign other roles when needed + if (readyRoleNum < playerCount && readyNeutralApocalypseNum < optNeutralApocalypseNum) + { + while (ChanceNARoles.Any() && optNeutralApocalypseNum > 0) + { + var selectesItem = rd.Next(0, ChanceNARoles.Count); + var selected = ChanceNARoles[selectesItem]; + var info = NARoleCounts.FirstOrDefault(x => x.Role == selected); + + // Remove 'x' times + for (int j = 0; j < info.SpawnChance / 5; j++) + ChanceNARoles.Remove(selected); + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyNeutralApocalypseNum++; + + NAs = NARoleCounts; + + if (info.AssignedCount >= info.MaxCount) while (ChanceNARoles.Contains(selected)) ChanceNARoles.Remove(selected); + + if (readyRoleNum >= playerCount) goto EndOfAssign; + if (readyNeutralApocalypseNum >= optNeutralApocalypseNum) break; + } + } + } } // Crewmate Roles diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index a404a24ce9..dab1b9b4b6 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -25,6 +25,7 @@ internal class Captain : RoleBase private static OptionItem CaptainCanTargetNC; private static OptionItem CaptainCanTargetNE; private static OptionItem CaptainCanTargetNK; + private static OptionItem CaptainCanTargetNA; private static readonly Dictionary OriginalSpeed = []; private static readonly Dictionary> CaptainVoteTargets = []; @@ -44,6 +45,7 @@ public static void SetupCustomOption() CaptainCanTargetNC = BooleanOptionItem.Create(Id + 18, "CaptainCanTargetNC", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNE = BooleanOptionItem.Create(Id + 19, "CaptainCanTargetNE", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNK = BooleanOptionItem.Create(Id + 20, "CaptainCanTargetNK", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); + CaptainCanTargetNA = BooleanOptionItem.Create(Id + 22, "CaptainCanTargetNA", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); OverrideTasksData.Create(Id + 21, TabGroup.CrewmateRoles, CustomRoles.Captain); } @@ -146,7 +148,8 @@ public override bool OnTaskComplete(PlayerControl pc, int completedTaskCount, in (CaptainCanTargetNB.GetBool() && x.GetCustomRole().IsNB()) || (CaptainCanTargetNE.GetBool() && x.GetCustomRole().IsNE()) || (CaptainCanTargetNC.GetBool() && x.GetCustomRole().IsNC()) || - (CaptainCanTargetNK.GetBool() && x.GetCustomRole().IsNeutralKillerTeam()))).ToList(); + (CaptainCanTargetNK.GetBool() && x.GetCustomRole().IsNeutralKillerTeam()) + || (CaptainCanTargetNA.GetBool() && x.GetCustomRole().IsNA()))).ToList(); Logger.Info($"Total Number of Potential Target {allTargets.Count}", "Total Captain Target"); if (allTargets.Count == 0) return true; diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index c8839f78f5..8a22ff09e5 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -22,6 +22,7 @@ internal class Jailer : RoleBase private static OptionItem NCCanBeExe; private static OptionItem NECanBeExe; private static OptionItem NKCanBeExe; + private static OptionItem NACanBeExe; private static OptionItem CKCanBeExe; private static OptionItem NotifyJailedOnMeetingOpt; @@ -41,6 +42,7 @@ public static void SetupCustomOption() NCCanBeExe = BooleanOptionItem.Create(Id + 13, "JailerNCCanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); NECanBeExe = BooleanOptionItem.Create(Id + 14, "JailerNECanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); NKCanBeExe = BooleanOptionItem.Create(Id + 15, "JailerNKCanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); + NACanBeExe = BooleanOptionItem.Create(Id + 17, "JailerNACanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); CKCanBeExe = BooleanOptionItem.Create(Id + 16, "JailerCKCanBeExe", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); NotifyJailedOnMeetingOpt = BooleanOptionItem.Create(Id + 18, "notifyJailedOnMeeting", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); } @@ -176,8 +178,9 @@ private static bool CanBeExecuted(CustomRoles role) { return ((role.IsNB() && NBCanBeExe.GetBool()) || (role.IsNC() && NCCanBeExe.GetBool()) || - (role.IsNE() && NCCanBeExe.GetBool()) || + (role.IsNE() && NECanBeExe.GetBool()) || (role.IsNK() && NKCanBeExe.GetBool()) || + (role.IsNA() && NACanBeExe.GetBool()) || (role.IsCK() && CKCanBeExe.GetBool()) || (role.IsImpostorTeamV3())); } diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index e49c422efd..4b1f601424 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -20,12 +20,14 @@ internal class Snitch : RoleBase private static OptionItem OptionEnableTargetArrow; private static OptionItem OptionCanGetColoredArrow; private static OptionItem OptionCanFindNeutralKiller; + private static OptionItem OptionCanFindNeutralApocalypse; private static OptionItem OptionCanFindMadmate; private static OptionItem OptionRemainingTasks; private static bool EnableTargetArrow; private static bool CanGetColoredArrow; private static bool CanFindNeutralKiller; + private static bool CanFindNeutralApocalypse; private static bool CanFindMadmate; private static int RemainingTasksToBeFound; @@ -41,6 +43,7 @@ public static void SetupCustomOption() OptionEnableTargetArrow = BooleanOptionItem.Create(Id + 10, "SnitchEnableTargetArrow", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionCanGetColoredArrow = BooleanOptionItem.Create(Id + 11, "SnitchCanGetArrowColor", true, TabGroup.CrewmateRoles, false).SetParent(OptionEnableTargetArrow); OptionCanFindNeutralKiller = BooleanOptionItem.Create(Id + 12, "SnitchCanFindNeutralKiller", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OptionCanFindNeutralApocalypse = BooleanOptionItem.Create(Id + 15, "SnitchCanFindNeutralApocalypse", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionCanFindMadmate = BooleanOptionItem.Create(Id + 14, "SnitchCanFindMadmate", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionRemainingTasks = IntegerOptionItem.Create(Id + 13, "SnitchRemainingTaskFound", new(0, 10, 1), 1, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Snitch); @@ -52,6 +55,7 @@ public override void Init() EnableTargetArrow = OptionEnableTargetArrow.GetBool(); CanGetColoredArrow = OptionCanGetColoredArrow.GetBool(); CanFindNeutralKiller = OptionCanFindNeutralKiller.GetBool(); + CanFindNeutralApocalypse = OptionCanFindNeutralApocalypse.GetBool(); CanFindMadmate = OptionCanFindMadmate.GetBool(); RemainingTasksToBeFound = OptionRemainingTasks.GetInt(); @@ -91,7 +95,7 @@ private static bool GetExpose(PlayerControl pc) } private static bool IsSnitchTarget(PlayerControl target) - => HasEnabled && (target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate)); + => HasEnabled && (target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)); private static void CheckTask(PlayerControl snitch) { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs new file mode 100644 index 0000000000..e9959e66a0 --- /dev/null +++ b/Roles/Neutral/Baker.cs @@ -0,0 +1,170 @@ +using AmongUs.GameOptions; +using Hazel; +using TOHE.Roles.Core; +using TOHE.Roles.Impostor; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; +using static UnityEngine.ParticleSystem.PlaybackState; + +namespace TOHE.Roles.Neutral; + +internal class Baker : RoleBase +{ + //===========================SETUP================================\\ + private static readonly int Id = 28200; + public static readonly HashSet playerIdList = []; + public static bool HasEnabled => playerIdList.Any(); + public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + //==================================================================\\ + + private static OptionItem BreadNeededToTransform; + + private static readonly Dictionary> BreadList = []; + private static bool CanUseAbility; + + public static void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Baker, 1, zeroOne: false); + BreadNeededToTransform = IntegerOptionItem.Create(Id + 10, "BakerBreadNeededToTransform", new(1, 5, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]) + .SetValueFormat(OptionFormat.Times); + } + public override void Init() + { + playerIdList.Clear(); + BreadList.Clear(); + CanUseAbility = false; + } + public override void Add(byte playerId) + { + playerIdList.Add(playerId); + BreadList[playerId] = []; + CanUseAbility = true; + CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); + } + + private static (int, int) BreadedPlayerCount(byte playerId) + { + int breaded = 0, all = BreadNeededToTransform.GetInt(); + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.PlayerId == playerId) continue; + + if (HasBread(playerId, pc.PlayerId)) + breaded++; + } + return (breaded, all); + } + public static void SendRPC(PlayerControl player, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WritePacked((int)CustomRoles.Baker); + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte BakerId = reader.ReadByte(); + byte BreadHolderId = reader.ReadByte(); + BreadList[BakerId].Add(BreadHolderId); + } + public override string GetProgressText(byte playerId, bool comms) => CustomRoles.Baker.RoleExist() ? ColorString(GetRoleColor(CustomRoles.Baker).ShadeColor(0.25f), $"({BreadedPlayerCount(playerId).Item1}/{BreadedPlayerCount(playerId).Item2})") : ""; + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => BreadList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; + public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + hud.KillButton.OverrideText(GetString("BakerKillButtonText")); + } + private static bool HasBread(byte pc, byte target) + { + return BreadList[pc].Contains(target); + } + private static bool AllHasBread(PlayerControl player) + { + if (!player.Is(CustomRoles.Baker)) return false; + + var (countItem1, countItem2) = BreadedPlayerCount(player.PlayerId); + return countItem1 >= countItem2; + } + + public override void OnReportDeadBody(PlayerControl marg, PlayerControl iscute) + { + CanUseAbility = true; + } + private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) + { + foreach (var playerId in BreadList.Keys.ToArray()) + { + if (deadPlayer.PlayerId == playerId) + { + BreadList[playerId].Remove(playerId); + } + } + } + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (!CanUseAbility) + { + killer.Notify(GetString("BakerBreadUsedAlready")); + return false; + } + if (target.IsNeutralApocalypse()) + { + killer.Notify(GetString("BakerCantBreadApoc")); + return false; + } + if (HasBread(killer.PlayerId, target.PlayerId)) + { + killer.Notify(GetString("BakerAlreadyBreaded")); + return false; + } + BreadList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("BakerBreaded")); + Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); + CanUseAbility = false; + return false; + } + public override void OnFixedUpdate(PlayerControl player) + { + if (!AllHasBread(player) || player.Is(CustomRoles.Famine)) return; + + player.RpcSetCustomRole(CustomRoles.Famine); + player.Notify(GetString("BakerToFamine")); + player.RpcGuardAndKill(player); + KillIfNotEjected(player); + + } + public static void KillIfNotEjected(PlayerControl player) + { + var deathList = new List(); + var baker = player.PlayerId; + if (Main.AfterMeetingDeathPlayers.ContainsKey(baker)) return; + foreach (var pc in Main.AllAlivePlayerControls) + { + var notBaker = pc.PlayerId; + if (pc.IsNeutralApocalypse() || HasBread(baker, notBaker)) continue; + if (player != null && player.IsAlive()) + { + if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + { + pc.SetRealKiller(player); + deathList.Add(pc.PlayerId); + } + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } + } + else return; + } + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Starved, [.. deathList]); + } +} diff --git a/Roles/Impostor/Berserker.cs b/Roles/Neutral/Berserker.cs similarity index 56% rename from Roles/Impostor/Berserker.cs rename to Roles/Neutral/Berserker.cs index f425ce56a7..10fc01bebb 100644 --- a/Roles/Impostor/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -1,7 +1,11 @@ using UnityEngine; using TOHE.Modules; +using TOHE.Roles.Impostor; +using static TOHE.Options; +using static TOHE.Translator; +using AmongUs.GameOptions; -namespace TOHE.Roles.Impostor; +namespace TOHE.Roles.Neutral; internal class Berserker : RoleBase { @@ -27,33 +31,44 @@ internal class Berserker : RoleBase //public static OptionItem BerserkerSpeed; private static OptionItem BerserkerFourCanNotKill; private static OptionItem BerserkerImmortalLevel; + private static OptionItem WarKillCooldown; + private static OptionItem BerserkerHasImpostorVision; + private static OptionItem WarHasImpostorVision; + private static OptionItem BerserkerCanVent; + private static OptionItem WarCanVent; private static readonly Dictionary BerserkerKillMax = []; public static void SetupCustomOption() { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Berserker); - BerserkerKillCooldown = FloatOptionItem.Create(Id + 2, "BerserkerKillCooldown", new(25f, 250f, 2.5f), 35f, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]) + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Berserker, 1, zeroOne: false); + BerserkerKillCooldown = FloatOptionItem.Create(Id + 2, "BerserkerKillCooldown", new(25f, 250f, 2.5f), 35f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]) .SetValueFormat(OptionFormat.Seconds); - BerserkerMax = IntegerOptionItem.Create(Id + 3, "BerserkerMax", new(1, 10, 1), 4, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]) + BerserkerMax = IntegerOptionItem.Create(Id + 3, "BerserkerMax", new(1, 10, 1), 4, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]) .SetValueFormat(OptionFormat.Level); - BerserkerOneCanKillCooldown = BooleanOptionItem.Create(Id + 4, "BerserkerOneCanKillCooldown", true, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]); - BerserkerOneKillCooldown = FloatOptionItem.Create(Id + 5, "BerserkerOneKillCooldown", new(10f, 45f, 2.5f), 15f, TabGroup.ImpostorRoles, false).SetParent(BerserkerOneCanKillCooldown) + BerserkerHasImpostorVision = BooleanOptionItem.Create(Id + 15, "BerserkerHasImpostorVision", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + WarHasImpostorVision = BooleanOptionItem.Create(Id + 16, "WarHasImpostorVision", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + BerserkerCanVent = BooleanOptionItem.Create(Id + 17, "BerserkerCanVent", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + WarCanVent = BooleanOptionItem.Create(Id + 18, "WarCanVent", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + BerserkerOneCanKillCooldown = BooleanOptionItem.Create(Id + 4, "BerserkerOneCanKillCooldown", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + BerserkerOneKillCooldown = FloatOptionItem.Create(Id + 5, "BerserkerOneKillCooldown", new(10f, 45f, 2.5f), 15f, TabGroup.NeutralRoles, false).SetParent(BerserkerOneCanKillCooldown) .SetValueFormat(OptionFormat.Seconds); - BerserkerKillCooldownLevel = IntegerOptionItem.Create(Id + 6, "BerserkerLevelRequirement", new(1, 10, 1), 1, TabGroup.ImpostorRoles, false).SetParent(BerserkerOneCanKillCooldown) + BerserkerKillCooldownLevel = IntegerOptionItem.Create(Id + 6, "BerserkerLevelRequirement", new(1, 10, 1), 1, TabGroup.NeutralRoles, false).SetParent(BerserkerOneCanKillCooldown) .SetValueFormat(OptionFormat.Level); - BerserkerTwoCanScavenger = BooleanOptionItem.Create(Id + 7, "BerserkerTwoCanScavenger", true, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]); - BerserkerScavengerLevel = IntegerOptionItem.Create(Id + 8, "BerserkerLevelRequirement", new(1, 10, 1), 2, TabGroup.ImpostorRoles, false).SetParent(BerserkerTwoCanScavenger) + BerserkerTwoCanScavenger = BooleanOptionItem.Create(Id + 7, "BerserkerTwoCanScavenger", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + BerserkerScavengerLevel = IntegerOptionItem.Create(Id + 8, "BerserkerLevelRequirement", new(1, 10, 1), 2, TabGroup.NeutralRoles, false).SetParent(BerserkerTwoCanScavenger) .SetValueFormat(OptionFormat.Level); - BerserkerThreeCanBomber = BooleanOptionItem.Create(Id + 9, "BerserkerThreeCanBomber", true, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]); - BerserkerBomberLevel = IntegerOptionItem.Create(Id + 10, "BerserkerLevelRequirement", new(1, 10, 1), 3, TabGroup.ImpostorRoles, false).SetParent(BerserkerThreeCanBomber) + BerserkerThreeCanBomber = BooleanOptionItem.Create(Id + 9, "BerserkerThreeCanBomber", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + BerserkerBomberLevel = IntegerOptionItem.Create(Id + 10, "BerserkerLevelRequirement", new(1, 10, 1), 3, TabGroup.NeutralRoles, false).SetParent(BerserkerThreeCanBomber) .SetValueFormat(OptionFormat.Level); - //BerserkerFourCanFlash = BooleanOptionItem.Create(Id + 11, "BerserkerFourCanFlash", true, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]); - //BerserkerSpeed = FloatOptionItem.Create(611, "BerserkerSpeed", new(1.5f, 5f, 0.25f), 2.5f, TabGroup.ImpostorRoles, false).SetParent(BerserkerOneCanKillCooldown) + //BerserkerFourCanFlash = BooleanOptionItem.Create(Id + 11, "BerserkerFourCanFlash", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + //BerserkerSpeed = FloatOptionItem.Create(611, "BerserkerSpeed", new(1.5f, 5f, 0.25f), 2.5f, TabGroup.NeutralRoles, false).SetParent(BerserkerOneCanKillCooldown) // .SetValueFormat(OptionFormat.Multiplier); - BerserkerFourCanNotKill = BooleanOptionItem.Create(Id + 12, "BerserkerFourCanNotKill", true, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Berserker]); - BerserkerImmortalLevel = IntegerOptionItem.Create(Id + 13, "BerserkerLevelRequirement", new(1, 10, 1), 4, TabGroup.ImpostorRoles, false).SetParent(BerserkerFourCanNotKill) + BerserkerFourCanNotKill = BooleanOptionItem.Create(Id + 12, "BerserkerFourCanNotKill", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]); + BerserkerImmortalLevel = IntegerOptionItem.Create(Id + 13, "BerserkerLevelRequirement", new(1, 10, 1), 4, TabGroup.NeutralRoles, false).SetParent(BerserkerFourCanNotKill) .SetValueFormat(OptionFormat.Level); + WarKillCooldown = FloatOptionItem.Create(Id + 14, "WarKillCooldown", new(0f, 150f, 2.5f), 15f, TabGroup.NeutralRoles, false).SetParent(BerserkerFourCanNotKill) + .SetValueFormat(OptionFormat.Seconds); } public override void Init() { @@ -70,8 +85,29 @@ public override void Remove(byte playerId) BerserkerKillMax.Remove(playerId); } - public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = BerserkerKillCooldown.GetFloat(); - + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override void SetKillCooldown(byte id) + { + if (CustomRoles.Berserker.RoleExist()) + Main.AllPlayerKillCooldown[id] = BerserkerKillCooldown.GetFloat(); + else if (CustomRoles.War.RoleExist()) + Main.AllPlayerKillCooldown[id] = WarKillCooldown.GetFloat(); + } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + if (pc.Is(CustomRoles.Berserker) && BerserkerCanVent.GetBool()) return true; + if (pc.Is(CustomRoles.War) && WarCanVent.GetBool()) return true; + return false; + } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (CustomRoles.Berserker.RoleExist()) + opt.SetVision(BerserkerHasImpostorVision.GetBool()); + else if (CustomRoles.War.RoleExist()) + opt.SetVision(WarHasImpostorVision.GetBool()); + } + public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (BerserkerKillMax[target.PlayerId] >= BerserkerImmortalLevel.GetInt() && BerserkerFourCanNotKill.GetBool()) @@ -85,6 +121,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { + if (target.IsNeutralApocalypse()) return false; if (BerserkerKillMax[killer.PlayerId] < BerserkerMax.GetInt()) { BerserkerKillMax[killer.PlayerId]++; @@ -138,6 +175,12 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } } } + if (BerserkerKillMax[killer.PlayerId] >= BerserkerImmortalLevel.GetInt() && BerserkerFourCanNotKill.GetBool()) + { + killer.RpcSetCustomRole(CustomRoles.War); + killer.Notify(GetString("BerserkerToWar")); + Main.AllPlayerKillCooldown[killer.PlayerId] = WarKillCooldown.GetFloat(); + } return true; } diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index c663004ee0..f85a969d0f 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -20,6 +20,7 @@ internal class Executioner : RoleBase private static OptionItem CanTargetNeutralBenign; private static OptionItem CanTargetNeutralEvil; private static OptionItem CanTargetNeutralChaos; + private static OptionItem CanTargetNeutralApocalypse; private static OptionItem KnowTargetRole; private static OptionItem ChangeRolesAfterTargetKilled; @@ -56,6 +57,7 @@ public static void SetupCustomOption() CanTargetNeutralBenign = BooleanOptionItem.Create(Id + 14, "ExecutionerCanTargetNeutralBenign", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); CanTargetNeutralEvil = BooleanOptionItem.Create(Id + 15, "ExecutionerCanTargetNeutralEvil", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); CanTargetNeutralChaos = BooleanOptionItem.Create(Id + 16, "ExecutionerCanTargetNeutralChaos", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanTargetNeutralApocalypse = BooleanOptionItem.Create(Id + 17, "ExecutionerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); KnowTargetRole = BooleanOptionItem.Create(Id + 13, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); ChangeRolesAfterTargetKilled = StringOptionItem.Create(Id + 11, "ExecutionerChangeRolesAfterTargetKilled", EnumHelper.GetAllNames(), 1, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); } @@ -82,6 +84,7 @@ public override void Add(byte playerId) else if (!CanTargetNeutralBenign.GetBool() && target.GetCustomRole().IsNB()) continue; else if (!CanTargetNeutralEvil.GetBool() && target.GetCustomRole().IsNE()) continue; else if (!CanTargetNeutralChaos.GetBool() && target.GetCustomRole().IsNC()) continue; + else if (!CanTargetNeutralApocalypse.GetBool() && target.GetCustomRole().IsNA()) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 488a386043..5c4fc498d5 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -65,6 +65,9 @@ public override void SetKillCooldown(byte id) else Main.AllPlayerKillCooldown[id] = PestilenceCDOpt.GetFloat(); } + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); private static bool IsPlagued(byte pc, byte target) { @@ -158,7 +161,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - return killer.Is(CustomRoles.Pestilence); + return killer.Is(CustomRoles.Pestilence) && !target.IsNeutralApocalypse(); } public override string GetProgressText(byte playerId, bool comms) diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index d83c2852e9..4e51e49829 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -16,6 +16,8 @@ internal class SoulCollector : RoleBase private static OptionItem SoulCollectorPointsOpt; private static OptionItem CollectOwnSoulOpt; + private static OptionItem CallMeetingIfDeath; + private static OptionItem GetPassiveSouls; private static readonly Dictionary SoulCollectorTarget = []; private static readonly Dictionary SoulCollectorPoints = []; @@ -23,10 +25,12 @@ internal class SoulCollector : RoleBase public static void SetupCustomOption() { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulCollector); + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulCollector, 1, zeroOne: false); SoulCollectorPointsOpt = IntegerOptionItem.Create(Id + 10, "SoulCollectorPointsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]) .SetValueFormat(OptionFormat.Times); CollectOwnSoulOpt = BooleanOptionItem.Create(Id + 11, "CollectOwnSoulOpt", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); + CallMeetingIfDeath = BooleanOptionItem.Create(Id + 12, "CallMeetingIfDeath", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); + /*GetPassiveSouls = BooleanOptionItem.Create(Id + 13, "GetPassiveSouls", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]);*/ } public override void Init() { @@ -51,7 +55,7 @@ public override void Add(byte playerId) private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WritePacked((int)CustomRoles.Collector); //SetSoulCollectorLimit + writer.WritePacked((int)CustomRoles.SoulCollector); //SetSoulCollectorLimit writer.Write(playerId); writer.Write(SoulCollectorPoints[playerId]); writer.Write(SoulCollectorTarget[playerId]); @@ -72,7 +76,9 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) else SoulCollectorTarget.Add(SoulCollectorId, byte.MaxValue); } - + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override void OnVote(PlayerControl voter, PlayerControl target) { if (DidVote[voter.PlayerId]) return; @@ -96,8 +102,8 @@ public override void OnVote(PlayerControl voter, PlayerControl target) public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) { - foreach (var playerId in SoulCollectorTarget.Keys) - { + foreach (var playerId in SoulCollectorTarget.Keys) + { SoulCollectorTarget[playerId] = byte.MaxValue; DidVote[playerId] = false; } @@ -118,13 +124,40 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i if (SoulCollectorPoints[playerId] >= SoulCollectorPointsOpt.GetInt()) { SoulCollectorPoints[playerId] = SoulCollectorPointsOpt.GetInt(); - if (!CustomWinnerHolder.CheckForConvertedWinner(playerId)) + } + } + } + + public static void KillIfNotEjected(PlayerControl player) + { + var deathList = new List(); + if (Main.AfterMeetingDeathPlayers.ContainsKey(player.PlayerId)) return; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.IsNeutralApocalypse()) continue; + if (player != null && player.IsAlive()) + { + if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + { + pc.SetRealKiller(player); + deathList.Add(pc.PlayerId); + } + else { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.SoulCollector); - CustomWinnerHolder.WinnerIds.Add(playerId); + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); } } + else return; } + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [..deathList]); + } + public override void OnFixedUpdate(PlayerControl player) + { + if (SoulCollectorPoints[player.PlayerId] < SoulCollectorPointsOpt.GetInt() || player.GetCustomRole() is CustomRoles.Death) return; + player.RpcSetCustomRole(CustomRoles.Death); + player.Notify(GetString("SoulCollectorToDeath")); + player.RpcGuardAndKill(player); + if (CallMeetingIfDeath.GetBool()) PlayerControl.LocalPlayer.NoCheckStartMeeting(null); + KillIfNotEjected(player); } - } \ No newline at end of file diff --git a/main.cs b/main.cs index 535fcbb6d9..24035f0965 100644 --- a/main.cs +++ b/main.cs @@ -539,7 +539,6 @@ public enum CustomRoles AntiAdminer, Arrogance, Bard, - Berserker, Blackmailer, Bomber, BountyHunter, @@ -698,16 +697,21 @@ public enum CustomRoles //Neutral Agitater, Amnesiac, + Apocalypse, Arsonist, + Baker, Bandit, + Berserker, BloodKnight, Collector, Cultist, CursedSoul, + Death, Demon, Doomsayer, Doppelganger, Executioner, + Famine, Follower, Glitch, God, @@ -762,6 +766,7 @@ public enum CustomRoles VengefulRomantic, Virus, Vulture, + War, Werewolf, Workaholic, Wraith, @@ -903,11 +908,9 @@ public enum CustomWinner Pickpocket = CustomRoles.Pickpocket, Traitor = CustomRoles.Traitor, Vulture = CustomRoles.Vulture, - Pestilence = CustomRoles.Pestilence, Medusa = CustomRoles.Medusa, Spiritcaller = CustomRoles.Spiritcaller, Glitch = CustomRoles.Glitch, - Plaguebearer = CustomRoles.PlagueBearer, PlagueDoctor = CustomRoles.PlagueDoctor, Masochist = CustomRoles.Masochist, Doomsayer = CustomRoles.Doomsayer, @@ -918,6 +921,7 @@ public enum CustomWinner NiceMini = CustomRoles.Mini, Doppelganger = CustomRoles.Doppelganger, Solsticer = CustomRoles.Solsticer, + Apocalypse = CustomRoles.Apocalypse, } public enum AdditionalWinners { From 97ab9adf96849a5534767756a3c984756a6aa4a5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:41:02 -0400 Subject: [PATCH 002/778] conflict --- Patches/CheckGameEndPatch.cs | 4 +- Resources/Lang/en_US.json | 7184 ++++++++++++++++---------------- Roles/Neutral/SoulCollector.cs | 2 +- 3 files changed, 3595 insertions(+), 3595 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 4fdf4875f9..8b5f147a02 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -5,9 +5,9 @@ using UnityEngine; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.Neutral; -using static TOHE.Translator; using TOHE.Roles.AddOns.Common; -using System.Threading.Channels; +using TOHE.Roles.Core; +using static TOHE.Translator; namespace TOHE; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index bcc2a4da73..c29439ce6e 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1,3594 +1,3594 @@ { - "LanguageID": "0", - "HostText": "Host", - "HostColor": "#902efd", - "IconColor": "#4bf4ff", - "Icon": "♥", - "HideHostText": "Hide 'Host♥' Text", - "NameColor": "#ffc0cb", - "kofi": "Ko-Fi", - "update": "Update", - "GitHub": "GitHub", - "Discord": "Discord", - "Website": "Website", - "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", - - "SubText.Crewmate": "Find and exile the Impostors", - "SubText.Impostor": "Sabotage and kill everyone", - "SubText.Neutral": "Work alone to achieve your victory", - "SubText.Madmate": "Help the Impostors", - - "TypeImpostor": "Impostors", - "TypeCrewmate": "Crewmates", - "TypeNeutral": "Neutrals", - "TypeAddon": "Add-ons", - "GuesserMode": "Guesser Mode", - - "TeamImpostor": "Impostor", - "TeamNeutral": "Neutral", - "TeamCrewmate": "Crewmate", - "TeamMadmate": "Madmate", - - "YouAreCrewmate": "You are a Crewmate", - "YouAreImpostor": "You are an Impostor", - "YouAreNeutral": "You are a Neutral", - "YouAreMadmate": "You are a Madmate", - - - "Role_Crewmate": "Crewmate", - "Role_Jester": "Jester", - "Role_Opportunist": "Opportunist", - "Role_Celebrity": "Celebrity", - "Role_Bodyguard": "Bodyguard", - "Role_Dictator": "Dictator", - "Role_Mayor": "Mayor", - "Role_Doctor": "Doctor", - "Role_Maverick": "Maverick", - "Role_Pursuer": "Pursuer", - "Role_Follower": "Follower", - "Role_Amnesiac": "Amnesiac", - "Role_Imitator": "Imitator", - "Role_Sheriff": "Sheriff", - "Role_Knight": "Knight", - "Role_Deputy": "Deputy", - "Role_NoChange": "Don't change the role", - - "CrewmatesCanGuess": "Crewmates can guess", - "ImpostorsCanGuess": "Impostors can guess", - "NeutralKillersCanGuess": "Neutral Killers can guess", - "PassiveNeutralsCanGuess": "Passive Neutrals can guess", - - "CanGuessAddons": "Can Guess Add-ons", - "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", - "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", - "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", - "GuessImmune": "Sorry, but target is immune to being guessed!", - - - "GM": "Game Master", - "Sunnyboy": "Sunnyboy", - "Bard": "Bard", - "Nuker": "Nuker", - "Crewmate": "Crewmate", - "CrewmateTOHE": "Crewmate", - "Engineer": "Engineer", - "EngineerTOHE": "Engineer", - "Scientist": "Scientist", - "ScientistTOHE": "Scientist", - "GuardianAngel": "Guardian Angel", - "GuardianAngelTOHE": "Guardian Angel", - "Impostor": "Impostor", - "ImpostorTOHE": "Impostor", - "Shapeshifter": "Shapeshifter", - "ShapeshifterTOHE": "Shapeshifter", - - "BountyHunter": "Bounty Hunter", - "Fireworker": "Fireworker", - "Mercenary": "Mercenary", - "ShapeMaster": "Shapemaster", - "Vampire": "Vampire", - "Vampiress": "Vampiress", - "Warlock": "Warlock", - "Ninja": "Ninja", - "Zombie": "Zombie", - "Anonymous": "Anonymous", - "Miner": "Miner", - "KillingMachine": "Killing Machine", - "Escapist": "Escapist", - "Witch": "Witch", - "Nemesis": "Nemesis", - "Bloodmoon": "Bloodmoon", - "Puppeteer": "Puppeteer", - "Mastermind": "Mastermind", - "TimeThief": "Time Thief", - "Sniper": "Sniper", - "Undertaker": "Undertaker", - "RiftMaker": "Rift Maker", - "EvilTracker": "Evil Tracker", - "EvilGuesser": "Evil Guesser", - "AntiAdminer": "Anti Adminer", - "Arrogance": "Arrogance", - "Bomber": "Bomber", - "Scavenger": "Scavenger", - "Trapster": "Trapster", - "Gangster": "Gangster", - "Cleaner": "Cleaner", - "Lightning": "Lightning", - "Greedy": "Greedy", - "CursedWolf": "Cursed Wolf", - "SoulCatcher": "Soul Catcher", - "QuickShooter": "Quick Shooter", - "Camouflager": "Camouflager", - "Eraser": "Eraser", - "Butcher": "Butcher", - "Hangman": "Hangman", - "Swooper": "Swooper", - "Crewpostor": "Crewpostor", - "Wildling": "Wildling", - "Trickster": "Trickster", - "Vindicator": "Vindicator", - "Parasite": "Parasite", - "Disperser": "Disperser", - "Inhibitor": "Inhibitor", - "Saboteur": "Saboteur", - "Councillor": "Councillor", - "Dazzler": "Dazzler", - "Deathpact": "Deathpact", - "Devourer": "Devourer", - "Consigliere": "Consigliere", - "Morphling": "Morphling", - "Twister": "Twister", - "Lurker": "Lurker", - "Visionary": "Visionary", - "Refugee": "Refugee", - "Underdog": "Underdog", - "Ludopath": "Ludopath", - "Godfather": "Godfather", - "Chronomancer": "Chronomancer", - "Pitfall": "Pitfall", - "EvilMini": "Evil Mini", - "Blackmailer": "Blackmailer", - "Instigator": "Instigator", - "LazyGuy": "Lazy Guy", - "SuperStar": "Super Star", - "Celebrity": "Celebrity", - "Cleanser": "Cleanser", - "Keeper": "Keeper", - "Knight": "Knight", - "Mayor": "Mayor", - "Psychic": "Psychic", - "Mechanic": "Mechanic", - "Sheriff": "Sheriff", - "Vigilante": "Vigilante", - "Jailer": "Jailer", - "CopyCat": "Copycat", - "Snitch": "Snitch", - "Marshall": "Marshall", - "SpeedBooster": "Speed Booster", - "Doctor": "Doctor", - "Dictator": "Dictator", - "Detective": "Detective", - "NiceGuesser": "Nice Guesser", - "GuessMaster": "Guess Master", - "Transporter": "Transporter", - "TimeManager": "Time Manager", - "Veteran": "Veteran", - "Bastion": "Bastion", - "Bodyguard": "Bodyguard", - "Deceiver": "Deceiver", - "Grenadier": "Grenadier", - "Medic": "Medic", - "FortuneTeller": "Fortune Teller", - "Judge": "Judge", - "Mortician": "Mortician", - "Medium": "Medium", - "Pacifist": "Pacifist", - "Observer": "Observer", - "Monarch": "Monarch", - "Overseer": "Overseer", - "Coroner": "Coroner", - "Tracker": "Tracker", - "Merchant": "Merchant", - "President": "President", - "Hawk": "Hawk", - "Retributionist": "Retributionist", - "Deputy": "Deputy", - "Investigator": "Investigator", - "Guardian": "Guardian", - "Addict": "Addict", - "Mole": "Mole", - "Alchemist": "Alchemist", - "Tracefinder": "Tracefinder", - "Oracle": "Oracle", - "Spiritualist": "Spiritualist", - "Chameleon": "Chameleon", - "Inspector": "Inspector", - "Captain": "Captain", - "Admirer": "Admirer", - "TimeMaster": "Time Master", - "Crusader": "Crusader", - "Reverie": "Reverie", - "Lookout": "Lookout", - "Telecommunication": "Telecommunication", - "Lighter": "Lighter", - "TaskManager": "Task Manager", - "Witness": "Witness", - "Swapper": "Swapper", - "ChiefOfPolice": "Police Commissioner", - "NiceMini": "Nice Mini", - "Mini": "Mini", - "Spy": "Spy", - "Randomizer": "Randomizer", - "Enigma": "Enigma", - "Jester": "Jester", - "Arsonist": "Arsonist", - "Pyromaniac": "Pyromaniac", - "Kamikaze": "Kamikaze", - "Huntsman": "Huntsman", - "Terrorist": "Terrorist", - "Executioner": "Executioner", - "Lawyer": "Lawyer", - "Opportunist": "Opportunist", - "Vector": "Vector", - "Jackal": "Jackal", - "God": "God", - "Innocent": "Innocent", - "Stealth": "Stealth", - "Penguin": "Penguin", - "Pelican": "Pelican", - "PlagueDoctor": "Plague Scientist", - "Revolutionist": "Revolutionist", - "Hater": "Hater", - "Demon": "Demon", - "Stalker": "Stalker", - "Workaholic": "Workaholic", - "Solsticer": "Solsticer", - "Collector": "Collector", - "Provocateur": "Provocateur", - "BloodKnight": "Blood Knight", - "Apocalypse": "Apocalypse Team", - "PlagueBearer": "Plaguebearer", - "Pestilence": "Pestilence", - "SoulCollector": "Soul Collector", - "Death": "Death", - "Baker": "Baker", - "Famine": "Famine", - "Berserker": "Berserker", - "War": "War", - "Glitch": "Glitch", - "Sidekick": "Sidekick", - "Follower": "Follower", - "Cultist": "Cultist", - "SerialKiller": "Serial Killer", - "Juggernaut": "Juggernaut", - "Infectious": "Infectious", - "Virus": "Virus", - "Pursuer": "Pursuer", - "Phantom": "Phantom", - "Pirate": "Pirate", - "Agitater": "Agitator", - "Maverick": "Maverick", - "CursedSoul": "Cursed Soul", - "Pickpocket": "Pickpocket", - "Traitor": "Traitor", - "Vulture": "Vulture", - "Taskinator": "Taskinator", - "Benefactor": "Benefactor", - "Medusa": "Medusa", - "Spiritcaller": "Spiritcaller", - "Amnesiac": "Amnesiac", - "Imitator": "Imitator", - "Bandit": "Bandit", - "Doppelganger": "Doppelganger", - "Masochist": "Masochist", - "Doomsayer": "Doomsayer", - "Shroud": "Shroud", - "Werewolf": "Werewolf", - "Shaman": "Shaman", - "Seeker": "Seeker", - "Pixie": "Pixie", - "Occultist": "Occultist", - "SchrodingersCat": "Schrodingers Cat", - "Romantic": "Romantic", - "VengefulRomantic": "Vengeful Romantic", - "RuthlessRomantic": "Ruthless Romantic", - "Poisoner": "Poisoner", - "HexMaster": "Hex Master", - "Wraith": "Wraith", - "Jinx": "Jinx", - "PotionMaster": "Potion Master", - "Necromancer": "Necromancer", - "Warden": "Warden", - "Minion": "Minion", - "LastImpostor": "Last Impostor", - "Overclocked": "Overclocked", - "Lovers": "Lovers", - "Madmate": "Madmate", - "Ntr": "Neptune", - "Watcher": "Watcher", - "Flash": "Flash", - "Torch": "Torch", - "Seer": "Seer", - "Tiebreaker": "Tiebreaker", - "Oblivious": "Oblivious", - "Bewilder": "Bewilder", - "Sunglasses": "Sunglasses", - "Workhorse": "Workhorse", - "Fool": "Fool", - "Avanger": "Avenger", - "Youtuber": "YouTuber", - "Egoist": "Egoist", - "TicketsStealer": "Stealer", - "Schizophrenic": "Schizophrenic", - "Mimic": "Mimic", - "Guesser": "Guesser", - "Necroview": "Necroview", - "Reach": "Reach", - "Charmed": "Charmed", - "Cleansed": "Cleansed", - "Bait": "Bait", - "Trapper": "Beartrap", - "Infected": "Infected", - "Onbound": "Onbound", - "Rebound": "Rebound", - "Mundane": "Mundane", - "Knighted": "Knighted", - "Unreportable": "Disregarded", - "Contagious": "Contagious", - "Lucky": "Lucky", - "Unlucky": "Unlucky", - "VoidBallot": "Void Ballot", - "Aware": "Aware", - "Fragile": "Fragile", - "DoubleShot": "Double Shot", - "Rascal": "Rascal", - "Soulless": "Soulless", - "Gravestone": "Gravestone", - "Lazy": "Lazy", - "Autopsy": "Autopsy", - "Loyal": "Loyal", - "EvilSpirit": "Evil Spirit", - "Recruit": "Recruit", - "Admired": "Admired", - "Glow": "Glow", - "Diseased": "Diseased", - "Antidote": "Antidote", - "Stubborn": "Stubborn", - "Swift": "Swift", - "Ghoul": "Ghoul", - "Bloodlust": "Bloodlust", - "Mare": "Mare", - "Burst": "Burst", - "Sleuth": "Sleuth", - "Clumsy": "Clumsy", - "Nimble": "Nimble", - "Circumvent": "Circumvent", - "Cyber": "Cyber", - "Hurried": "Hurried", - "Oiiai": "OIIAI", - "Influenced": "Influenced", - "Silent": "Silent", - "Susceptible": "Susceptible", - "Tricky": "Tricky", - "Rainbow": "Rainbow", - "Tired": "Tired", - "Statue": "Statue", - "BracketAddons": "Add Brackets To Add-ons", - "EngineerTOHEInfo": "Use the vents to catch the Impostors", - "ScientistTOHEInfo": "Access portable vitals from anywhere", - "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", - "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", - "ImpostorTOHEInfo": "Kill and sabotage", - "CrewmateTOHEInfo": "Search for the Impostors", - "BountyHunterInfo": "Eliminate your target", - "FireworkerInfo": "Go out with a BANG", - "MercenaryInfo": "Keep killing, else you suicide", - "ShapeMasterInfo": "Swiftly kill with no shift cooldown", - "VampireInfo": "Your kills are delayed", - "VampiressInfo": "Your kills are delayed and direct", - "WarlockInfo": "Curse crewmates then shift to make them kill", - "NinjaInfo": "Mark a target, then shift to kill", - "ZombieInfo": "You are very slow", - "AnonymousInfo": "Force a player to report a body", - "MinerInfo": "Warp to your last used vent by shifting", - "KillingMachineInfo": "You can ONLY kill, but low cooldown", - "EscapistInfo": "Shift to mark places and warp back to them", - "WitchInfo": "Spell crewmates to kill them in meetings", - "NemesisInfo": "Kill when you're the last Impostor", - "BeforeNemesisInfo": "You can't kill yet", - "AfterNemesisInfo": "Now start killing", - "BloodmoonInfo": "Seek havoc upon the crewmates", - "PuppeteerInfo": "Make players kill for you", - "MastermindInfo": "Make others kill for you", - "TimeThiefInfo": "Lower meeting time by killing", - "SniperInfo": "Snipe players from a distance by shifting", - "UndertakerInfo": "Teleport dead body to a marked location", - "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", - "EvilTrackerInfo": "Track players by shifting", - "AntiAdminerInfo": "Know when players are near devices", - "ArroganceInfo": "With each kill you make, your cooldown decreases", - "BomberInfo": "Shapeshift to explode", - "TrapsterInfo": "Trap your kills", - "ScavengerInfo": "Your kills are unreportable", - "EvilGuesserInfo": "Guess crew roles in meetings to kill", - "GangsterInfo": "Convert players to your side", - "CleanerInfo": "Report bodies to make them unreportable", - "LightningInfo": "Convert players to Quantum Ghosts", - "GreedyInfo": "Your kill cooldown shifts", - "CursedWolfInfo": "You survive a few kill attempts", - "SoulCatcherInfo": "You swap places with your shift target", - "QuickShooterInfo": "Store ammo to offset kill cooldown", - "CamouflagerInfo": "Camouflage everyone for easy kills", - "EraserInfo": "Erase the role of your vote target", - "ButcherInfo": "Enjoy my beautiful work", - "HangmanInfo": "I will decide when your life will end", - "SwooperInfo": "Turn invisible temporarily", - "CrewpostorInfo": "Kill by completing tasks", - "WildlingInfo": "Kill with strength and disguise", - "TricksterInfo": "Kill and trick the crew", - "VindicatorInfo": "Use your extra votes to kill everyone", - "ParasiteInfo": "Help the Impostors kill the crew", - "DisperserInfo": "Teleport everyone to random vents", - "InhibitorInfo": "You cannot kill during sabotages", - "SaboteurInfo": "You can only kill during sabotages", - "CouncillorInfo": "Kill off crewmates during meetings", - "DazzlerInfo": "Reduce the vision of the crew", - "DeathpactInfo": "Assign players to a death pact", - "DevourerInfo": "Consume the skin of the crew", - "ConsigliereInfo": "Discover the roles of other players", - "MorphlingInfo": "You can only kill while shapeshifted", - "TwisterInfo": "Swap all player positions", - "LurkerInfo": "Reduce your kill cooldown by venting", - "ConvictInfo": "Your target died, now help the Impostors", - "VisionaryInfo": "You see the alignments of the living", - "RefugeeInfo": "Help the Impostors kill off the crew", - "UnderdogInfo": "Start killing on a low player count", - "LudopathInfo": "Your kill cooldown is random", - "GodfatherInfo": "Convert players to Refugees by voting", - "ChronomancerInfo": "Kill in bursts", - "PitfallInfo": "Setup traps around the map", - "EvilMiniInfo": "No one can hurt you until you grow up", - "BlackmailerInfo": "Silence other players", - "InstigatorInfo": "Sow discord among the crewmates", - "LazyGuyInfo": "You're too lazy", - "SuperStarInfo": "Everyone knows you", - "CleanserInfo": "Erase All Addons of your vote target", - "KeeperInfo": "Reject the Eject, Keeper Protect!", - "MayorInfo": "Your vote counts multiple times", - "PsychicInfo": "One of the red names are evil", - "MechanicInfo": "Vent around and fix sabotages", - "SheriffInfo": "Shoot the Impostors", - "VigilanteInfo": "Not the hero we deserved but the hero we needed", - "JailerInfo": "Jail suspicious players", - "CopyCatInfo": "Use kill button to copy target's role", - "SnitchInfo": "Finish your tasks to find the Impostors", - "MarshallInfo": "Finish your tasks to prove your innocence", - "SpeedBoosterInfo": "Boost your speed", - "DoctorInfo": "Know how each player died", - "DictatorInfo": "Exile a player based on your own judgement", - "DetectiveInfo": "Gain extra info from your body reports", - "UndercoverInfo": "Impostors see you as their partner", - "KnightInfo": "You can kill 1 player", - "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", - "GuessMasterInfo": "Whispers heard, every guessed word.", - "TransporterInfo": "Do tasks to swap 2 players' locations", - "TimeManagerInfo": "Increase meeting time by doing tasks", - "VeteranInfo": "Alert to kill anyone who interacts with you", - "BastionInfo": "Bomb vents", - "BodyguardInfo": "Prevent nearby kills", - "DeceiverInfo": "Try to fool the players", - "GrenadierInfo": "Reduce Impostors' vision by venting", - "MedicInfo": "Cast a shield onto a player", - "FortuneTellerInfo": "Get clues to people's roles", - "JudgeInfo": "Silence in the courtroom!", - "MorticianInfo": "Locate dead bodies", - "MediumInfo": "Talk with ghosts", - "ObserverInfo": "You can see all shield-animations", - "PacifistInfo": "Vent to reset kill cooldowns", - "MonarchInfo": "Give your crew extra voting power!", - "StealthInfo": "Killing Blinds Everyone in the Room", - "PenguinInfo": "Drag your victims", - "OverseerInfo": "Reveal roles of other players", - "CoronerInfo": "Find corpses and their killers", - "TrackerInfo": "Keep track of other players", - "PresidentInfo": "You are in charge of the meeting", - "MerchantInfo": "Sell add-ons and bribe killers", - "RetributionistInfo": "Help the crew after you die", - "HawkInfo": "Seek murdering the bad guys!", - "DeputyInfo": "Handcuff killers to increase their cooldowns", - "InvestigatorInfo": "Find potential evils", - "GuardianInfo": "Complete your tasks to become immortal", - "AddictInfo": "Vent to become invulnerable, or you'll die", - "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", - "AlchemistInfo": "Brew potions by completing tasks", - "TracefinderInfo": "Sense the location of dead bodies", - "OracleInfo": "Vote a player to see their alignment", - "SpiritualistInfo": "Be guided by the ghostly life", - "ChameleonInfo": "Vent to disguise into your surroundings", - "InspectorInfo": "Validate the alignments of two players", - "CaptainInfo": "Sail with the Captain, lest addons be abandoned.", - "AdmirerInfo": "Choose a player to side with you", - "TimeMasterInfo": "Rewind time!", - "CrusaderInfo": "Kill a player's attacker", - "ReverieInfo": "With each kill, your cooldown decreases", - "LookoutInfo": "See through disguises", - "TelecommunicationInfo": "Track device usage", - "LighterInfo": "Catch killers with your enhanced vision", - "TaskManagerInfo": "See the total tasks completed in real time", - "WitnessInfo": "Find out if someone killed recently", - "SwapperInfo": "Swap the votes of two players", - "ChiefOfPoliceInfo": "Recruit as a Sheriff by killing players with knives", - "NiceMiniInfo": "No one can hurt you until you grow up.", - "ArsonistInfo": "Douse everyone and ignite", - "PyromaniacInfo": "Douse and kill everyone", - "HuntsmanInfo": "Kill your targets for a low cooldown", - "SpyInfo": "You know who interacts with you", - "RandomizerInfo": "You're going to be someone's burden when you die?", - "EnigmaInfo": "Get Clues about Killers", - "JesterInfo": "Get voted out", - "OpportunistInfo": "Stay alive until the end", - "TerroristInfo": "Finish your tasks, THEN die", - "ExecutionerInfo": "Get your target voted out", - "LawyerInfo": "Help your target win!", - "VectorInfo": "Jump in! Jump out!", - "JackalInfo": "Murder everyone", - "GodInfo": "Everything is under your control", - "InnocentInfo": "Get someone ejected by making them kill you", - "PelicanInfo": "Eat all players", - "RevolutionistInfo": "Recruit players to win with you", - "HaterInfo": "Kill Lovers and Neptunes", - "DemonInfo": "Consume blood volumes", - "StalkerInfo": "Descend into the darkness, release fear!", - "WorkaholicInfo": "Finish all tasks to solo win!", - "SolsticerInfo": "Speed run all your tasks!", - "CollectorInfo": "Collect votes from players", - "ProvocateurInfo": "Victory with help target", - "BloodKnightInfo": "Killing gives you a temporary shield", - "PlagueBearerInfo": "Plague everyone to turn into Pestilence", - "PestilenceInfo": "Obliterate everyone!", - "SoulCollectorInfo": "Predict deaths to collect souls", - "DeathInfo": "Enact Armageddon", - "BakerInfo": "Feed Players Bread in order to become Famine", - "FamineInfo": "Starve Everyone", - "BerserkerInfo": "Kill to increase your level", - "WarInfo": "Destroy everything", - "GlitchInfo": "Hack and kill everyone", - "SidekickInfo": "Help the Jackal kill everyone", - "FollowerInfo": "Follow a player and help them", - "CultistInfo": "Charm everyone", - "SerialKillerInfo": "Kill off everyone to win!", - "JuggernautInfo": "With each kill, your cooldown decreases", - "InfectiousInfo": "Infect everyone", - "VirusInfo": "Kill and infect everyone", - "PursuerInfo": "Protect yourself and live to the end!", - "PlagueDoctorInfo": "Spread the infection!", - "PhantomInfo": "Get killed and finish your tasks to win!", - "PirateInfo": "Successfully plunder players to win", - "AgitaterInfo": "Pass a Bomb onto others", - "MaverickInfo": "Kill and survive to the end", - "CursedSoulInfo": "Snatch souls and steal the win", - "PickpocketInfo": "Steal votes from your kills", - "TraitorInfo": "Eliminate the Impostors, then win", - "VultureInfo": "Eat bodies by reporting to win", - "TaskinatorInfo": "Silent tasks, deadly blasts", - "BenefactorInfo": "Task complete, shield elite!", - "MedusaInfo": "Stone bodies by reporting them", - "SpiritcallerInfo": "Turn Players into Evil Spirits", - "AmnesiacInfo": "Remember the role of a dead body", - "ImitatorInfo": "Imitate a player's role", - "BanditInfo": "Rob a player's add-on", - "DoppelgangerInfo": "Steal your target's identity", - "MasochistInfo": "Get attacked a few times to win!", - "KamikazeInfo": "Kill players with a suicidal mission", - "DoomsayerInfo": "Successfully guess players to win", - "ShroudInfo": "Shroud players to make them kill", - "WerewolfInfo": "Kill crewmates in groups", - "ShamanInfo": "Deflect all the attacks on Voodoo doll", - "SeekerInfo": "Play Hide and Seek with your target", - "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", - "OccultistInfo": "Kill and curse your enemies", - "SchrodingersCatInfo": "The cat is both alive and dead until observed.", - "RomanticInfo": "Protect your partner to win together", - "VengefulRomanticInfo": "Revenge your partner to win together", - "RuthlessRomanticInfo": "Kill everyone to win with your partner", - "PoisonerInfo": "Kill everyone with delayed kills", - "HexMasterInfo": "Hex players to kill them in meetings", - "WraithInfo": "Vent to temporarily go invisible", - "JinxInfo": "Reflect attacks onto your attackers", - "PotionMasterInfo": "Use your potions to your advantage", - "NecromancerInfo": "Kill your killer to defy death", - "WardenInfo": "(Ghost) Alert about danger", - "MinionInfo": "(Ghost) Blind enemies", - "LoversInfo": "Stay alive and win together", - "MadmateInfo": "Help the Impostors", - "NtrInfo": "Everyone sees you as their Lover", - "WatcherInfo": "You see all the colors of votes", - "LastImpostorInfo": "Lower kill cooldown", - "OverclockedInfo": "Lower cooldown", - "FlashInfo": "You're faster", - "TorchInfo": "You have enhanced vision!", - "SeerInfo": "You are alerted when somebody is killed", - "TiebreakerInfo": "Break tied votes", - "ObliviousInfo": "You can't report bodies", - "BewilderInfo": "A twist of vision, a web of confusion", - "WorkhorseInfo": "Be the first to complete all tasks and get more", - "FoolInfo": "You can't fix sabotages", - "AvangerInfo": "You take someone with you upon death", - "YoutuberInfo": "Get killed first to win", - "EgoistInfo": "Win on your own", - "TicketsStealerInfo": "Gain votes with kills", - "SchizophrenicInfo": "You're dead and alive simultaneously", - "MimicInfo": "Reveal killed players' roles to impostors upon death", - "GuesserInfo": "Guess roles of players in meetings to kill", - "NecroviewInfo": "See the team of the dead", - "ReachInfo": "You have a longer kill range", - "BaitInfo": "Your killer self-reports your body", - "TrapperInfo": "Freeze your killer for a few seconds", - "OnboundInfo": "You can't be guessed", - "ReboundInfo": "Guess me right, and face your plight!", - "MundaneInfo": "Tasks all done, guessing's begun.", - "UnreportableInfo": "Your body can't be reported", - "LuckyInfo": "Dodge attackers", - "DoubleShotInfo": "You have an extra life when guessing", - "RascalInfo": "You appear evil in some cases", - "SoullessInfo": "You have no soul", - "GravestoneInfo": "Your role is revealed when you die", - "LazyInfo": "You're too lazy", - "AutopsyInfo": "You see how others died", - "LoyalInfo": "You cannot be recruited", - "EvilSpiritInfo": "You are an evil Spirit", - "RecruitInfo": "Help the Jackal", - "AdmiredInfo": "The Admirer chose you as their love", - "GlowInfo": "You glow in the dark", - "DiseasedInfo": "Increase the cooldown of player who interacts with you", - "AntidoteInfo": "Decrease the cooldown of player who interacts with you", - "StubbornInfo": "Protect your role and addons", - "SwiftInfo": "Your kills don't cause a lunge", - "UnluckyInfo": "Doing things has a chance to kill you", - "VoidBallotInfo": "Your vote count is 0", - "AwareInfo": "Know who revealed your role", - "FragileInfo": "Die instantly if someone uses kill button on you", - "GhoulInfo": "Kill your killer after dying", - "BloodlustInfo": "Unleash your bloodlust and kill", - "SunglassesInfo": "You have reduced vision!", - "MareInfo": "Kill in the darkness", - "BurstInfo": "Make your killer burst!", - "SleuthInfo": "Gain info from dead bodies", - "ClumsyInfo": "You have a chance to miss your kill", - "NimbleInfo": "You can vent!", - "CircumventInfo": "You can no longer vent", - "OiiaiInfo": "OIIAIOIIIAI", - "CyberInfo": "You're popular!", - "HurriedInfo": "God I got too much stuffs!", - "InfluencedInfo": "You lack decisiveness!", - "SilentInfo": "Vote like a Ghost!", - "SusceptibleInfo": "Deathreason lotto!", - "TrickyInfo": "Tricky slays, in mysterious ways.", - "TiredInfo": "Labor makes you rest Zzz..", - "StatueInfo": "You're still as a rock nearby people", - "GMInfo": "Spectate the chaos!", - "NotAssignedInfo": "No assigned role", - "SunnyboyInfo": "Shine, shine my sunshine!", - "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in dark embrace.", - "NukerInfo": "Shapeshift to nuke everyone", - "RainbowInfo": "Colorful melodies! You don't even know your own color.", - "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", - "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time which shows you who is alive and who is dead.", - "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", - "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", - "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", - "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", - "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow, if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased.The Target swaps after a certain amount of time.", - "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworker, up to the max amount set by host.\nWhen you are the last Impostor and all Fireworker have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworker, it's considered an Impostor victory.", - "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", - "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", - "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. If a meeting is called first, your target still dies. If you bite a Bait, you kill normally and report the body.", - "VampiressInfoLong": "(Impostors):\nAs the Vampiress, you can Bite players like a Vampire (single click) or kill normally (double click).", - "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", - "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown, but moves very slowly and has very little vision. Zombie will not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", - "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", - "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", - "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", - "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown, but cannot vent, have Crewmate vision, cannot sabotage, cannot report, and cannot call emergency meetings.\n\nNote: You will bypass any and all shield, killing bait and beartrap won't take any effect", - "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport, be careful).", - "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", - "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID is typed. Use /id to show the IDs of all players, or look next to their names.", - "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon attack the enemies to make them drip blood, this means they will die in a time set by host, and will be aware of it.", - "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", - "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button. But if the target has a kill button of any time, they'll be told after a delay that they were manipulated and they must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", - "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", - "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", - "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out and all the rifts will be destroyed.\n\nNote: Up to two rifts can be placed at a time, if you try to place a third, it removes the first one.", - "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other people, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", - "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies, and if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog and/or other devices. Note: Anti Adminer does not know for sure if the player is using the device while near it, they only know that someone is near the device.", - "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", - "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill-flash when the Bomber explodes.", - "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", - "TrapsterInfoLong": "(Impostors):\nAs the Trapster, your main method of killing is by body reports.\nWhen someone tries to report a body you killed, they'll die.", - "GangsterInfoLong": "(Impostors):\nThe Gangster can attempt to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", - "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned up body cannot be reported (including bait's).", - "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they come into contact with to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", - "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always an odd kill.", - "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The max of times you can counterattack is set by the host)", - "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", - "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets he can use one to bypass kill cooldown, he will kill even if it's still on cooldown, and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (Number is set by the host).", - "CamouflagerInfoLong": "(Impostors):\nWhen Camouflager uses Shapeshift, all players start to look exactly the same. This state ends when Camouflager reverts its shape-shifting. Note: the skills of communication sabotage camouflage and skills of Camouflager can be superimposed.\nSkill will be invalid if a meeting is held during the skill activation of the Camouflager", - "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players whose skills are erased will always be considered a vanilla role, including the game result page.\nA player can only be erased once(include Oiiai)", - "ButcherInfoLong": "(Impostors):\nButcher kills (including passive kills) have multiple dead bodies on targets, making it impossible to accurately identify other dead body when reporting. Note: Due to the principle of implementation, the killed target has to repeatedly display the animation of being killed. This animation cannot be skipped and cannot participate in the meeting normally during this period. In addition, if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", - "HangmanInfoLong": "(Impostors):\nThe killing method of the Hangman during the shapeshifting is strangling. Strangling ignores any status of the target, such as the shield of the Medic, the protection of the Bodyguard, the skills of the Super Star, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), in addition, Seer will not be prompted.", - "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", - "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you complete a task.", - "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but lack the ability to vent.\nWhen you kill, you temporarily become immune to attacks.", - "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", - "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", - "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", - "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain target by pressing the kill button, and drag around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period of time.\nPress the kill button twice for a direct kill.", - "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", - "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not be teleported with shapeshift and players who are in the vent cannot be teleported.", - "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (eg Lights or Reactor), you cannot kill.", - "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (eg Comms or O2), then you can kill.", - "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", - "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, the targets of your shapeshifting are marked for a deathpact.\nIf enough players are marked for a death pact, the marked players must meet within a defined period of time; if they fail to do so, they die.\nIf a marked player dies before the death pact is completed, the pact is withdrawn.", - "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to permanently change the appearance of the target of the shapeshift. Additionally, for each player's appearance changed, your kill cooldown is reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", - "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshift.", - "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shape-shifting to randomly swap the position of all players. The swap happens twice, once when you start your shape shift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone and players who are in vents cannot be teleported.", - "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown is reset to its original value.", - "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following info will be displayed on the player.:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in range of the infected player becomes infected themselves.\nInfection progress is cumulative, and does not reset with distance or after meetings.", - "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor, or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", - "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", - "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", - "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", - "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round if someone kills the target, the killer will turn into a Refugee.", - "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you can charge up your kill button. Once activated the Chronomancer can use their kill button infinitely until they run out of charge.", - "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized for a short period of time and their vision will be affected.", - "EvilMiniInfoLong": "(Impostors):\nAs an Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which is drastically shortened as you grow up.", - "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target you will blackmail that player, and the blackmailed player cannot speak.\n\nSpeaking by the blackmailed player will trigger the confusion command, please do not speak when the blackmailed player sees his icon", - "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate is voted out in a meeting, as long as you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The number of additional players dying is determined by the host.", - "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task In addition, Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for the Anonymous, marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", - "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only be killed when the Murderer is alone with the Super Star (regular kills only). In addition, the Super Star cannot be guessed by Guessers.", - "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", - "CleanserInfoLong": "(Crewmates):\nCleanser can vote for any target at the meeting to erase the target's Add-ons, and the erasure will take effect after the meeting ends. Depending on the settings cleansed player may never get add on in future", - "KeeperInfoLong": "(Crewmates):\nAs keeper you can vote someone to protect them from being ejected. You can only do this a configurable amount of times.", - "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. As a setting, these votes can be hidden, you can vent to call a meeting at any time, and you are revealed as Mayor upon tasks completion.", - "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting, at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", - "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, Communications by using only one side. Lights can be fixed by flicking only one switch. Opening a door will open all doors in the map.", - "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", - "VigilanteInfoLong": "(Crewmates):\nThe Vigilante is tasked with eliminating potential threats to the crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster can not convert Vigilante into madmate.", - "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or be voted (vote count will be 0). The Jailer may choose to execute the prisoner by voting them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote : Jailed players cannot be guessed or judged and jailed players can only guess Jailer.", - "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see Impostors names being displayed in red on meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", - "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", - "SpeedBoosterInfoLong": "(Crewmates):\nSpeed Booster increase their movement speed every time they complete a task. Note: due to technical limitations, the Speed Booster appears to be at a normal speed to others, so they look like glitch.", - "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, Doctor can access vitals wherever you are while he still have battery.", - "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes someone, the meeting will end on the spot and the player they voted will be ejected. The moment the Dictator vote someone out, Dictator will also die.", - "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", - "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", - "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", - "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role guesser tried to guess, and you will also be notified in case of a misguess.", - "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill any person but they can only do it once the whole game.", - "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", - "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", - "VeteranInfoLong": "(Crewmates):\nVeteran can enter the alert state by venting. If a player tries to kill the veteran in the alert state, the veteran will kill the murderer instead. Veteran will see a shield-animation on their body and a text displayed above their head as a reminder when they enter and exit the alert state.", - "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though, crewmates can also be killed with the bombs.", - "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil that has a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to copycat after every meeting", - "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate and the murderer is an Impostor, the Bodyguard will not activate the skill.", - "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the kill ability has the counterfeit, he will suicide when he tries to kill someone next time.", - "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", - "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game, when the Medic dies, the target's shield will be removed. The Medic can also see if someone is trying to break the target's shield.\nDepending on the host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", - "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote:- If the setting to give random active players as hint is on, you will not be able to check same player multiple times", - "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the host), and if it is wrong, the judge commits suicide.\nThe judgment command is: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", - "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", - "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after their dead body is reported. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", - "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. This typically indicates the use of some role ability, so look out for this.", - "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has extra votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exhiled.", - "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", - "OverseerInfoLong": "(Crewmates):\nAs a Overseer you have very limited vision but you can use your kill button to reveal the role of a nearby player. Use the kill button to start the reveal, a 「○」 will be displayed next to the reveal target. Stay near the target for a defined time to reveal his role, if you move too far away from the target the reveal will be aborted.", - "CoronerInfoLong": "(Crewmates):\nAs a Coroner you can't report corpses, instead after trying to report the corpse you will see an arrow leading you to the killer. If a meeting is called, the arrows disappear. Depending on the setting, the body you found cannot be reported.", - "TrackerInfoLong": "(Crewmates):\nAs a Tracker, you can vote for a player in the meeting, which will mark their position for you in the game with arrows. In addition, at the beginning of a meeting you will be shown in which room the player was last, if the option is activated.", - "PresidentInfoLong": "(Crewmates):\nThe President has 2 abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", - "MerchantInfoLong": "(Crewmates):\\As a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can avert the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The bribe money used is lost and is not available for additional bribes.", - "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", - "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk you can kill a limited amount of players decided by host, tough there's a chance you miss, slicing someone multiple times increases the chances.", - "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", - "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in either red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when a meeting is called.", - "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon tasks completion. You can't even be guessed in meetings.", - "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is at 0 seconds, you still have a short time to vent.\nIf you don't make it you die, if you make it the suicide timer is reset.\nAlso, after you are ventilated, no one can interact with you for a defined period of time.\nAfter this period is over, you are immobilized for another defined period of time and cannot report any bodies.", - "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you come out of the vent, you will spawn near a random vent in the map (Except the one you just used).", - "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", - "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by host.", - "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", - "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability, if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", - "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", - "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message If they are in the same team, or a denial message if they are not in the same team.\n\nAll neutrals and converted playes are counted in the same team. Trickster is counted as crew and Rascal is counted as Impostor.\nChecking command : /cmp [player id 1] [player id 2]", - "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non crew role. Crewmates can see ☆ besides captain's name.\n\nIf anyone betrays the captain's trust by voting captain out, they will lose an addon.", - "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", - "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will be rewinded back to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", - "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", - "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the host's setting you may misfire on reaching the max kill cooldown and your target dies with you. \n\nYou win with other crewmates.", - "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", - "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, doorlogs, or admin.", - "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", - "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real time.", - "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings)", - "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", - "ChiefOfPoliceInfoLong": "(Crewmates):\nPlayers with swords can be recruited to join the sheriff's team to serve the crew, but players without swords cannot be recruited.\n note: only one recruitment opportunity", - "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, you can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses.", - "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability that is used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you have no ability uses left, you won't see orange names at all!\nNote: If the kill button interaction is blocked the player's cooldown will reset to 10s'", - "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player", - "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse by clicking the kill button on the player and following them for a few seconds. When the dousing starts and it's successful, a shield animation will be displayed as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he failed to kill everyone, he loses.", - "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting, depending on the setting, you may have to report the body to receive a clue. The more tasks you complete the more precise the clues get.", - "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", - "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double click to kill normally. When you die all marked also die, with death reason Targeted.", - "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you have a certain amount of targets that reset every meeting. If you kill one of your targets, your kill cooldown decreases by the set amount permanately. If you kill someone else other than any of your targets, your kill cooldown permanately increases by the set amount. You see your targets with a colored name.", - "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini is two roles. Either a Nice Mini or an Evil Mini is chosen.\n\nUse '/r nicemini' and '/r evilmini' respectively for more details.", - "JesterInfoLong": "(Neutrals):\nIf the Jester get voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", - "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", - "ExecutionerInfoLong": "(Neutrals):\nExecutioner has an execution target, which will be indicated by a diamond 「♦」 next to their name. If the execution target is killed, the Executioner will be changed to Crewmate, Jester or Opportunist according to the settings. If the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", - "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", - "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", - "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", - "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill normally (i.e. don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, then they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", - "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you snatch the win, i.e. everyone else loses and you win.", - "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target is voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", - "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those who are swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", - "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by host, to kill players (though they are still recruited). When the required number of players are recruited, (displayed next to your name) you must vent within the specified time in order to win the game immediately with all of your recruits. If you do not vent in time, you lose and die.", - "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, you can only kill Lovers, and other recruiting roles and add-ons, depending on the settings. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", - "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", - "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause electricity sabotage (if electricity is already sabotaged, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker win alone.", - "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on host's settings, you can only win if alive and/or you are revealed to everyone at the beginning (these settings are almost never both on).", - "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting is finished, your tasks get reset, and you need to start all over again.\nVotes on the Solsticer will be directly cancelled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in game.", - "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required number of votes, the game ends and you win alone, even if you voted a Jester or Executioner's target out.", - "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect, and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", - "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", - "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", - "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill that makes them immortal for a few seconds.", - "ApocalypseInfoLong": "(Apocalypse):\nEvery role of the Apocalypse Team has their own objective to carry out in order to transform.\nTransformed Apocalypse members are immortal, but everyone will be notified that they have transformed.\n\nRoles: Plaguebearer, Soul Collector, Baker, Berserker\nTransformed: Pestilence, Death, Famine, War\n\n(if you got this as a role, you have bugged the game, good job)Your presence is announced to everyone the meeting after you transform.", - "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you vote players to predict their death. If the prediction is correct and the target dies in the next round you collect their soul. \n\nOnce you collect the configurable amount of souls, you become Death.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Depending on the host's settings, a meeting may or may not be called immediately. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nYou are invincible and your presence is announced to everyone the meeting after you transform.", - "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", - "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nYou are invincible and your presence is announced to everyone the meeting after you transform.", - "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", - "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone the meeting after you transform.", - "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", - "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", - "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive. Depending on settings, you will not be impacted by any harmful interactions and may have a teammate.", - "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", - "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you then can simply outnumber the crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", - "VirusInfoLong": "(Neutrals):\nThe task of the virus is to kill or infect all other players. When the virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected and joins the virus team or dies at the end of the meeting if the virus won't get voted out, dependent on the settings. If there are more players on the Virus team than on the Crewmate team, the Virus team wins.", - "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, just survive to the end of the game.", - "PhantomInfoLong": "(Neutrals):\nAs the Phantom, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", - "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both Pirate and the target chooses same number, Pirate wins.\nAdditionally, if Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command:- /duel X (where X can be 0, 1 or 2)\n\nYou win after winning a certain number of duels set by the host.\n\nNote: If target did not participate in duel, the kill will not count towards pirate victory", - "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", - "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", - "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you snatch the victory if you survive to the end of the game.\n\nYou can snatch the win from a Jester or Executioner.\n\nAdditionally, you can snatch the souls of other players.\nSoulless players win with you and count as dead.", - "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\nThese votes are hidden.\n\nKill everyone to win.", - "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors but they don't know you.\nThe twist? They can kill you but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", - "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", - "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you complete a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", - "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you complete a task, the task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks", - "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", - "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players for a short time and/or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", - "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", - "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee or some Neutral", - "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button once to steal a player's addon and twice to kill. Depending on the settings, you may instantly steal the addon or after the meeting starts. After the max number of steals are reached you will kill normally. Additionally, if there are no stealable addons present on the target or the target is stubborn you will kill the target.\n\nKill everyone to win.\n\nNote:- Cleansed, Last Impostor and Lovers can not be stolen.\nNote:- If Bandit can vent is on, Nimble will become unstealable", - "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote:- You can not steal the identity of the target when Camouflage is active.", - "MasochistInfoLong": "(Neutrals):\nAs the Masochist, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", - "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings) then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themself after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", - "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", - "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.", - "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If seeker tags wrong player a point is deducted and if seeker tags correct player a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target\n\n The seeker needs to collect certain number of points set by the host to win", - "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark upto x amount of targets each round by using kill button on them. When the meeting starts, your job is to have one of the marked targets ejected. If unsuccessful you will suicide, except if you didn't mark any targets or all the targets are dead. The selected targets resets to 0 after the meeting ends. If you succeed you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the host.", - "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use their kill button on you, the interaction is not blocked, and you will die.", - "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield which protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote : If your role changes your win condition will be changed accordingly", - "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As Ruthless Romantic, you win if you kill everyone and be the last one standing. If you win your dead partner also wins with you.", - "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non neutral killer) is killed. As a Vengeful Romantic, Your goal is to avenge your partner, which means you have to kill the killer of your partner. If you succeed to do so, then both you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", - "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", - "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", - "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you are attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", - "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons defaults to killing.", - "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally, when someone tries to kill you, the kill will be blocked and you will be teleported to a random vent. You will have a limited time to kill your killer. If you succeed to do so, you live. If the time runs out before you kill your killer, you die permanately. If you try to kill someone else other than your killer, you will die.", - "LastImpostorInfoLong": "(Add-ons):\nThis effect is given to the last surviving Impostor. Reduces their kill cooldown.", - "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nOnly assigned to roles with a kill button.", - "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when only the Lovers are left. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 mark next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", - "MadmateInfoLong": "(Add-ons):\nOnly Crewmate can become Madmate. Madmate's task is to help the Impostors win the game, Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone.", - "NtrInfoLong": "(Add-ons):\nWhen there is Neptune, all players will see that they are Lovers with Neptune, and they will not die in love together and will not change the win conditions. Note: Lovers won't become Neptune, and Neptune won't become Lovers.", - "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", - "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the host)", - "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", - "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", - "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreaker choose different tie targets at the same time, the skills of the Tiebreaker will not take effect.", - "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still be reported automatically, and Oblivious can still be used as a scapegoat for the Anonymous.", - "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder is killed, the murderer's vision may become the same as the Bewilder's vision depending on the settings.", - "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, Workhorse will give the player extra tasks. The amount of additional tasks are set by the host.", - "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", - "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out and unconventional kills are not counted), the Avenger will revenge a random player.", - "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to be killed in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc. will not trigger the skills of the YouTuber.", - "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", - "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the vote number is set by the host, and the decimal is rounded down). Also, extra votes from the Stealer are hidden during meeting.", - "SchizophrenicInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Schizophrenic, you will be considered as two players in the game for the purpose of determining when the game ends due to killers having majority. Additionally, this grants you an extra vote, depending on options.", - "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called, this message will include information on roles who were killed by the Mimic.", - "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess roles of players in meetings to kill them.\nGuessing incorrectly kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. You have the longest kill range possible in the game, unlike everyone else.", - "BaitInfoLong": "(Add-ons):\nWhen the Bait is killed, the murderer who killed the Bait will be forced to self-report the Bait's body. However, this won't happen when the Bait is killed by a Scavenger, Cleaner, Swooper, Wraith, or Killing Machine. The report may have a delay according to Host's settings.", - "TrapperInfoLong": "(Add-ons):\nWhen Beartrap is killed, Beartrap immobilize killer for a configurable amount of time.", - "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", - "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", - "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", - "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", - "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", - "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess after all your tasks has been finished.", - "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", - "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse cannot be reported.", - "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", - "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on there is a probability for you to evade the kill, the specific probability is set by the host. When the evasion takes effect, the killer will see the shield-animation, but you not know anything.", - "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", - "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", - "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul snatches your soul, you get this add-on.\n\nYou are not counted as alive.", - "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", - "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", - "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", - "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to temporarily give the Spiritcaller a shield against a kill attempt.", - "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", - "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", - "GlowInfoLong": "(Add-ons):\nAs the Glow, your name is colored during a lights sabotage.", - "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be increased by configurable amount of time", - "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be decreased by configurable amount of time", - "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add on, Eraser can’t erase your role, Cleanser can't cleanse you, Bandit can't steal from you and Monarch can't knight you.\nAdditionally, you can’t gain any new addons from the merchant", - "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.", - "UnluckyInfoLong": "(Add-ons):\nAs the Unlucky, doing tasks, killing, or venting as a chance to kill you.", - "VoidBallotInfoLong": "(Add-ons):\nHolder of this addon will have 0 vote count", - "AwareInfoLong": "(Add-ons):\nAs the Aware, you will be notified in the next meeting if a revealing role had interacted with you", - "FragileInfoLong": "(Add-ons):\nAs Fragile, you will die instantly if someone tries to use kill button on you (even if the role can not directly kill)", - "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on tasks completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nOnly assigned to crewmates, and not crewmates with no tasks or are task based.", - "BloodlustInfoLong": "(Add-ons):\nAs the Bloodlust, doing tasks allows you to kill.\nWhen you complete a task, the next player you come in contact with dies.\n\nYour bloodlust remains after a meeting.\nUpon making a kill, your bloodlust clears till the next task you complete.\nBloodlusts do not stack.\n\nOnly assigned to crewmates with tasks.", - "SunglassesInfoLong": "(Add-ons):\nAs the Sunglasses, your vision is reduced.", - "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", - "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", - "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", - "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset and the target remains untouched.\n\nOnly assigned to killers.", - "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", - "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", - "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced,then the vote result won't shift\nCollector cannot become influenced.", - "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", - "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", - "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", - "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they complete a task, they will temporarily get lower vision & lower speed.", - "StatueInfoLong": "(Add-ons):\nWhenever many people are near Statue, the Statue is completely frozen or slowed down dependand on settings.", - "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot be killed while in a group.\nAdditionally, your death will be known.", - "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you failed with your tasks, you lose.\nHurried hurries to his goal so it won't get madmate,charmed or so.", - "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", - "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy", - "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence has no effect on the game, and all players know who the Game Master is. The Game Master role will be assigned to the host, who will automatically become a ghost at the start of the game.", - "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. When you are alive, the game will not end due to killers gaining majority.\nAdditionally, you have access to portable vitals.", - "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown is permanently halved.", - "NukerInfoLong": "(Impostors):\nAs the Nuker, you're a stronger Bomber.\n\nShapeshift to nuke everyone.", - "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", - "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", - "ShowTextOverlay": "Text Overlay", - "Overlay.GuesserMode": "Guesser Mode", - "Overlay.NoGameEnd": "No Game End", - "Overlay.DebugMode": "Debug Mode", - "Overlay.LowLoadMode": "Low Load Mode", - "Overlay.AllowConsole": "Console", - "DisableShieldAnimations": "Disable Unnecessary Shield Animations", - "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", - "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", - "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", - "AbilityUseLimit": "Initial Ability Use Limit", - "ShowArrows": "Has Arrows pointing toward bodies", - "ArrowDelayMin": "Minimum Arrow show-up delay", - "ArrowDelayMax": "Maximum Arrow show-up delay", - "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", - "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", - - "AbilityCD": "Ability Cooldown", - "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", - "ShowSpecificRole": "Know specific roles on Task Completion", - "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", - "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", - "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", - "DisableMeeting": "Disable Meetings", - "DisableCloseDoor": "Disable Doors Sabotage", - "DisableSabotage": "Disable Sabotages", - "NoGameEnd": "No Game End", - "AllowConsole": "BepInEx Console", - "DebugMode": "Debug Mode", - "SyncButtonMode": "Sync Buttons Mode", - "RandomMapsMode": "Random Maps Mode", - "SyncedButtonCount": "Max Number of Emergency Meetings Allowed", - "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", - "HHFailureKCDIncrease": "Kill cooldown increase on killing others", - "HHNumOfTargets": "Number of targets", - "Targets": "Targets: ", - "HHMaxKCD": "Maximum kill cooldown", - "HHMinKCD": "Minimum kill cooldown", - "AllAliveMeeting": "Meeting When No One is Dead", - "AllAliveMeetingTime": "Meeting Time When No One is Dead", - "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", - "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", - "AdditionalEmergencyCooldownTime": "Additional Cooldown", - "LadderDeath": "Fall From Ladders", - "LadderDeathChance": "Fall To Death Chance", - "DisableSwipeCardTask": "Disable Swipe Card Task", - "DisableSubmitScanTask": "Disable Submit Scan Task", - "DisableUnlockSafeTask": "Disable Unlock Safe Task", - "DisableUploadDataTask": "Disable Upload Data Task", - "DisableStartReactorTask": "Disable Start Reactor Task", - "DisableResetBreakerTask": "Disable Reset Breakers Task", - "DisableShortTasks": "Disable Short Tasks", - "DisableCleanVent": "Disable Clean Vent Task", - "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", - "DisableChartCourse": "Disable Chart Course Task", - "DisableStabilizeSteering": "Disable Stabilize Steering Task", - "DisableCleanO2Filter": "Disable Clean O2 Filter Task", - "DisableUnlockManifolds": "Disable Unlock Manifolds Task", - "DisablePrimeShields": "Disable Prime Shields Task", - "DisableMeasureWeather": "Disable Measure Weather", - "DisableBuyBeverage": "Disable Buy Beverage", - "DisableAssembleArtifact": "Disable Assemble Artifact Task", - "DisableSortSamples": "Disable Sort Samples Task", - "DisableProcessData": "Disable Process Data Task", - "DisableRunDiagnostics": "Disable Run Diagnostics Task", - "DisableRepairDrill": "Disable Repair Drill Task", - "DisableAlignTelescope": "Disable Align Telescope Task", - "DisableRecordTemperature": "Disable Record Temperature Task", - "DisableFillCanisters": "Disable Fill Canisters Task", - "DisableMonitorTree": "Disable Monitor Tree Task", - "DisableStoreArtifacts": "Disable Store Artifacts Task", - "DisablePutAwayPistols": "Disable Put Away Pistols Task", - "DisablePutAwayRifles": "Disable Put Away Rifles Task", - "DisableMakeBurger": "Disable Make Burger Task", - "DisableCleanToilet": "Disable Clean Toilet Task", - "DisableDecontaminate": "Disable Decontaminate Task", - "DisableSortRecords": "Disable Sort Records Task", - "DisableFixShower": "Disable Fix Shower Task", - "DisablePickUpTowels": "Disable Pick Up Towels Task", - "DisablePolishRuby": "Disable Polish Ruby Task", - "DisableDressMannequin": "Disable Dress Mannequin Task", - "DisableCommonTasks": "Disable Common Tasks", - "DisableFixWiring": "Disable Fix Wiring Task", - "DisableEnterIdCode": "Disable Enter ID Code Task", - "DisableInsertKeys": "Disable Insert Keys Task", - "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", - "DisableLongTasks": "Disable Long Tasks", - "DisableAlignEngineOutput": "Disable Align Engine Output Task", - "DisableInspectSample": "Disable Inspect Sample Task", - "DisableEmptyChute": "Disable Empty Chute Task", - "DisableClearAsteroids": "Disable Clear Asteroids Task", - "DisableWaterPlants": "Disable Water Plants Task", - "DisableOpenWaterways": "Disable Open Waterways Task", - "DisableReplaceWaterJug": "Disable Replace Water Jug Task", - "DisableRebootWifi": "Disable Reboot Wifi Task", - "DisableDevelopPhotos": "Disable Develop Photos Task", - "DisableRewindTapes": "Disable Rewind Tapes Task", - "DisableStartFans": "Disable Start Fans Task", - "DisableOtherTasks": "Disable Situational Tasks", - "DisableEmptyGarbage": "Disable Empty Garbage Task", - "DisableFuelEngines": "Disable Fuel Engines Task", - "DisableDivertPower": "Disable Divert Power Task", - "DisableActivateWeatherNodes": "Disable Weather Nodes Task", - "DisableRoastMarshmallow": "Disable Roast Marshmallow", - "DisableCollectSamples": "Disable Collect Samples", - "DisableReplaceParts": "Disable Replace Parts", - "DisableCollectVegetables": "Disable Collect Vegetables", - "DisableMineOres": "Disable Mine Ores", - "DisableExtractFuel": "Disable Extract Fuel", - "DisableCatchFish": "Disable Catch Fish", - "DisablePolishGem": "Disable Polish Gem", - "DisableHelpCritter": "Disable Help Critter", - "DisableHoistSupplies": "Disable Hoist Supplies", - "DisableFixAntenna": "Disable Fix Antenna", - "DisableBuildSandcastle": "Disable Build Sandcastle", - "DisableCrankGenerator": "Disable Crank Generator", - "DisableMonitorMushroom": "Disable Monitor Mushroom", - "DisablePlayVideoGame": "Disable Play Video Game", - "DisableFindSignal": "Disable Find Signal", - "DisableThrowFisbee": "Disable Throw Frisbee", - "DisableLiftWeights": "Disable Lift Weights", - "DisableCollectShells": "Disable Collect Shells", - "SuffixMode": "Suffix", - "SuffixMode.None": "None", - "SuffixMode.Version": "Version", - "SuffixMode.Streaming": "Streaming", - "SuffixMode.Recording": "Recording", - "SuffixMode.RoomHost": "Room Host", - "SuffixMode.OriginalName": "Original Name", - "SuffixMode.DoNotKillMe": "Don't kill me", - "SuffixMode.NoAndroidPlz": "No phones", - "SuffixMode.AutoHost": "Auto-Host", - "SuffixModeText.DoNotKillMe": "Don't kill me", - "SuffixModeText.NoAndroidPlz": "No phones please", - "SuffixModeText.AutoHost": "Auto-hosting", - "FormatNameMode": "Player Name Mode", - "FormatNameModes.None": "Disable", - "FormatNameModes.Color": "Color", - "FormatNameModes.Snacks": "Random", - "DisableEmojiName": "Disable Emoji in names", - "FixFirstKillCooldown": "Override Starting Kill Cooldown", - "FixKillCooldownValue": "Starting Kill Cooldown", - "OverclockedReduction": "Kill Cooldown Reduction", - "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", - "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", - "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", - "GhostIgnoreTasks": "Ghosts Exempt From Tasks", - "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", - "MaxImpGhostRole": "Max Impostor Ghost-Roles", - "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", - "DefaultAngelCooldown": "Default Ability Cooldown", - "DisableTaskWin": "Disable Task Win", - "HideGameSettings": "Hide Game Settings", - "DIYGameSettings": "Enable only custom /n messages", - "Settings:": "Settings:", - "PlayerCanSetColor": "Players can use the /color command", - "PlayerCanSetName": "Players can use the /rn command", - "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", - "PlayerCanUseTP": "Players can use the /tpin and /tpout command", - "CanPlayMiniGames": "Players can play mini games", - "KPDCamouflageMode": "Camouflage Appearance", - "RoleOptions": "Role Options", - "DarkTheme": "Enable Dark Theme", - "AutoStart": "Auto start", - "EnableCustomButton": "Enable Custom Button Images", - "EnableCustomSoundEffect": "Enable Custom Sound Effects", - "SwitchVanilla": "Switch Vanilla", - "ModeForSmallScreen": "Small Screen Mode", - "UnlockFPS": "Unlock FPS", - "ForceOwnLanguage": "Force mod to use your language if possible", - "ForceOwnLanguageRoleName": "Force role names in your language if possible", - "VersionCheat": "Bypass version synchronization check", - "GodMode": "God Mode", - - "AutoDisplayKillLog": "Display Kill-log", - "AutoDisplayLastRoles": "Display Last Roles", - "AutoDisplayLastResult": "Auto Display Last Result", - "RevertOldKillLog": "Revert to old kill-log", - - "HideExileChat": "Hide exile (lava) chat", - "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", - - "EnableYTPlan": "Enable Youtuber Plan", - "KickLowLevelPlayer": "Kick players whose level is lower than", - "TempBanLowLevelPlayer": "Temporarily ban low-level players", - "ApplyWhiteList": "Turn on Whitelist to bypass level kick", - "AllowOnlyWhiteList": "Allow only whitelisted players to join", - "AutoKickStart": "Kick players that say start", - "AutoKickStartTimes": "Number of warnings before kick", - "AutoKickStartAsBan": "Block a player after they're kicked", - "AutoKickStopWords": "Kick players who write banned words", - "AutoKickStopWordsTimes": "Number of warnings for banned words", - "AutoKickStopWordsAsBan": "Block a player after they're kicked", - "AutoWarnStopWords": "Warning to those who write banned words", - "TempBanPlayersWhoKeepQuitting": "Temporarily ban players who leave and join repeatedly", - "QuitTimesTillTempBan": "The quit frequency needed for temp ban", - "KickOtherPlatformPlayer": "Kick Non-PC players", - "OptKickAndroidPlayer": "Kick Android players", - "OptKickIphonePlayer": "Kick iOS players", - "OptKickXboxPlayer": "Kick Xbox players", - "OptKickPlayStationPlayer": "Kick PlayStation players", - "OptKickNintendoPlayer": "Kick Nintendo Switch players", - "ShareLobby": "Allow TOHE-Chan Shares Lobby Code To Discord", - "ShareLobbyMinPlayer": "Share Lobby Code When The Number Of Players Reaches", - "DisableVanillaRoles": "Disable Vanilla Roles", - "VoteMode": "Voting Mode", - "WhenSkipVote": "If the Player Skipped", - "WhenSkipVoteIgnoreFirstMeeting": "Ignore the First Meeting", - "WhenSkipVoteIgnoreNoDeadBody": "Ignore When No Dead Body", - "WhenSkipVoteIgnoreEmergency": "Ignore at Emergency Meetings", - "WhenNonVote": "If the Player didn't vote", - "Default": "No vote", - "Suicide": "Suicide", - "SelfVote": "Self Vote", - "Skip": "Skip", - "WhenTie": "When Tied Vote", - "TieMode.Default": "No ejects", - "TieMode.All": "Eject All", - "TieMode.Random": "Eject Random", - "DisableDevices": "Disable Devices", - "DisableSkeldDevices": "Disable Skeld Devices", - "DisableMiraHQDevices": "Disable MIRA HQ Devices", - "DisablePolusDevices": "Disable Polus Devices", - "DisableAirshipDevices": "Disable Airship Devices", - "DisableFungleDevices": "Disable Fungle Devices", - "DisableSkeldAdmin": "Disable Admin", - "DisableMiraHQAdmin": "Disable Admin", - "DisablePolusAdmin": "Disable Admin", - "DisableAirshipCockpitAdmin": "Disable Cockpit Admin", - "DisableAirshipRecordsAdmin": "Disable Records Admin", - "DisableSkeldCamera": "Disable Cameras", - "DisablePolusCamera": "Disable Cameras", - "DisableAirshipCamera": "Disable Cameras", - "DisableMiraHQDoorLog": "Disable DoorLog", - "DisablePolusVital": "Disable Vitals", - "DisableAirshipVital": "Disable Vitals", - "DisableFungleVital": "Disable Vitals", - "DisableFungleBinoculars": "Disable Binoculars (Not work for vanilla)", - "IgnoreConditions": "Ignore Conditions", - "IgnoreImpostors": "Ignore Impostors", - "IgnoreNeutrals": "Ignore Neutrals", - "IgnoreCrewmates": "Ignore Crewmates", - "IgnoreAfterAnyoneDied": "Ignore After First Death", - "LightsOutSpecialSettings": "Fix Lights Special Settings", - "BlockDisturbancesToSwitches": "Block Switches When They Are Up", - "DisableAirshipViewingDeckLightsPanel": "Disable Viewing Deck Lights Panel (Airship)", - "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", - "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", - "RandomSpawnMode": "Random Spawns Mode", - "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", - "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", - "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", - "CommsCamouflage": "Camouflage during Comms Sabotage", - "DisableOnSomeMaps": "Disable comms camouflage on some maps", - "DisableOnSkeld": "Disable on The Skeld", - "DisableOnMira": "Disable on MIRA HQ", - "DisableOnPolus": "Disable on Polus", - "DisableOnDleks": "Disable on dlekS ehT", - "DisableOnAirship": "Disable on Airship", - "DisableOnFungle": "Disable on The Fungle", - "DisableReportWhenCC": "Disable body reporting while camouflaged", - "EnableDebugMode": "Enable Debug Mode", - "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", - "SendRoleDescriptionFirstMeeting": "Show Role Descriptions to Unmodded Clients at First Meeting", - "RoleAssigningAlgorithm": "Role Assigning Algorithm", - "RoleAssigningAlgorithm.Default": "Default", - "RoleAssigningAlgorithm.NetRandom": ".NET System.Random", - "RoleAssigningAlgorithm.HashRandom": "HashRandom", - "RoleAssigningAlgorithm.Xorshift": "Xorshift", - "RoleAssigningAlgorithm.MersenneTwister": "MersenneTwister", - "MapModification": "Map Modifications", - "DisableAirshipMovingPlatform": "Disable Moving Platform (Airship)", - "AirshipVariableElectrical": "Variable Electrical (Airship)", - "DisableSporeTriggerOnFungle": "Disable Spore Trigger (Fungle)", - "DisableZiplineOnFungle": "Disable Zipline (Fungle)", - "DisableZiplineFromTop": "Disable Use From Top", - "DisableZiplineFromUnder": "Disable Use From Under", - "ResetDoorsEveryTurns": "Reset Doors After Meeting (Airship/Polus/Fungle)", - "DoorsResetMode": "Reset Doors Mode", - "AllOpen": "All Open", - "AllClosed": "All Closed", - "RandomByDoor": "Closed Random", - "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", - "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", - "DecontaminationTimeOnPolus": "Decontamination Time On Polus", - "ApplyDenyNameList": "Apply DenyName List", - "KickPlayerFriendCodeNotExist": "Kick players without a friend code", - "TempBanPlayerFriendCodeNotExist": "Temp Ban players without a friend code", - "ApplyBanList": "Apply BanList", - "EndWhenPlayerBug": "End the game when a player has a critical error", - "RemovePetsAtDeadPlayers": "Remove pets at dead players", - "KillFlashDuration": "Kill-Flash Duration", - "ConfirmEjectionsMode": "Confirm Ejections Mode", - "ConfirmEjections.None": "None", - "ConfirmEjections.Team": "Team", - "ConfirmEjections.Role": "Role", - "ShowImpRemainOnEject": "Show remaining Impostors on ejects", - "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", - "ConfirmEgoistOnEject": "Confirm Egoists on ejection", - "ConfirmLoversOnEject": "Confirm Lovers on ejection", - "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", - "HideBittenRolesOnEject": "Hide roles of bitten players on ejection", - "ShowTeamNextToRoleNameOnEject": "Show what team the ejected player's role is on", - "Ban": "Ban", - "Kick": "Kick", - "NoticeMe": "Notify me", - "NoticeEveryone": "Notify everyone", - "TempBan": "Temporary Ban", - "OnlyCancel": "Only Cancel the cheat actions", - "CheatResponses": "When a cheating player is found", - "NeutralRoleWinTogether": "Neutrals win together", - "NeutralWinTogether": "All Neutrals win together", - "MenuTitle.Disable": "★ Disable ★", - "MenuTitle.MapsSettings": "★ Maps ★", - "MenuTitle.Sabotage": "★ Sabotage ★", - "MenuTitle.Meeting": "★ Meeting ★", - "MenuTitle.Ghost": "★ Ghost ★", - "MenuTitle.Other": "★ Different ★", - "MenuTitle.Ejections": "★ Ejection ★", - "MenuTitle.Settings": "★ Settings ★", - "MenuTitle.TaskSettings": "★ Task Management ★", - "MenuTitle.Guessers": "★ Guesser Mode ★", - "MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★", - "ShieldPersonDiedFirst": "Shield player who dead first in the last game", - "LegacyNemesis": "Use Legacy Version", - "ArsonistKeepsGameGoing": "Arsonist keeps the game going", - "ArsonistCanIgniteAnytime": "Can Ignite Anytime", - "ArsonistMinPlayersToIgnite": "Minimum doused needed for ignite", - "ArsonistMaxPlayersToIgnite": "Maximum doused needed for ignite", - "PuppeteerDoubleKills": "Puppet dies alongside victim", - "MastermindCD": "Manipulate Cooldown", - "MastermindTimeLimit": "Time limit to kill someone", - "MastermindDelay": "Manipulation notification delay", - "ManipulateNotify": "Kill someone in {0}s or die!", - "ManipulatedKilled": "{0} has killed someone", - "SurvivedManipulation": "You survived the Mastermind's manipulation!", - "Glitch_HackCooldown": "Hack Cooldown", - "Glitch_HackDuration": "Hack Duration", - "Glitch_MimicCooldown": "Mimic Cooldown", - "Glitch_MimicDuration": "Mimic Duration", - "Glitch_MimicButtonText": "Mimic", - "Glitch_MimicDur": "Mimic Duration: {0}s", - "Glitch_HackCD": "Hack Cooldown: {0}s", - "Glitch_KCD": "Kill Cooldown: {0}s", - "Glitch_MimicCD": "Mimic Cooldown: {0}s", - "HackedByGlitch": "You are hacked by the Glitch, you can't {0}.", - "GlitchKill": "kill", - "GlitchReport": "report", - "GlitchVent": "vent", - "ShowFPS": "Show FPS", - "FPSGame": "FPS: ", - "Cooldown": "Cooldown", - "KillCooldown": "Kill Cooldown", - "AbilityCooldown": "Ability Cooldown", - "VentCooldown": "Vent Cooldown", - "ControlCooldown": "Control Cooldown", - "PoisonCooldown": "Poison Cooldown", - "PoisonerKillDelay": "Poison Kill Delay", - "WardenNotifyLimit": "Max number of alerts", - "CanVent": "Can Vent", - "CanKill": "Can Kill", - "CanGuess": "Can Guess in Guesser Mode or as Guesser", - "BombCooldown": "Bomb Cooldown", - "ImpostorVision": "Has Impostor Vision", - "CanUseSabotage": "Can Sabotage", - "CanKillAllies": "Can Kill Impostors", - "CanKillSelf": "Can Kill Themself", - "CrewpostorKnowsAllies": "Knows Impostors", - "AlliesKnowCrewpostor": "Known to Impostors", - "CrewpostorLungeKill": "Crewpostor lunges on kill", - "CrewpostorKillAfterTask": "Number of tasks completed to make 1 kill", - - "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", - "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", - "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", - "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", - "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", - "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", - "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", - "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", - "ImpKnowWhosMadmate": "Impostors know Madmates", - "MadmateKnowWhosImp": "Madmates know Impostors", - "MadmateKnowWhosMadmate": "Madmates know each other", - "ImpCanKillMadmate": "Impostors can kill Madmates", - "MadmateCanKillImp": "Madmates can kill Impostors", - "MadmateHasImpostorVision": "Madmates Have Impostor Vision", - "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", - "EGCanGuessImp": "Can Guess Impostor Roles", - "GGCanGuessCrew": "Can Guess Crewmate Roles", - "EGCanGuessAdt": "Can Guess Add-Ons", - "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "GGCanGuessAdt": "Can Guess Add-Ons", - "GuesserCanGuessTimes": "Maximum number of guesses", - "GuesserTryHideMsg": "Try to hide guesser's command", - "GCanGuessImp": "Impostor can guess Impostor roles", - "GCanGuessCrew": "Crewmate can guess Crewmate roles", - "GCanGuessAdt": "Can guess Add-ons", - "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "BountyTargetChangeTime": "Time Until Target Swaps", - "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", - "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", - "BountyShowTargetArrow": "Show arrow pointing towards target", - "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", - "ShapeshiftDuration": "Shapeshift Duration", - "ShapeshiftCooldown": "Shapeshift Cooldown", - "VitalsDuration": "Vitals Duration", - "VitalsCooldown": "Vitals Cooldown", - "DeadImpCantSabotage": "Impostors can't sabotage after they've died", - "VampireKillDelay": "Bite Kill Delay", - - "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", - "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", - - "MechanicSkillLimit": "Initial repair use limit", - "MechanicFixesDoors": "Can open all doors in the same building", - "MechanicFixesReactors": "Can Fix Both Reactors Alone", - "MechanicFixesOxygens": "Can Fix Both O2 Alone", - "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", - "MechanicFixesElectrical": "Can Fix Lights With One Switch", - - "SheriffShowShotLimit": "Display Shot Limit next to Role Name", - "SheriffCanKill%role%": "Can Kill %role%", - "SheriffCanKillNeutrals": "Can Kill Neutrals", - "SheriffCanKillNeutralsMode": "Neutral Configuration", - "SheriffCanKillAll": "All ON", - "SheriffCanKillSeparately": "Individual Settings", - "In%team%": "(Team %team%)", - "SheriffMisfireKillsTarget": "Misfire Kills Target", - "SheriffShotLimit": "Max number of Kills", - "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", - "SheriffCanKillCharmed": "Can kill Charmed players", - "SheriffCanKillEgoist": "Can Kill Egoists", - "SheriffCanKillSidekick": "Can Kill Sidekicks", - "SheriffCanKillLovers": "Can Kill Lovers", - "SheriffCanKillMadmate": "Can Kill Madmates", - "SheriffCanKillInfected": "Can Kill Infected players", - "SheriffCanKillContagious": "Can Kill Contagious players", - "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", - "SheriffMadCanKillImp": "Can kill Impostors", - "SheriffMadCanKillNeutral": "Can kill Neutrals", - "SheriffMadCanKillCrew": "Can kill Crewmates", - - "ReverieIncreaseKillCooldown": "Increase kill cooldown", - "ReverieMaxKillCooldown": "Max kill cooldown", - "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", - "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", - "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", - - "VigilanteNotify": "You have become the very thing you swore to destroy", - - "DoctorTaskCompletedBatteryCharge": "Battery Duration", - "SnitchEnableTargetArrow": "See Arrow Towards Target", - "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", - "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", - "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", - "SnitchCanFindMadmate": "Can Find Madmates", - "SnitchRemainingTaskFound": "Remaining tasks to be known", - "SpeedBoosterUpSpeed": "Increase Speed by", - "SpeedBoosterTimes": "Max Boosts", - "MayorAdditionalVote": "Additional Votes Count", - "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", - "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", - "MayorHideVote": "Hide additional vote(s)", - "HideJesterVote": "Hide Jester's vote", - "MeetingsNeededForWin": "Meetings needed to win", - "ExecutionerCanTargetImpostor": "Can Target Impostors", - "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", - "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", - "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", - "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", - "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", - "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", - "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", - "LawyerCanTargetImpostor": "Can Target Impostors", - "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", - "LawyerCanTargetCrewmate": "Can Target Crewmates", - "LawyerCanTargetJester": "Can Target Jester", - "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", - "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", - "LawyerTargetDeadInMeeting": "Your target was killed while meeting./nYour role may change depending on the settings.", - - "MercenaryLimit": "Time Until Suicide", - "ArsonistDouseTime": "Douse Duration", - "CanTerroristSuicideWin": "Can Win By Suicide", - "FireworkerMaxCount": "Fireworker Count", - "FireworkerRadius": "Firework Explosion Radius", - "SniperCanKill": "Sniper can kill with bullets remaining", - "SniperBulletCount": "Ammo", - "SniperPrecisionShooting": "Precise Shooting", - "SniperAimAssist": "Aim Assist", - "SniperAimAssistOneshot": "One shot Assist", - - "PyroDouseCooldown": "Douse cooldown", - "PyroBurnCooldown": "Kill cooldown after killing a doused player", - - "UndertakerFreezeDuration": "Freeze Duration", - - "NameDisplayAddons": "Display Add-Ons next to the role name", - "YourAddon": "Your Addons:", - "NoLimitAddonsNumMax": "Max Add-ons Per Player", - "LoverSpawnChances": "Spawn Chance of Lovers", - "AdditionRolesSpawnRate": "Spawn Chance", - "TorchVision": "Torch Vision", - "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", - "BewilderVision": "Bewilder Vision", - "JesterVision": "Jester Vision", - "LawyerVision": "Lawyer Vision", - "FlashSpeed": "Flash Speed", - "LoverSuicide": "Lovers die together", - "NumberOfLovers": "Number of Lover Pairs (x2 members)", - "LoverKnowRoles": "Lovers know the roles of each other", - "TrapperBlockMoveTime": "Freeze time", - "BecomeTrapperBlockMoveTime": "Freeze time", - "ImpCanBeTrapper": "Impostors can become Beartrap", - "CrewCanBeTrapper": "Crewmates can become Beartrap", - "NeutralCanBeTrapper": "Neutrals can become Beartrap", - "ImpCanBeGravestone": "Impostors can become Gravestone", - "CrewCanBeGravestone": "Crewmates can become Gravestone", - "NeutralCanBeGravestone": "Neutrals can become Gravestone", - "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", - "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", - "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", - "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", - "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", - "EvilTrackerTargetMode": "Can Set Target", - "EvilTrackerTargetMode.Never": "Never", - "EvilTrackerTargetMode.OnceInGame": "Once in game", - "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", - "EvilTrackerTargetMode.Always": "Any time", - "WitchModeSwitchAction": "Switch Action via", - "NBareRed": "Neutral Benign can be red", - "NEareRed": "Neutral Evil can be red", - "NCareRed": "Neutral Chaos can be red", - "NAareRed": "Neutral Apocalypse can be red", - "CrewKillingRed": "Crewmate Killings can be red", - "PsychicCanSeeNum": "Max number of red names", - "PsychicFresh": "New red names every meeting", - "DetectiveCanknowKiller": "Can find the killer's role", - "EveryOneKnowSuperStar": "Everyone knows the Super Star", - "HackLimit": "Ability Use Count", - "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", - "NemesisCanKillNum": "Max number of revenges", - "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", - "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", - "JesterCanUseButton": "Can call emergency meetings", - "VectorVentNumWin": "Number of Vents to win", - "CanCheckCamera": "Can track camera usage", - "Arrogance/Juggernaut___DefaultKillCooldown": "Starting kill cooldown", - "Arrogance/Juggernaut___ReduceKillCooldown": "Reduce kill cooldown by", - "Arrogance/Juggernaut___MinKillCooldown": "Minimum kill cooldown", - "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", - "NotifyGodAlive": "Inform players at meetings that God is still alive", - "TransporterTeleportMax": "Max number of teleports", - "TriggerKill": "Kill", - "TriggerVent": "Vent", - "TriggerDouble": "Double Click", - "TimeManagerIncreaseMeetingTime": "Increase voting time by", - "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", - "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", - "AssignOnlyToCrewmate": "Assign only to Crewmates", - "WorkhorseNumLongTasks": "Additional Long Tasks", - "WorkhorseNumShortTasks": "Additional Short Tasks", - "SnitchCanBeWorkhorse": "Snitch can become Workhorse", - "InnocentCanWinByImp": "If their target was an Impostor then they win with them", - "ImpCanBeSchizophrenic": "Impostors can become Schizophrenic", - "CrewCanBeSchizophrenic": "Crewmates can become Schizophrenic", - "DualVotes": "Duplicate votes", - "ProtectCooldown": "Protect Cooldown", - "ProtectDur": "Protection Duration", - "ProtectVisToImp": "Protect Visible To Impostors", - "VeteranSkillCooldown": "Alert Cooldown", - "VeteranSkillDuration": "Alert Duration", - "BodyguardProtectRadius": "Protect Radius", - "ImpCanBeEgoist": "An Impostor can become Egoist", - "CrewCanBeEgoist": "Crewmates can become Egoist", - "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", - "EgoistCountAsConverted": "Egoist count as converted neutral", - "ImpCanBeSeer": "Impostors can become Seer", - "CrewCanBeSeer": "Crewmates can become Seer", - "NeutralCanBeSeer": "Neutrals can become Seer", - "ImpCanBeGuesser": "Impostors can become Guesser", - "CrewCanBeGuesser": "Crewmates can become Guesser", - "NeutralCanBeGuesser": "Neutrals can become Guesser", - "ImpCanBeWatcher": "Impostors can become Watcher", - "CrewCanBeWatcher": "Crewmates can become Watcher", - "NeutralCanBeWatcher": "Neutrals can become Watcher", - "ImpCanBeBait": "Impostors can become Bait", - "CrewCanBeBait": "Crewmates can become Bait", - "NeutralCanBeBait": "Neutrals can become Bait", - "ImpCanBeRainbow": "Impostors can become Rainbow", - "NeutralCanBeRainbow": "Neutrals can become Rainbow", - "CrewCanBeRainbow": "Crewmates can become Rainbow", - "GuessRainbow": "He seems too obvious, doesn't he?", - "RainbowColorChangeCoolDown": "The cooldown for changing colors", - "RainbowInCamouflage": "Rainbow color changes during Camouflage", - "BaitDelayMin": "Minimum Report Delay", - "BaitDelayMax": "Maximum Report Delay", - "BaitDelayNotify": "Warn the killer about the upcoming self-report", - "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", - "BaitNotification": "Reveal Bait at the first meeting", - "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self report.", - "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if meeting is disabled during comms sabotage", - "DeceiverSkillCooldown": "Ability cooldown", - "DeceiverSkillLimitTimes": "Max number of uses", - "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", - "PursuerSkillCooldown": "Ability cooldown", - "PursuerSkillLimitTimes": "Max number of uses", - "AddictSuicideTimer": "Time Until Suicide", - "GrenadierSkillCooldown": "Grenade Cooldown", - "GrenadierSkillDuration": "Grenade Duration", - "GrenadierCauseVision": "Lowered vision", - "GrenadierCanAffectNeutral": "Can affect Neutrals", - "TicketsPerKill": "Votes Increase Amount Per Kill", - "GangsterRecruitCooldown": "Recruit cooldown", - "GangsterRecruitLimit": "Recruit limit", - "KamikazeMaxMarked": "Max Marked", - "RevolutionistDrawTime": "Tag Duration", - "RevolutionistCooldown": "Tag Cooldown", - "RevolutionistDrawCount": "Amount of Players needed to Tag", - "RevolutionistKillProbability": "Tagged player sacrifice probability", - "RevolutionistVentCountDown": "Time to Vent", - "PelicanKillCooldown": "Eat Cooldown", - "Pelican.TargetCannotBeEaten": "Target cannot be eaten", - "MadSnitchTasks": "Snitch Tasks", - "MedicWhoCanSeeProtect": "Who can see shield", - "MedicKnowShieldBroken": "Who sees kill attempt", - "Medic_SeeMedicAndTarget": "Medic+Shielded", - "Medic_SeeMedic": "Medic", - "Medic_SeeTarget": "Shielded", - "Medic_SeeNoOne": "Nothing", - "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", - "MedicShielDeactivationIsVisible": "Shield deactivation is visible", - "MedicShieldDeactivationIsVisible_DeactivationImmediately": "Immediately", - "MedicShieldDeactivationIsVisible_DeactivationAfterMeeting": "After Meeting", - "MedicShieldDeactivationIsVisible_DeactivationIsVisibleOFF": "OFF", - "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", - "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", - "FortuneTellerSkillLimit": "Max number of ability uses", - "MadmateSpawnMode": "Madmate spawning mode", - "MadmateSpawnMode.Assign": "Assign", - "MadmateSpawnMode.FirstKill": "First Kill", - "MadmateSpawnMode.SelfVote": "Self Vote", - "MadmateCountMode": "Madmates count as", - "MadmateCountMode.None": "Nothing", - "MadmateCountMode.Imp": "Impostors", - "MadmateCountMode.Original": "Original Team", - - "SnatchesWin": "Snatches victory", - "DemonKillCooldown": "Attack Cooldown", - "DemonHealthMax": "Player max health", - "DemonDamage": "Damage ", - "DemonSelfHealthMax": "Demon max health", - "DemonSelfDamage": "Demon damage received", - "LightningConvertTime": "Duration of the transformation to Quantum Ghost", - "LightningKillCooldown": "Lightning Cooldown", - "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", - "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", - "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", - "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", - "WorkaholicCannotWinAtDeath": "Can't win after they died", - "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", - "WorkaholicGiveAdviceAlive": "Advice at first meeting if alive, can win after death, ghost tasks ON", - "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", - "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", - "Jinx/CursedWolf___KillAttacker": "Kill attacker when ability is remaining", - "JinxSpellTimes": "Amount of Jinx Spells", - "CollectorCollectAmount": "Required number of votes", - "GlitchCanVote": "Can vote", - "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", - "MeetingReserved": "Max Bullets reserved for a meeting", - "AccurateCheckMode": "Can know specific role when tasks are not done", - "RandomActiveRoles": "Show random active roles in Fortune Teller hints", - "CamouflageCooldown": "Camouflage Cooldown", - "CamouflageDuration": "Camouflage Duration", - "EraseLimit": "Max Erases", - "EraserHideVote": "Hide Eraser Votes", - "NinjaMarkCooldown": "Mark Cooldown", - "NinjaAssassinateCooldown": "Ass​assinate Cooldown", - "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", - "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", - "JudgeCanTrialNeutralB": "Can trial Neutral Benign", - "JudgeCanTrialNeutralK": "Can trial Neutral Killing", - "JudgeCanTrialNeutralE": "Can trial Neutral Evil", - "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", - "JudgeCanTrialSidekick": "Can trial Sidekick", - "JudgeCanTrialInfected": "Can trial Infected", - "JudgeCanTrialContagious": "Can trial Contagious", - "JudgeTryHideMsg": "Hide Judge's commands", - "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", - "JudgeCanTrialMadmate": "Can trial Madmates", - "JudgeCanTrialCharmed": "Can trial Charmed players", - "JudgeDead": "Sorry, you can't trial after death.", - "JudgeTrialMax": "\nNo more trials left!", - "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", - "Judge_TrialKill": "{0} was judged.", - "Judge_TrialKillTitle": "COURT", - "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Judge_TrialNull": "Please choose a living player for the trial", - "VeteranSkillMaxOfUseage": "Max number of Alerts", - "SwooperCooldown": "Swoop Cooldown", - "SwooperDuration": "Swoop Duration", - "WraithCooldown": "Vanish Cooldown", - "WraithDuration": "Vanish Duration", - "BastionNotify": "A bomb was set off", - "EnteredBombedVent": "That vent was bombed!", - "BastionVentButtonText": "Bomb", - "BombsClearAfterMeeting": "Bombs clear after meetings", - "BastionMaxBombs": "(Initial) Maximum bombs", - "VentBombSuccess": "Bomb has been planted", - "LowLoadMode": "Low Load Mode", - "ShowLobbyCode": "Show lobby code in Discord status", - "BKProtectDuration": "Protection Duration", - "FollowerMaxBetTimes": "Maximum Number of Follows", - "FollowerBetCooldown": "Follow Cooldown", - "FollowerMaxBetCooldown": "Maximum Follow Cooldown", - "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", - "FollowerKnowTargetRole": "Follower knows their target's role", - "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", - "FortuneTellerHideVote": "Hide Fortune Teller's Votes", - "CultistCharmCooldown": "Charm Cooldown", - "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", - "CultistCharmMax": "Maximum Number Of Charm", - "CultistKnowTargetRole": "Know Charmed Player's Role", - "CultistTargetKnowOtherTarget": "Charmed players know each other", - "CultistCanCharmNeutral": "Neutral Roles can be Charmed", - "InfectiousBiteCooldown": "Infect Cooldown", - "KnowTargetRole": "Knows role of target", - "TargetKnowsLawyer": "Target knows their Lawyer", - "InfectiousBiteMax": "Maximum Infections", - "InfectiousKnowTargetRole": "Know infected player's role", - "InfectiousTargetKnowOtherTarget": "Infected players know each other", - "DoubleClickKill": "Double click to kill the target", - - "VirusInfectMax": "Maximum Number Of Spreads", - "VirusKnowTargetRole": "Know Contagious Player's Role", - "VirusTargetKnowOtherTarget": "Contagious players know each other", - "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", - "Virus_ContagiousCountMode": "Contagious players count as", - "Virus_ContagiousCountMode_None": "Nothing", - "Virus_ContagiousCountMode_Virus": "Virus", - "Virus_ContagiousCountMode_Original": "Original Team", - "VirusNoticeTitle": "[ Infected Corpse! ]", - "VirusNoticeMessage": "The body your reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", - "VirusNoticeMessage2": "The body your reported was infected by the Virus! Vote the Virus out this meeting or you will die.", - - "Cultist_CharmedCountMode": "Charmed players count as", - "Cultist_CharmedCountMode_None": "Nothing", - "Cultist_CharmedCountMode_Cultist": "Cultist", - "Cultist_CharmedCountMode_Original": "Original Team", - - "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", - "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", - "JackalResetKillCooldownOn": "Kill Cooldown On Reset", - "JackalCanRecruitSidekick": "Can recruit Sidekick", - "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", - "Jackal_SidekickCountMode": "Sidekicks count as", - "Jackal_SidekickCountMode_None": "Nothing", - "Jackal_SidekickCountMode_Jackal": "Jackal", - "Jackal_SidekickCountMode_Original": "Original Team", - "Jackal_SidekickAssignMode": "Sidekick Assign Mode", - "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", - "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", - "Jackal_SidekickAssignMode_Recruit": "Recruit Only", - "JackalWinWithSidekick": "Jackal can win with Sidekick's team", - "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", - "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", - "JackalCanKillSidekick": "Jackal can kill Sidekick", - - "ImpCanBeNecroview": "Impostors can become Necroview", - "CrewCanBeNecroview": "Crewmates can become Necroview", - "NeutralCanBeNecroview": "Neutrals can become Necroview", - "ImpCanBeInLove": "Impostors can be in love", - "CrewCanBeInLove": "Crewmates can be in love", - "NeutralCanBeInLove": "Neutrals can be in love", - "ImpCanBeOblivious": "Impostors can become Oblivious", - "CrewCanBeOblivious": "Crewmates can become Oblivious", - "NeutralCanBeOblivious": "Neutrals can become Oblivious", - "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", - "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", - "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", - "HexesLookLikeSpells": "Hexes appear as spells", - "HexButtonText": "Hex", - "ObliviousBaitImmune": "Immune to Bait", - "ImpCanBeOnbound": "Impostors can become Onbound", - "CrewCanBeOnbound": "Crewmates can become Onbound", - "NeutralCanBeOnbound": "Neutrals can become Onbound", - - "ImpCanBeRebound": "Impostors can become Rebound", - "CrewCanBeRebound": "Crewmates can become Rebound", - "NeutralCanBeRebound": "Neutrals can become Rebound", - - "CrewCanBeMundane": "Crewmates can become Mundane", - "NeutralCanBeMundane": "Neutrals can become Mundane", - "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", - - "ImpCanBeUnreportable": "Impostors can become Disregarded", - "CrewCanBeUnreportable": "Crewmates can become Disregarded", - "NeutralCanBeUnreportable": "Neutrals can become Disregarded", - "PacifistCooldown": "Ability Cooldown", - "PacifistMaxOfUseage": "Max Number of Ability Uses", - "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", - "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", - "TrackerHideVote": "Hide Tracker Votes", - "TrackerCanGetArrowColor": "Can See Colored Arrows", - - "PresidentAbilityUses": "Max Number of Ability Uses", - "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", - "HidePresidentEndCommand": "Hide President's commands", - "NeutralsSeePresident": "Neutrals can see revealed President", - "MadmatesSeePresident": "Madmates can see revealed President", - "ImpsSeePresident": "Impostors can see revealed President", - "PresidentDead": "Sorry, you can't force end the meeting after death.", - "PresidentEndMax": "No more force end meeting uses left!", - "PresidentRevealMax": "You have already revealed yourself...", - "PresidentRevealed": "[{0}] has chose to reveal themselves as President!", - "GuessPresident": "President has revealed themselves, you can't guess them.", - "PresidentRevealTitle": "PRESIDENT REVEAL", - - "LuckyProbability": "Probability of surviving a kill", - "ImpCanBeLucky": "Impostors can become Lucky", - "CrewCanBeLucky": "Crewmates can become Lucky", - "NeutralCanBeLucky": "Neutrals can become Lucky", - "ImpCanBeFool": "Impostors can become Fool", - "CrewCanBeFool": "Crewmates can become Fool", - "NeutralCanBeFool": "Neutrals can become Fool", - "ImpCanBeDoubleShot": "Impostors can have Double Shot", - "CrewCanBeDoubleShot": "Crewmates can have Double Shot", - "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", - "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", - "DisableReportWhenCamouflageIsActive": "Disable body reporting when сamouflage is active", - "CanUseCommsSabotage": "Can use comms sabotage", - "ModTag": "Moderator♥", - "ApplyModeratorList": "Apply Moderator List", - "VipTag": "VIP★", - "ApplyVipList": "Apply VIP List", - "AllowSayCommand": "Allow moderators to use /say command", - "KickCommandDisabled": "The kick command is currently disabled.", - "KickCommandNoAccess": "You do not have access to the kick command.", - "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reseaon]' to kick a player.\nExample :- /kick 5 not following rules", - "KickCommandKickHost": "You are not permitted to kick the host.", - "KickCommandKickMod": "You are not permitted to kick other moderators.", - "KickCommandKicked": "was kicked from the game by ", - "KickCommandKickedRole": "Their role was", - "BanCommandDisabled": "The ban command is currently disabled.", - "BanCommandNoAccess": "You do not have access to the ban command.", - "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", - "BanCommandBanHost": "You are not permitted to ban the host.", - "BanCommandBanMod": "You are not permitted to ban other moderators.", - "BanCommandBanned": "was banned from the game by ", - "BanCommandBannedRole": "Their role was", - "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", - "ColorCommandDisabled": "The modcolor command is currently disabled.", - "ColorCommandNoAccess": "You do not have access to the modcolor command.", - "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff", - "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", - "VipColorCommandDisabled": "The vipcolor command is currently disabled.", - "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", - "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff", - "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", - "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change color of your tag.\nExample :- /tagcolor ff00ff", - "midCommandDisabled": "The mid command is currently disabled.", - "midCommandNoAccess": "You do not have access to the mid command.", - "DisableVoteBan": "Disable VoteKick System", - "WarnCommandDisabled": "The warn command is currently disabled.", - "WarnCommandNoAccess": "You do not have access to the warn command.", - "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", - "WarnCommandWarnHost": "You are not permitted to warn the host.", - "WarnCommandWarnMod": "You are not permitted to warn other moderators.", - "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", - "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", - "SayCommandDisabled": "The say command is currently disabled.", - "MessageFromModerator": "MODERATOR", - "DeathReason.Kill": "Kill", - "DeathReason.Vote": "Ejected", - "DeathReason.Suicide": "Suicide", - "DeathReason.Spell": "Spelled", - "DeathReason.Cursed": "Cursed", - "DeathReason.Hex": "Hexed", - "DeathReason.Bite": "Bitten", - "DeathReason.Poison": "Poisoned", - "DeathReason.Gambled": "Guessed", - "DeathReason.FollowingSuicide": "Heartbroken", - "DeathReason.Bombed": "Exploded", - "DeathReason.Misfire": "Misfire", - "DeathReason.Torched": "Burned", - "DeathReason.Sniped": "Sniped", - "DeathReason.Execution": "Executed", - "DeathReason.Disconnected": "Disconnected", - "DeathReason.Fall": "Fall", - "DeathReason.Revenge": "Revenge", - "DeathReason.Eaten": "Eaten", - "DeathReason.Sacrifice": "Victim", - "DeathReason.Quantization": "Quantization", - "DeathReason.Overtired": "Overtired", - "DeathReason.Ashamed": "Ashamed", - "DeathReason.PissedOff": "Destroyed", - "DeathReason.Dismembered": "Dismembered", - "DeathReason.LossOfHead": "Strangled", - "DeathReason.Trialed": "Judged", - "DeathReason.Infected": "Infected", - "DeathReason.Jinx": "Jinxed", - "DeathReason.Pirate": "Plundered", - "DeathReason.Shrouded": "Shrouded", - "DeathReason.etc": "Other", - "DeathReason.Mauled": "Mauled", - "DeathReason.Hack": "Hacked", - "DeathReason.Curse": "Cursed", - "DeathReason.Drained": "Drained", - "DeathReason.Shattered": "Shattered", - "DeathReason.Trap": "Trapped", - "DeathReason.Targeted": "Targeted", - "DeathReason.Retribution": "Retribution", - "DeathReason.Slice": "Sliced", - "DeathReason.BloodLet": "Bleed", - "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", - "Alive": "Alive", - "Win": " Wins!", - - "Last-": "Last ", - "Madmate-": "Madmate ", - "Recruit-": "Recruit ", - "Charmed-": "Charmed ", - "Soulless-": "Soulless ", - "Infected-": "Infected ", - "Contagious-": "Contagious ", - "Admired-": "Admired ", - - "DeputyHandcuffCooldown": "Handcuff Cooldown", - "DeputyHandcuffMax": "Maximum Handcuffs", - "DeputyHandcuffedPlayer": "Handcuffed target", - "HandcuffedByDeputy": "You were handcuffed!", - "DeputyInvalidTarget": "Target cannot be handcuffed", - "DeputyHandcuffText": "Handcuff", - "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", - - "RejectShapeshift.AbilityWasUsed": "Ability was used", - "ShowShapeshiftAnimations": "Show Shapeshift animations", - - "EscapisMtarkedPosition": "You marked self position", - - "InvestigateCooldown": "Investigate Cooldown", - "InvestigateMax": "Maximum Investigations", - "InvestigateRoundMax": "Maximum Investigations in one round", - - "Color.Red": "Red", - "Color.Green": "Green", - "Color.Gray": "Gray", - "InvestigatorInvestigatedPlayer": "Player Investigated", - "InvestigatorInvalidTarget": "Can not investigate", - "InvestigatorButtonText": "Check", - - "Investigator.Suspicion": "Suspicion", - "Investigator.Role": "Role", - "SabotageCooldownControl": "Sabotage Cooldown Control", - "SabotageCooldown": "Sabotage Cooldown", - "SabotageTimeControl": "Sabotage Duration Control", - "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", - "SkeldO2TimeLimit": "The Skeld O2 Time Limit", - "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", - "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", - "PolusReactorTimeLimit": "Polus Reactor Time Limit", - "AirshipReactorTimeLimit": "Airship Reactor Time Limit", - "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", - "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", - "CommandList": "★ Command list:", - "Command.now": "→ Display active Settings", - "Command.roles": "[RoleName] → Display Role description", - "Command.myrole": "→ Displays a description of your role", - "Command.lastresult": "→ Display match results", - "Command.winner": "→ Display winners", - "CommandOtherList": "● Other commands:", - "Command.color": "[Color] → Change your color", - "Command.rename": "[Name] → Change Host Name", - "Command.quit": "→ I don't want to enter this lobby anymore", - "CommandHostList": "▲ Host Commands:", - "Command.say": "[Content] → Send message as Host", - "Command.mw": "[Seconds] → Set the message waiting duration", - "Command.solvecover": "→ Fix an issue where role names overlap the messages", - "Command.kill": "[Player ID] → Kill assigned player", - "Command.exe": "[Player ID] → Eject assigned player", - "Command.level": "[Level] → Change your in-game level", - "Command.idlist": "→ Display a list of player IDs", - "Command.qq": "→ Lobby will be posted on QQ website (China only)", - "Command.dump": "→ Output Log to Desktop", - "Command.death": "→ Display info on how you died", - "Command.icons": "Icon Meanings\n† - This player was spelled by a Witch and will die if the Witch is not killed by the end of the meeting\n乂 - This player was hexed by a Hex Master and will die if the Hex Master is not killed by the end of the meeting\n❖ - This player was hexed by an Occultist and will die if the Occultist is not killed by the end of the meeting\n◈ - This player was shrouded by a Shroud and will die if the Shroud is not killed by the end of the meeting\n⦿ - This player is being dueled by a Pirate\n♥ - This player is a Lover\n⚠ - This player is a Snitch who has finished their tasks", - "Command.iconinfo": "→ Display info on in-meeting icons", - "Command.iconhelp": "→ Display info on in-meeting icons to everyone", - "Remaining.ImpostorCount": "Impostors left: ", - "Remaining.NeutralCount": "Neutral Killers left: ", - "EnableKillerLeftCommand": "Enable use of /kcount command", - "SeeEjectedRolesInMeeting": "See ejected roles in meetings", - - "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", - "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", - "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", - "NemesisKillDead": "Choose a living player to take revenge", - "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", - "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", - - "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period of time!", - "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", - "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", - "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", - "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer, this is most likely suicide.", - "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", - "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", - "GuessDead": "Sorry, but it's impossible to guess roles after your death", - "GuessSuperStar": "The Super Star can't be guessed.... you thought it would be that easy, right?", - "GuessNotifiedBait": "Bait can't be guessed because it was announced, you thought it would be that easy, right?", - "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", - "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", - "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", - "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", - "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", - "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", - "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", - "GuessKill": "{0} was guessed", - "GuessNull": "Please select an ID of a living player to guess their role", - "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "GGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", - "EGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", - "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all of their tasks are done? Nice try.... You're not getting out of this that easily.", - "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot so you get another chance!", - "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", - "GuessDisabled": "Sorry, the host restricted guessing for your role.", - "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", - "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", - "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", - "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", - "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", - "GuessShielded": "Sorry, you can't guess the player who is shielded by Medic", - "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", - "MimicDeadMsg": "Mimic's hint: ", - "FortuneTellerCheck": "According to your fortune...", - "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", - "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", - "FortuneTellerCheckReachLimit": "You've run out of fortunes.", - "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", - "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", - "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", - "EraserEraseSelf": "Unfortunately, you can't erase yourself.... Wait, why would you do that in the first place?!", - "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", - "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", - - "MediumContactLimit": "Max number of contacts (ability uses)", - "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", - "MediumTitle": "MEDIUM", - "MediumHelp": "/ms yes to agree\n/ms no to disagree", - "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", - "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", - "MediumDone": "You successfully responded to the Medium.", - "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", - "MediumNotifySelf": "You established contact with {0}, please ask questions to them and wait for them to respond.\n\nRemaining ability uses: {1}", - "MediumKnowPlayerDead": "Someone died somewhere", - - "ByBard": "by Bard", - "ByBardGetFailed": "oops, I seem to be out of inspiration.", - "GangsterSuccessfullyRecruited": "You successfully recruited a player", - "GangsterRecruitmentFailure": "Target cannot be recruited", - "BeRecruitedByGangster": "You have been recruited by the Gangster", - "KamikazeHostage": "Can't hold target hostage", - "VeteranOnGuard": "Ability in use", - "VeteranOffGuard": "Ability expired, {0} uses remain", - "VeteranMaxUsage": "Ability use limit reached", - "GrenadierSkillInUse": "Ability in use", - "GrenadierSkillStop": "Ability expired", - "TicketsStealerGetTicket": "You've got {0} votes", - "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", - "SpeedBoosterTaskDone": "Your speed is {0} now", - "SpeedBoosterSpeedLimit": "You reached your maximum speed (3x)", - "CleanerCleanBody": "The body has been cleaned", - "QuickShooterStoraging": "Bullets stored successfully", - "VampireTargetDead": "Target died", - "PoisonerTargetDead": "Target died", - "BloodlustAdded": "Your bloodlust is now active!", - "WarlockNoTarget": "Manipulation failed due to no target", - "WarlockNoTargetYet": "You haven't mark a target.", - "WarlockTargetDead": "Manipulation failed due to target dead", - "WarlockControlKill": "Target died", - "OnCelebrityDead": "Warning: Celebrity death!", - "OnCyberDead": "Warning: Cyber died!", - "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", - "TeleportedByTransporter": "Swapping places with: {0}", - "ErrorTeleport": "Teleport failed", - "LostRoleByEraser": "You lost your role because of the Eraser", - "KilledByScavenger": "You were killed by the Scavenger and thus teleported off-map", - "SnitchDoneTasks": "Call a meeting to find the impostors", - "SwooperCanVent": "Vent to turn invisible", - "SwooperInvisState": "You're invisible", - "SwooperInvisStateOut": "You're now visible", - "SwooperInvisInCooldown": "Swoop cooldown isn't up yet, swooping failed", - "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", - "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", - "WraithCanVent": "Vent to turn invisible", - "WraithInvisState": "You are invisible", - "WraithInvisStateOut": "You are visible again", - "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", - "WraithInvisStateCountdown": "Invisibility will expire in {0}s", - "WraithInvisCooldownRemain": "{0}s left in invisibility", - "BKInProtect": "Currently immortal", - "BKProtectOut": "Shield expired", - "BKSkillTimeRemain": "You're immune for {0} seconds", - "BKSkillNotice": "Kill a player to enter immune status", - "BKOffsetKill": "Someone tried killing you", - "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", - "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", - "FollowerBetPlayer": "You're now following your target", - "FollowerBetOnYou": "The Follower is now following you", - "CultistCharmedPlayer": "You successfully charmed a player", - "CharmedByCultist": "You have been charmed by the Cultist", - "CultistInvalidTarget": "Target cannot be charmed", - "KillBaitNotify": "You'll self-report in {0}s", - "InfectiousInvalidTarget": "Target cannot be infected", - "BittenByInfectious": "You were infected by the Infectious!", - "InfectiousBittenPlayer": "You successfully infected a player", - "GuessNotAllowed": "Sorry, your role does not have access to guessing.", - "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", - "GuessPhantom": "You can't guess a Phantom, that allows them to win!", - "PacifistOnGuard": "Ability used, {0} uses remain", - "PacifistMaxUsage": "Ability use limit reached", - "PacifistSkillNotify": "Pacifist reset your kill cooldown", - "BeRecruitedByJackal": "You have been recruited by the Jackal", - "CoronerTrackRecorded": "Track recorded", - "CoronerNoTrack": "Nothing to track", - "CoronerIsTrackingYou": "The Coroner is tracking you!", - "TrackerLastRoomMessage": "The evaluation of your tracking showed that the last room in which your target was located was:[{0}]", - "MerchantAddonDelivered": "Add-on sold", - "MerchantAddonSell": "The Merchant sold you a new Add-on", - "MerchantAddonSellFail": "Could not sell an Add-on", - "BribedByMerchant": "The Merchant bribed you, you can't kill him", - "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", - "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", - "TrapTrapsterBody": "Trap Trapster's body", - "TrapConsecutiveBodies": "Trap consecutive bodies", - "HauntedByEvilSpirit": "Haunted by an Evil Spirit", - "MonarchKnightCooldown": "Knight Cooldown", - "MonarchKnightMax": "Maximum Knights", - "MonarchKnightedPlayer": "You successfully knighted a player!", - "KnightedByMonarch": "You have been knighted by a Monarch!", - "MonarchInvalidTarget": "Target cannot be knighted", - "GhostTransformTitle": "Your Role Has Transformed!", - "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", - "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", - "OverseerRevealCooldown": "Reveal Cooldown", - "OverseerRevealTime": "Reveal Time", - "OverseerVision": "Overseer Vision", - "MerchantMaxSell": "Max number of Add-ons to sell", - "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", - "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", - "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", - "MerchantTargetCrew": "Can sell to Crewmates", - "MerchantTargetImpostor": "Can sell to Impostors", - - "MerchantTargetNeutral": "Can sell to Neutrals", - "MerchantSellHelpful": "Can sell Helpful Add-ons", - "MerchantSellHarmful": "Can sell Harmful Add-ons", - "MerchantSellMixed": "Can sell Mixed Add-ons", - "MerchantSellExperimental": "Can sell experimental Add-ons", - "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", - "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", - "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", - - "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", - "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", - "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", - "SpiritcallerProtectTime": "Evil Spirit ability protect time", - "SpiritcallerCauseVision": "Evil Spirit ability caused vision", - "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", - "Message.SetToSeconds": "Set to [{0}] seconds.", - "Message.MessageWaitHelp": "Specify the first argument in seconds.", - "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", - "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", - "Message.SyncButtonLeft": "There are {0} more emergency buttons left", - "Message.Executed": "{0} was executed", - "Message.HideGameSettings": "Game settings have been hidden by the host.", - "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", - "Message.NoDescription": "No description", - "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", - "Message.BannedByBanList": "{0} was banned because they were banned in the past.", - "Message.BannedByEACList": "{0} has been banned because he is in EAC list of Banned people.", - "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", - "Message.DumpcmdUsed": "{0} used /dump command.", - "Message.KickedByNoFriendCode": "{0} was kicked because their friend code does not exist.", - "Message.TempBannedByNoFriendCode": "{0} was temporary banned because their friend code does not exist.", - "Message.AddedPlayerToBanList": "Added {0} to the ban list", - "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", - "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", - "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", - "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", - "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", - "Message.NoticeByEAC": "[{0}]Detected:{1}", - "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", - "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", - "Message.KickedByWhiteList": "{0} kicked because friendcode not found in WhiteList.txt", - "Message.SetLevel": "Your game level is set to: {0}", - "Message.SetColor": "Your color is set to: {0}", - "Message.SetName": "Your name is set to: {0}", - "Message.AllowLevelRange": "The game level can be set in the range: 0-100", - "Message.AllowNameLength": "Nickname can be set length: 1-10", - "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", - "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", - "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", - "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", - "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", - "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", - "Message.HostLeftGameInGame": "★Warning★ Host left the game, in next time game wouldn't start normally. Please, exit the lobby or wait until new Host opens a lobby.", - "Message.HostLeftGameInLobby": "★Warning★ Host left the game, in next time game wouldn't start normally. If new host has TOHE, you need to re-enter the lobby to play normally.", - "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, just start a game and end it immediately to reset the lobby!", - "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please, exit the lobby or wait until new Host opens a lobby.", - "Message.LobbyShared": "The lobby has successfully been shared!", - "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", - "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", - "Message.YTPlanSelected": "In the next game, your role will be {0}", - "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", - "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. In case the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", - "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", - "Message.MaxPlayers": "Maximum players set to ", - "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little bit about ghost roles...\n\nGhost roles drastically impact the game, so not recommended for smaller lobbies, if you're unfamiliar.\n\nSpawning:\nGhost-roles only spawn after death, the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g sheriff), your tasks as a ghost-role aren't needed for task-win", - - "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", - "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", - "WarningTitle": "Warning!", - "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", - "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", - - "AntiBlackoutProtectionTitle": "Anti Blackout", - "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normaly", - "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in last meeting.\n", - "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", - - "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE installed.", - "Warning.NoModHost": "TOHE is not installed on the host", - "Warning.MismatchedVersion": "{0} has a different version of {1}", - "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", - "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", - "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", - "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", - "Error.InvalidColor": "Error: Only default colors are available", - "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors, otherwise it will result in a serious error", - "ErrorLevel1": "Bugs may occur.", - "ErrorLevel2": "This may be a bug.", - "ErrorLevel3": "This version shouldn't have been released.", - "TerminateCommand": "Abort Command", - "ERR-000-000-0": "No Error", - "ERR-000-900-0": "Test Error Lv.0", - "ERR-000-910-1": "Test Error Lv.1", - "ERR-000-920-2": "Test Error Lv.2", - "ERR-000-930-3": "Test Error Lv.3", - "ERR-000-804-1": "TownofHost-Enhanced does not support the Vanilla HnS, so unloaded.", - "ERR-001-000-3": "Main dictionary has duplicated keys.", - "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", - "DefaultSystemMessageTitle": "SYSTEM MESSAGE", - "MessageFromTheHost": "HOST MESSAGE", - "MessageFromEAC": "EAC", - "DetectiveNoticeTitle": "INVESTIGATION", - "SleuthNoticeTitle": "SLEUTH", - "GuessKillTitle": "GUESSING INFO", - "CelebrityNewsTitle": "CELEBRITY", - "CyberNewsTitle": "CYBER", - "GodAliveTitle": "GOD ", - "WorkaholicAliveTitle": "WORKAHOLIC", - "BaitAliveTitle": "BAIT", - "MessageFromKPD": "KARPED1EM ", - "MessageFromSponsor": "SPONSOR MESSAGE ", - "MessageFromDev": "DEVELOPER MESSAGE ", - "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", - "MimicMsgTitle": "MIMIC", - "EraserEraseMsgTitle": "ERASER", - "MorticianCheckTitle": "CORPSE EXAMINATION", - "NemesisRevengeTitle": "NEMESIS", - "RetributionistRevengeTitle": "RETRIBUTIONIST", - "TabGroup.SystemSettings": "System Settings", - "TabGroup.GameSettings": "Game Settings", - "TabGroup.CrewmateRoles": "Crewmate Roles", - "TabGroup.NeutralRoles": "Neutral Roles", - "TabGroup.ImpostorRoles": "Impostor Roles", - "TabGroup.Addons": "Add-Ons", - "TabGroup.OtherRoles": "Experimental Roles (Not recommended)", - "TabGroup.TaskSettings": "Game Modifiers", - "OtherRoles.CrewmateRoles": "★ Crewmate Roles", - "OtherRoles.NeutralRoles": "★ Neutral Roles", - "OtherRoles.ImpostorRoles": "★ Impostor Roles", - "OtherRoles.Addons": "★ Add-Ons", - "ActiveRolesList": "Active Roles List", - "ForExample": "Example Use", - "updateButton": "Update", - "updatePleaseWait": "Please Wait...", - "updateManually": "Update failed.\nPlease Update Manually.", - "updateRestart": "Update Finished!\nPlease restart the game.", - "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease update.", - "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", - "UnsupportedVersion": "Unsupported Among Us version.\nPlease update Among Us", - "DisabledByProgram": "Public rooms have been disabled by the program", - "EnterVentToWin": "Enter Vent to Win!!", - "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", - "FireworkerPutPhase": "{0} Fireworker Left", - "FireworkerWaitPhase": "Wait for it...", - "FireworkerReadyFirePhase": "Fire!", - "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", - "On": "ON", - "Off": "OFF", - "ColoredOn": "ON", - "ColoredOff": "OFF", - "CurrentActiveSettingsHelp": "Current Active Settings Help", - "WitchCurrentMode": "Current Mode", - "WitchModeKill": "Kill", - "WitchModeSpell": "Spell", - "HexMasterModeHex": "Hex", - "HexMasterModeKill": "Kill", - "PoisonerPoisonButtonText": "Poison", - "WitchModeDouble": "Double Click = Kill, Single Click = Spell", - "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", - "BountyCurrentTarget": "Current Target", - "Roles": "Roles", - "Settings": "Settings", - "Addons": "Add-Ons", - "LastResult": "★ Match Results", - "LastEndReason": "★ End Reason", - "KillLog": "Kill Log", - "Maximum": "Max", - "RoleRate": "ON", - "RoleOn": "ALWAYS", - "RoleOff": "OFF", - "Chance0": "0%", - "Chance5": "5%", - "Chance10": "10%", - "Chance15": "15%", - "Chance20": "20%", - "Chance25": "25%", - "Chance30": "30%", - "Chance35": "35%", - "Chance40": "40%", - "Chance45": "45%", - "Chance50": "50%", - "Chance55": "55%", - "Chance60": "60%", - "Chance65": "65%", - "Chance70": "70%", - "Chance75": "75%", - "Chance80": "80%", - "Chance85": "85%", - "Chance90": "90%", - "Chance95": "95%", - "Chance100": "100%", - "Preset": "Preset", - "Preset_1": "Preset 1", - "Preset_2": "Preset 2", - "Preset_3": "Preset 3", - "Preset_4": "Preset 4", - "Preset_5": "Preset 5", - "Standard": "Standard", - "GameMode": "Game Mode", - "PressTabToNextPage": "Press Tab or Number for Next Page...", - "RoleSummaryText": "Role Summary:", - "doOverride": "Override %role%'s Tasks", - "assignCommonTasks": "%role% has Common Tasks", - "roleLongTasksNum": "Amount of Long Tasks for %role%", - "roleShortTasksNum": "Amount of Short Tasks for %role%", - "Format.Players": "{0}", - "Format.Seconds": "{0}s", - "Format.Percent": "{0}%", - "Format.Times": "{0}", - "Format.Multiplier": "{0}x", - "Format.Votes": "{0}", - "Format.Pieces": "{0}", - "Format.Health": "{0}", - "Format.Level": "{0}", - "KillButtonText": "Kill", - "ReportButtonText": "Report", - "VentButtonText": "Vent", - "SabotageButtonText": "Sabotage", - "SniperSnipeButtonText": "Snipe", - "FireworkerExplosionButtonText": "Detonate", - "FireworkerInstallAtionButtonText": "Install", - "MercenarySuicideButtonText": "Suicide Timer", - "WarlockCurseButtonText": "Curse", - "NinjaShapeshiftText": "Kill", - "NinjaMarkButtonText": "Mark", - "WitchSpellButtonText": "Spell", - "VampireBiteButtonText": "Bite", - "MinerTeleButtonText": "Warp", - "ArsonistDouseButtonText": "Douse", - "PuppeteerOperateButtonText": "Manipulate", - "WarlockShapeshiftButtonText": "Spell", - "BountyHunterChangeButtonText": "Swap", - "EvilTrackerChangeButtonText": "Track", - "InnocentButtonText": "Frame", - "PelicanButtonText": "Eat", - "DeceiverButtonText": "Cheat", - "PursuerButtonText": "Trick", - "GangsterButtonText": "Recruit", - "RevolutionistDrawButtonText": "Win over", - "HaterButtonText": "Hatred", - "MedicalerButtonText": "Protect", - "DemonButtonText": "Attack", - "SoulCatcherButtonText": "Teleport", - "LightningButtonText": "Evaporate", - "ProvocateurButtonText": "Greet", - "ButcherButtonText": "Dismember", - "BomberShapeshiftText": "Explode", - "QuickShooterShapeshiftText": "Keep", - "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", - "CamouflagerShapeshiftTextAfterDisguise": "Duration", - "AnonymousShapeshiftText": "Hack", - "DefaultShapeshiftText": "Shift", - "CleanerReportButtonText": "Clean", - "SwooperVentButtonText": "Swoop", - "SwooperRevertVentButtonText": "Expose", - "WraithVentButtonText": "Vanish", - "WraithRevertVentButtonText": "Expose", - "VectorVentButtonText": "Hop", - "VeteranVentButtonText": "Alert", - "GrenadierVentButtonText": "Flash", - "MayorVentButtonText": "Button", - "SheriffKillButtonText": "Shoot", - "UndertakerButtonText": "Mark", - "ArsonistVentButtonText": "Ignite", - "RevolutionistVentButtonText": "Revolution", - "FollowerKillButtonText": "Follow", - "PacifistVentButtonText": "Reset", - "CultistKillButtonText": "Charm", - "InfectiousKillButtonText": "Infect", - "MonarchKillButtonText": "Knight", - "OverseerKillButtonText": "Reveal", - "DisabledBySettings": "Disabled by Settings", - "Disabled": "Disabled", - "FailToTrack": "Failed To Track", - "KillCount": "Kills: {0}", - "CantUse.lastroles": "Unable to use /lastroles during a game.", - "CantUse.killlog": "Unable to use /killlog during a game.", - "CantUse.lastresult": "Unable to use /lastresult during a game.", - "IllegalColor": "Please enter the correct color", - "DisableUseCommand": "The Host's settings do not allow this command to be used.", - "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", - "PlayerIdList": "List of player IDs: ", - "CancelStartCountDown": "The starting countdown was cancelled", - "RestTOHESetting": "TOHE settings have been restored to default", - "FPSSetTo": "FPS Set To: {0}", - "HostKillSelfByCommand": "The lobby Host decided to commit suicide", - "SyncCustomSettingsRPC": "Synchronized RPC", - "Mode": "Mode", - "Target": "Target", - "PlayerInfo": "Player Info", - "NoInfoExists": "No Info Exists", - "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", - "PlayerLeftByError": "Game will auto-end to prevent black screens.", - "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", - "KickBecauseLowLevel": "{0} was kicked because their level was too low", - "TempBannedBecauseLowLevel": "{0} was temporary banned because their level was too low", - "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", - - "FFADisplayScore": "Ranking: {0} Score: {1}", - "FFATimeRemain": "Time Remaining: {0} second(s)", - - "GameOver": "Game Over", - "TOHEOptions": "TOHE Options", - "Cancel": "Cancel", - "Back": "Back", - "Yes": "Yes", - "No": "No", - "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", - "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, game will end to prevent black screen.", - "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred, to prevent black screen, turn off [{1}] in settings.", - "AntiBlackOutNotifyInLobby": "An unknown error occurred, to prevent black screen. Sadly, this error exists in all Town of Host versions. Automatic ending game is required, or the game will not start.", - "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent black screen.", - "AntiBlackOutRequestHostToForceEnd": "You were the reason of the black screen, you are asking the host to stop the game...", - "AntiBlackOutHostRejectForceEnd": "You were the reason of the black screen, and the host is not going to end the game, we will disconnect you soon.", - "NextPage": "Next Page", - "PreviousPage": "Previous Page", - "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", - "PressF1ShowMainRoleDes": "Press F1: Show Role Description", - "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", - "FakeTask": "Fake Tasks:", - "PVP.ATK": "Attack", - "PVP.DF": "Defend", - "PVP.RCO": "Recover", - "SettingsAreLoading": "Loading\nsettings...", - "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", - "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", - "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", - "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", - "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", - "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", - "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", - "IsGood": "{0} was a good guy", - "BelongTo": "{0} belongs to {1}", - "PlayerIsRole": "{0} was The {1}", - "PlayerExiled": "{0} was ejected", - "NoImpRemain": "0 Impostors remain", - "OneImpRemain": "1 Impostor remains", - "TwoImpRemain": "2 Impostors remain", - "ThreeImpRemain": "3 Impostors remain", - "ImpRemain": "{0} Impostors remaining", - "NeutralRemain": "\n{0} Neutral Killers remain", - "OneNeutralRemain": "\n{0} Neutral Killer remains", - "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", - "GameOverReason.HumansByTask": "The Crewmates completed all tasks", - "GameOverReason.HumansDisconnect": "Crewmates disconnected", - "GameOverReason.ImpostorByVote": "The Crewmates were ejected", - "GameOverReason.ImpostorByKill": "The Impostors killed everyone", - "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", - "GameOverReason.ImpostorDisconnect": "Impostors disconnected", - "FortuneTellerCheck.Honest": "Looks like [{0}] is being honest.", - "FortuneTellerCheck.Impulse": "Looks like [{0}] is suppressing some kind of impulse.", - "FortuneTellerCheck.Weirdo": "Looks like [{0}] is mixed in crowd.", - "FortuneTellerCheck.Blockbuster": "Looks like [{0}] got big desires to change something.", - "FortuneTellerCheck.Strong": "Looks like [{0}] has a strong power but is dim in society.", - "FortuneTellerCheck.Incomprehensible": "Looks like [{0}] is being misunderstood.", - "FortuneTellerCheck.Enthusiasm": "Looks like [{0}] speaks with everyone very enthusiastic.", - "FortuneTellerCheck.Disturbed": "Looks like [{0}] is disturbed by something.", - "FortuneTellerCheck.None": "Looks like [{0}] has just an ordinary life.", - "FortuneTellerCheck.Glitch": "TV2gPZ4wUJWYr{0}05gA6T5PKzBG17", - "FortuneTellerCheck.HideMsg": "Looks like [{0}] hides secrets.", - "FortuneTellerCheck.Love": "♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥", - "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", - "DevAndSpnTitle": "TOHE family", - "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", - "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", - "SunnyboyChance": "Sunnyboy Chance", - "BardChance": "Bard Chance", - "VampiressChance": "Vampiress Chance", - "NukerChance": "Nuker Chance", - "SkeldChance": "Chance that the map is The Skeld", - "MiraChance": "Chance that the map is MIRA HQ", - "PolusChance": "Chance that the map is Polus", - "DleksChance": "Chance that the map is dlekS ehT", - "AirshipChance": "Chance that the map is Airship", - "FungleChance": "Chance that the map is The Fungle", - "UseMoreRandomMapSelection": "Use a more random map selection", - "CamouflageMode.Default": "Default", - "CamouflageMode.Host": "Host", - "CamouflageMode.Random": "Random", - "CamouflageMode.OnlyRandomColor": "Only Random Color", - "CamouflageMode.Karpe": "KARPED1EM", - "CamouflageMode.Lauryn": "Lauryn", - "CamouflageMode.Moe": "Moe", - "CamouflageMode.Pyro": "Pyro", - "CamouflageMode.ryuk": "ryuk", - "CamouflageMode.Gurge44": "Gurge44", - "CamouflageMode.TommyXL": "TommyXL", - "DeathCmd.HeyPlayer": "Hey ", - "DeathCmd.YouAreRole": ", looks like you're the ", - "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", - "DeathCmd.KillerName": "You were killed by ", - "DeathCmd.KillerRole": "Their role is ", - "DeathCmd.DeathReason": "Your cause of death was ", - "DeathCmd.YourName": "You are ", - "DeathCmd.YourRole": "Your role is ", - "DeathCmd.Ejected": "You were ejected during a meeting", - "DeathCmd.Misfired": "You misfired.", - "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", - "DeathCmd.Lovers": "Your lover had died.", - - "RpsCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", - "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", - "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", - "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I just have the world's worst luck algorithm.", - - "CoinFlipCommandInfo": "This Command can only be used when in lobby or after you die.", - "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", - - "GNoCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", - "GNoLost": "Oh, you were so close! Just one more guess, and you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", - "GNoLow": "Oh, you're really nailing this! It's so low, I almost need a shovel to dig it up!\nYou have {0} guesses left!", - "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high, I need a telescope to see it from here! \nYou have {0} guesses left!", - "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", - - "RandCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", - "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", - - "ChanceToMiss": "Chance to miss a kill", - - "SoulCollectorPointsToWin": "Required number of souls", - "SoulCollectorTarget": "You have predicted the death of {0}", - "SoulCollectorTitle": "SOUL COLLECTOR", - "CollectOwnSoulOpt": "Can collect their own soul", - "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", - "SoulCollectorToDeath": "You have become Death!!!", - "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", - "CallMeetingIfDeath": "Call a meeting immediately after Death transforms", - "GetPassiveSouls": "Gain a passive soul every round", - "PassiveSoulGained": "You have gained a passive soul from the underworld.", - - "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", - "BakerToFamine": "You have become Famine!!!", - "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", - "BakerAlreadyBreaded": "That player already has bread!", - "BakerBreadUsedAlready": "You've already given a player bread this round!", - "BakerBreaded": "Player given bread", - "BakerBreadNeededToTransform": "Required number of bread to become Famine", - "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", - "BakerKillButtonText": "Bread", - - "ChronomancerKillCooldown": "Time to fully charge the kill button", - - "ShamanButtonText": "Voodoo", - "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", - "VoodooCooldown": "Voodoo Cooldown", - - "AdminWarning": "Admin Table in use!", - "VitalsWarning": "Vitals in use!", - "DoorlogWarning": "Doorlogs in use!", - "CameraWarning": "Cameras in use!", - "MinWaitAutoStart": "Minutes to wait before auto-starting", - "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", - "PlayerAutoStart": "Minimum Player Threshold to auto-start", - "AutoStartTimer": "Initial countdown for auto-starting", - "AutoPlayAgainCountdown": "Delay before re-entering lobby", - "AutoPlayAgain": "Auto Play Again", - "AutoRehost": "Auto Re-Host on Bad Disconnect", - "CountdownText": "Rejoining lobby in {0}s", - "TimeMasterSkillDuration": "Time Shield Duration", - "TimeMasterSkillCooldown": "Time Shield Cooldown", - "TimeMasterOnGuard": "Time Shield is active!", - "TimeMasterSkillStop": "Time Shield has ended!", - "TimeMasterVentButtonText": "Time Shield", - "BodyCannotBeReported": "Body could not be reported", - "BurstKillDelay": "Burst Kill Delay", - "BurstNotify": "That was a Burst! Get in a vent or die.", - "ImpCanBeBurst": "Impostors can become Burst", - "CrewCanBeBurst": "Crewmates can become Burst", - "NeutralCanBeBurst": "Neutrals can become Burst", - "BurstFailed": "Burst failed to bomb you", - "ShroudButtonText": "Shroud", - "ShroudCooldown": "Shroud Cooldown", - "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", - "LudopathRandomKillCD": "Maximum kill cooldown", - "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", - "GodfatherTargetCountMode": "Killer turns into", - "GodfatherCount_Refugee": "Refugee", - "GodfatherCount_Madmate": "Madmate", - "MissChance": "Chance To Miss", - "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", - "HawkMissed": "Missed!", - "HawkCanKillNum": "Max Slices", - "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", - "BloodMoonCanKillNum": "Max BloodLettings", - "BloodMoonTimeTilDie": "Time Until Death", - "DeathTimer": "Death In: {DeathTimer}s", - "BerserkerKillCooldown": "Berserker kill cooldown", - "BerserkerMax": "Max level that Berserker can reach", - "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", - "WarHasImpostorVision": "War Has Impostor Vision", - "BerserkerCanVent": "Berserker Can Vent", - "WarCanVent": "War Can Vent", - "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", - "BerserkerOneKillCooldown": "Kill cooldown after unlocking", - "BerserkerTwoCanScavenger": "Unlock scavenged kills", - "BerserkerThreeCanBomber": "Unlock bombed kills", - "BerserkerFourCanNotKill": "Become War", - "BerserkerMaxReached": "Maximum level reached!", - "BerserkerLevelChanged": "Increased level to {0}", - "BerserkerLevelRequirement": "Level requirement for unlock", - "KilledByBerserker": "Killed by Berserker", - "BerserkerToWar": "You have become War!!!", - "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", - "WarKillCooldown": "War kill cooldown", - - "ImpCanBeUnlucky": "Impostors can become Unlucky", - "CrewCanBeUnlucky": "Crewmates can become Unlucky", - "NeutralCanBeUnlucky": "Neutrals can become Unlucky", - "BlackmailerSkillCooldown": "Blackmail Cooldown", - "BlackmailerMax": "Maximum times blackmailed players may speak", - "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", - "BlackmaileKillTitle": "BLACKMAILER", - "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", - "UnluckyKillSuicideChance": "Chance to suicide from killing", - "UnluckyVentSuicideChance": "Chance to suicide from venting", - "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", - "UnluckySabotageSuicideChance": "Chance to suicide from opening a door", - "ImpCanBeVoidBallot": "Impostors can become VoidBallot", - "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", - "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", - "ImpCanBeAware": "Impostors can become Aware", - "NeutralCanBeAware": "Neutrals can become Aware", - "CrewCanBeAware": "Crewmates can become Aware", - "AwareKnowRole": "Knows the role of player", - "AwareInteracted": "{0} tried to reveal your role.", - "AwareTitle": "AWARE MESSAGE", - "LighterVentButtonText": "Light", - "LighterSkillCooldown": "Light Cooldown", - "LighterSkillDuration": "Light Duration", - "LighterVisionNormal": "Increased Vision", - "LighterVisionOnLightsOut": "Increased Vision During Lights Out", - "LighterSkillInUse": "Ability in use", - "LighterSkillStop": "Ability expired", - "StealthDarkened": "Darkened: {0}", - "StealthExcludeImpostors": "Ignore Impostors when Blinding", - "StealthDarkenDuration": "Blinding Duration", - "PenguinAbductTimerLimit": "Dragging Time", - "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", - "PenguinKillButtonText": "Drag", - "PenguinTimerText": "Drag Timer", - "PenguinTargetOnCheckMurder": "You are grabbed, Try escape that first!", - "WitnessTime": "Max Time after killing where killer appears red", - "WitnessButtonText": "Examine", - "WitnessFoundInnocent": "✓", - "WitnessFoundKiller": "⚠", - "SwapperMax": "Maximum swaps", - "CanSwapSelfVotes": "Can exchange your own votes.", - "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", - "CantSwapSelf": "Can't exchange of one's own vote", - "SwapVote": "The votes of {0} and {1} were swapped!", - "SwapDead": "Sorry, you can't swap votes after death.", - "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", - "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", - "Swap1": "Swap target 1 selected", - "Swap2": "Swap target 2 selected", - "CancelSwap": "Cleared your previous swap!", - "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your target is dead.", - "Swap1=Swap2": "The target you input is same as Swap target 1.\nPls input a different one", - "SwapTitle": "SWAPPER", - "SwapperTryHideMsg": "Try to hide Swapper's command", - "SwapperPreResult": "Currently you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", - "ImpCanBeFragile": "Impostors can become Fragile", - "NeutralCanBeFragile": "Neutrals can become Fragile", - "CrewCanBeFragile": "Crewmates can become Fragile", - "ImpCanKillFragile": "Impostors can force kill Fragile", - "NeutralCanKillFragile": "Neutrals can force kill Fragile", - "CrewCanKillFragile": "Crewmates can force kill Fragile", - "FragileKillerLunge": "Killer lunges on kill", - "CrusaderSkillLimit": "Maximum Crusades", - "CrusaderSkillCooldown": "Crusade Cooldown", - "CrusaderKillButtonText": "Crusade", - "JailorKillButtonText": "Jail", - "AgitaterKillButtonText": "Pass", - "HasSerialKillerBuddy": "Has Serial Killer buddy", - "ChanceToSpawn": "Chance to spawn", - "ChanceToSpawnAnother": "Chance to spawn another", - "BloodlustKillCD": "Bloodlust Kill Cooldown", - "BloodlustPlayerCount": "Max players alive for Bloodlust", - "ReflectHarmfulInteractions": "Reflect harmful interactions", - - "ImpCanBeDiseased": "Impostors can become Diseased", - "NeutralCanBeDiseased": "Neutrals can become Diseased", - "CrewCanBeDiseased": "Crewmates can become Diseased", - "DiseasedCDOpt": "Increase the cooldown by", - "DiseasedCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeAntidote": "Impostors can become Antidote", - "NeutralCanBeAntidote": "Neutrals can become Antidote", - "CrewCanBeAntidote": "Crewmates can become Antidote", - "AntidoteCDOpt": "Decrease the cooldown by", - "AntidoteCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeStubborn": "Impostors can become Stubborn", - "NeutralCanBeStubborn": "Neutrals can become Stubborn", - "CrewCanBeStubborn": "Crewmates can become Stubborn", - - "ImpCanBeAvanger": "Impostors can become Avenger", - "NeutralCanBeAvanger": "Neutrals can become Avenger", - "CrewCanBeAvanger": "Crewmates can become Avenger", - "ImpCanBeSleuth": "Impostors can become Sleuth", - "CrewCanBeSleuth": "Crewmates can become Sleuth", - "NeutralCanBeSleuth": "Neutrals can become Sleuth", - "SleuthCanKnowKillerRole": "Can find the role of the killer", - "SleuthNoticeKiller": "\nThe killer's role is {0}.", - "SleuthNoticeVictim": "{0}'s role is {1}.", - "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", - "BomberDiesInExplosion": "Bomber dies in their explosion", - "ImpostorsSurviveBombs": "Impostors survive bombs", - "NukeRadius": "Nuke radius (12x is very large)", - "NukeCooldown": "Nuke Cooldown", - - "MasochistKillMax": "Amount of attacks needed to win", - "GuessMasochist": "You just tried to guess a Masochist!\nThey're now one step closer to winning!\n\nDo not guess them again.", - "MasochistKill": "You were attacked!", - "SelfGuessMasochist": "You can't self guess as a Masochist, you cheater!", - "GuessMasochistBlocked": "Masochist cannot guess due to self guessing.", - - "RememberCooldown": "Imitate Cooldown", - "RefugeeKillCD": "Refugee's Kill Cooldown", - "RememberedNeutralKiller": "You remembered you were a neutral killer!", - "RememberedMaverick": "You remembered you were a Maverick!", - "RememberedPursuer": "You remembered you were a Pursuer!", - "RememberedFollower": "You remembered you were a Follower!", - "RememberedAmnesiac": "You failed to remember your role.", - "RememberedImitator": "You remmebered you were an Imitator.", - "RememberedImpostor": "You remembered you were an Impostor!", - "RememberedCrewmate": "You remembered you were a crewmate!", - "ImitatorImitated": "An Imitator imitated your role!", - "ImitatorInvalidTarget": "Imitation failed", - "RememberButtonText": "Remember", - "ImitatorKillButtonText": "Imitate", - "IncompatibleNeutralMode": "If neutral is incompatible, turn into", - "RememberedYourRole": "An Amnesiac rmembered your role!", - "YouRememberedRole": "You remembered who you were!", - - "BanditStealMode": "Steal Mode", - "BanditStealMode_OnMeeting": "On Meeting", - "BanditStealMode_Instantly": "Instantly", - "BanditMaxSteals": "Maximum Steals", - "BanditCanStealBetrayalAddon": "Can Steal Betrayal Addons", - "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", - "NoStealableAddons": "Could not steal addon from the player", - "StealCooldown": "Steal cooldown", - - "DoppelMaxSteals": "Maximum Steals", - - "NecromancerRevengeTime": "Necromancy time", - "NecromancerRevenge": "You have {0}s to kill {1}", - "NecromancerSuccess": "Necromancy complete! You live to see another day.", - "NecromancerHide": "Venting is disabled, hide from the Necromancer!", - "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", - "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", - "RetributionistKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", - "RetributionistKillDead": "Choose a living player to kill.", - "RetributionistKillSucceed": "{0} was killed by the Retributionist!", - "RetributionistKillDisable": "You can't retribute until your tasks are done.", - "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", - "RetributionistCanKillNum": "Max retributions", - "RetributionistKillTooManyDead": "Too many players are dead, you can't retribute.", - "MinimumPlayersAliveToRetri": "Maximum players needed to block retributions", - "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", - "BakerChangeChances": "Chance that Baker turns into Famine", - "BakerChange": "The Baker has turned into Famine!\nThe bread is now poisoned.\nIf the Famine is not exiled, all players with poisoned bread die.", - "BakerChangeNow": "A player has poisoned bread!\nExile the Famine or that player dies.", - "BakerChangeNONE": "The Famine has not given out poisoned bread.\nNobody will die of poison, but you should still exile the Famine.", - "PanAliveMessageTitle": "BAKER ", - "PanAlive": "The Baker has given out bread.", - "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", - - "TwisterCooldown": "Twist Cooldown", - "TwisterButtonText": "Twist", - "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", - "InstigatorAbilityLimit": "Ability Use Count", - "InstigatorKillsPerAbilityUse": "Kills per Ability use", - - "CrewCanFindCaptain": "Crewmates can find Captain", - "MadmateCanFindCaptain": "Madmates can find Captain", - "ReducedSpeed": "Reduced speed", - "ReducedSpeedTime": "Time duration for reduced speed", - "CaptainCanTargetNB": "Captain can target Neutral Benign", - "CaptainCanTargetNE": "Captain can target Neutral Evil", - "CaptainCanTargetNC": "Captain can target Neutral Chaos", - "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", - "CaptainCanTargetNK": "Captain can target Neutral Killer", - "CaptainSpeedReduced": "Captain reduced your speed", - "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", - "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", - - "InspectorTryHideMsg": "Hide Inspector's commands", - "MaxInspectCheckLimit": "Max inspections per game", - "InspectCheckLimitPerMeeting": "Max inspections per meeting", - "InspectCheckTargetKnow": "Targets know they were checked by Inspector", - "InspectCheckOtherTargetKnow": "Targets know who they were checked with", - "InspectorDead": "You can not use your power after death", - "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", - "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", - "InspectCheckSelf": "HA!! you thought it would be this easy. You can not check yourself", - "InspectCheckReveal": "HA! you thought it would be this easy. You can not check a role that is revealed", - "InspectCheckTitle": "INSPECTOR ", - "InspectCheckTrue": "{0} and {1} are in the same team!", - "InspectCheckFalse": "{0} and {1} are NOT in the same team!", - "InspectCheckTargetMsg": " were checked by Inspector.", - "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "InspectCheckNull": "Please select an ID of a living player to check their team", - "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", - "InspectCheckRevealTarget": "When tasks done, target knows team of other target", - "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", - - "EgoistCountMode.Original": "Original", - "EgoistCountMode.Neutral": "Neutral", - - "JailerJailCooldown": "Jail cooldown", - "JailerMaxExecution": "Maximum executions", - "JailerNBCanBeExe": "Can execute Neutral Benign", - "JailerNCCanBeExe": "Can execute Neutral Chaos", - "JailerNECanBeExe": "Can execute Neutral Evil", - "JailerNKCanBeExe": "Can execute Neutral Killing", - "JailerNACanBeExe": "Can execute Neutral Apocalypse", - "JailerCKCanBeExe": "Can execute Crew Killing", - "JailerTargetAlreadySelected": "You have already selected a target", - "SuccessfullyJailed": "Target successfully jailed", - "CantGuessJailed": "You can not guess the target", - "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", - "CanNotTrialJailed": "You can not trial the target.", - "notifyJailedOnMeeting": "Notify jailed player when meeting starts", - "JailedNotifyMsg": "You have been locked up in jail by the Jailer. No one can guess or judge you and you can only guess Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", - "JailerTitle": "Jailer", - - "CopyCatCopyCooldown": "Copy cooldown", - "CopyCatRoleChange": "Your role has been changed to {0}", - "CopyCatCanNotCopy": "You can not copy target's role", - "CopyButtonText": "Copy", - "CopyCrewVar": "Can copy evil variants of crew roles", - "CopyTeamChangingAddon": "Can copy team changing addon", - - "MaxCleanserUses": "Max cleanses", - "CleansedCanGetAddon": "Cleansed player can get Add-on", - "CleanserTitle": "CLEANSER", - "CleanserRemoveSelf": "You can not cleanse yourself", - "CleanserCantRemove": "Oops! the player can not be cleansed.", - "CleanserRemovedRole": "{0} has been cleansed. All their Addons will be removed after the meeting.", - "LostAddonByCleanser": "All your Addons were removed by the cleanser", - - "MaxProtections": "Max protections", - "KeeperHideVote": "Hide Keeper's vote", - "KeeperProtect": "You chose to protect {0}, your vote has been returned", - "KeeperTitle": "Keeper", - - "MaulRadius": "Maul Radius", - "ImpCanBeAutopsy": "Impostors can become Autopsy", - "CrewCanBeAutopsy": "Crewmates can become Autopsy", - "NeutralCanBeAutopsy": "Neutrals can become Autopsy", - "ImpCanBeCyber": "Impostors can become Cyber", - "CrewCanBeCyber": "Crewmates can become Cyber", - "NeutralCanBeCyber": "Neutrals can become Cyber", - "ImpKnowCyberDead": "Impostors know if Cyber died", - "CrewKnowCyberDead": "Crewmates know if Cyber died", - "NeutralKnowCyberDead": "Neutrals know if Cyber died", - "CyberKnown": "Everyone can see Cyber", - "ImpCanBeInfluenced": "Impostors can become Influenced", - "CrewCanBeInfluenced": "Crewmates can become Influenced", - "NeutralCanBeInfluenced": "Neutrals can become Influenced", - "ImpCanBeBewilder": "Impostors can become Bewilder", - "CrewCanBeBewilder": "Crewmates can become Bewilder", - "NeutralCanBeBewilder": "Neutrals can become Bewilder", - "KillerGetBewilderVision": "Killer gets Bewilder's vision", - "ImpCanBeOiiai": "Impostors can be OIIAI", - "CrewCanBeOiiai": "Crewmates can be OIIAI", - "NeutralCanBeOiiai": "Neutrals can be OIIAI", - "OiiaiCanPassOn": "OIIAI can pass on to the killer", - "NeutralChangeRolesForOiiai": "Neutrals turns to ", - "LostRoleByOiiai": "You got erased by OIIAI!", - "ImpCanBeSunglasses": "Impostors can become Sunglasses", - "CrewCanBeSunglasses": "Crewmates can become Sunglasses", - "NeutralCanBeSunglasses": "Neutrals can become Sunglasses", - "SunglassesVision": "Sunglasses Vision", - "ImpCanBeLoyal": "Impostors can become Loyal", - "CrewCanBeLoyal": "Crewmates can become Loyal", - "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", - "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", - "SheriffCanBeMadmate": "Sheriff can become Madmate", - "MayorCanBeMadmate": "Mayor can become Madmate", - "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", - "SnitchCanBeMadmate": "Snitch can become Madmate", - "JudgeCanBeMadmate": "Judge can become Madmate", - "MarshallCanBeMadmate": "Marshall can become Madmate", - "GanRetributionistCanBeMadmate": "Retributionist can be converted", - "RetributionistCanBeMadmate": "Retributionist can become Madmate", - "OverseerCanBeMadmate": "Overseer can become Madmate", - "GanSheriffCanBeMadmate": "Sheriff can be converted", - "GanMayorCanBeMadmate": "Mayor can be converted", - "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", - "GanJudgeCanBeMadmate": "Judge can be converted", - "GanMarshallCanBeMadmate": "Marshall can be converted", - "GanOverseerCanBeMadmate": "Overseer can be converted", - "RascalAppearAsMadmate": "Appear As Madmate On Ejection", - "CouncillorDead": "Sorry, you can't murder from the dead.", - "CouncillorMurderMax": "Sorry, you've reached the maximum amount of murders for the meeting.", - "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", - "Councillor_MurderKill": "{0} was judged.", - "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Councillor_MurderNull": "Please choose a living player to murder.", - "Councillor_MurderKillTitle": "COURT ", - "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", - "CouncillorCanMurderMadmate": "Can Murder Madmates", - "CouncillorCanMurderImpostor": "Can Murder Impostors", - "CouncillorTryHideMsg": "Try to hide Councillor's commands", - "DazzlerDazzled": "You were dazzled by the Dazzler!", - "DazzlerCauseVision": "Reduced vision", - "DazzlerDazzleLimit": "Max number of players affected by reduced vision", - "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", - "DazzleCooldown": "Dazzle Cooldown", - "DazzleButtonText": "Dazzle", - - "MoleVentButtonText": "Dig", - "MoleVentCooldown": "Dig cooldown", - - "AddictVentButtonText": "Get Fix", - "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", - "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerble", - - "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", - "AlchemistShieldDur": "Resistance Potion Duration", - "AlchemistInvisDur": "Invisibility Potion Duration", - "AlchemistVision": "Night Vision", - "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", - "AlchemistVisionDur": "Night Vision Potion Duration", - "AlchemistSpeed": "Speed Potion Boost", - "AlchemistVentButtonText": "Drink", - "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", - "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", - "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", - "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", - "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", - "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", - "AlchemistGotBloodlustPotion": "Potion of Harming: Kill the next player you touch", - "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", - "NoPotion": "You have no potions", - - "StoreShield": "Potion of Resistance", - "StoreSuicide": "Potion of Poison", - "StoreTP": "Potion of Warping", - "StoreSP": "Potion Of Speed", - "StoreQF": "Potion of Fixing", - "StoreBL": "Potion of Harming", - "StoreNS": "Potion of Night Vision", - "StoreNull": "None", - "PotionStore": "Potion in store: ", - "WaitQFPotion": "\nPotion of Fixing waiting for use", - - "AlchemistShielded": "Potion of Resistance started", - "AlchemistHasVision": "Potion of Night Vision started", - "AlchemistShieldOut": "Potion of Resistance ended", - "AlchemistVisionOut": "Potion of Night Vision ended", - "AlchemistPotionBloodlust": "You gained bloodlust", - "AlchemistHasSpeed": "Potion Of Speed started", - "AlchemistSpeedOut": "Potion Of Speed ended", - - "DeathpactDuration": "Death Pact duration", - "DeathPactCooldown": "Death Pact Assign Cooldown", - "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", - "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", - "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", - "DeathpactVisionWhileInPact": "Vision for players in Death Pact", - "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", - "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", - "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", - "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", - "DeathpactComplete": "Death Pact was concluded.", - "DeathpactExecuted": "Death Pact was executed.", - "DeathpactAverted": "Death Pact was averted.", - "DeathpactButtonText": "Assign", - "DevourerHideNameConsumed": "Hide the names of consumed players", - "DevourCooldown": "Devour Cooldown", - "DevourerButtonText": "Devour", - "EatenByDevourer": "Your skin was eaten by the Devourer", - "DevourerEatenSkin": "Target skin eaten", - "DevouredName": "Devoured", - "PitfallTrapCooldown": "Trap Cooldown", - "PitfallMaxTrapCount": "Number of Traps that can be set", - "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", - "PitfallTrapDuration": "Time the Trap remains active", - "PitfallTrapRadius": "Trap Radius", - "PitfallTrapFreezeTime": "Trap freeze time", - "PitfallTrapCauseVision": "Trap caused vision", - "PitfallTrapCauseVisionTime": "Trap caused vision time", - "PitfallTrap": "You have fallen into a trap!", - "ConsigliereDivinationMaxCount": "Maximum Reveals", - "RitualMaxCount": "Maximum Reveals", - "CleanserHideVote": "Hide Cleanser's vote", - "OracleSkillLimit": "Maximum Uses", - "OracleHideVote": "Hide Oracle's vote", - "OracleCheckReachLimit": "You're out of uses!", - "OracleCheckSelfMsg": "You can't even trust yourself, huh?", - "OracleCheckLimit": "Reminder: You have {0} uses left", - "OracleCheckMsgTitle": "ORACLE ", - "OracleCheck.NotCrewmate": "Appears to not be a crewmate", - "OracleCheck.Crewmate": "Appears to be a crewmate", - "OracleCheck.Neutral": "Appears to be a neutral", - "OracleCheck.Impostor": "Appears to be an Impostor", - "OracleCheck": "Target Results:", - "FailChance": "Chance of showing incorrect result", - "OracleCheckAddons": "Oracle checks add-ons", - "ChameleonCanVent": "Vent to disguise", - "ChameleonInvisState": "You are disguising!", - "ChameleonInvisStateOut": "Your disguise ended", - "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", - "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", - "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", - "ChameleonCooldown": "Disguise Cooldown", - "ChameleonDuration": "Disguise Duration", - "ChameleonRevertDisguise": "Expose", - "ChameleonDisguise": "Disguise", - "KillCooldownAfterCleaning": "Kill Cooldown On Clean", - "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", - "MedusaStoneBody": "Body stoned", - "MedusaReportButtonText": "Stone", - - "CursedSoulCurseCooldown": "Soul Snatch Cooldown", - "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", - "CursedSoulCurseMax": "Maximum Soul Snatches", - "CursedSoulKnowTargetRole": "Know the roles of Soulless players", - "CursedSoulCanCurseNeutral": "Neutral roles have souls", - "CursedSoulKillButtonText": "Snatch", - "SoullessByCursedSoul": "Your soul was snatched by a Cursed Soul", - "CursedSoulSoullessPlayer": "Soul snatched", - "CursedSoulInvalidTarget": "No soul found", - - "AdmireCooldown": "Admire Cooldown", - "AdmirerKnowTargetRole": "Know the roles of Admired players", - "AdmirerSkillLimit": "Skill Limit", - "AdmireButtonText": "Admire", - "AdmirerAdmired": "The Admirer admired you!", - "AdmiredPlayer": "Player admired", - "AdmirerInvalidTarget": "Target cannot be admired", - - "SpiritualistNoticeTitle": "SPIRITUALIST ", - "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", - "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", - "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", - "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", - "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", - "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", - "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", - "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", - "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", - "EnigmaClueHat1": "The Killer wears a Hat!", - "EnigmaClueHat2": "The Killer does not wear a Hat!", - "EnigmaClueHat3": "The Killer wears {0} as a Hat!", - "EnigmaClueSkin1": "The Killer wears a Skin!", - "EnigmaClueSkin2": "The Killer does not wear a Skin!", - "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", - "EnigmaClueVisor1": "The Killer wears a Visor!", - "EnigmaClueVisor2": "The Killer does not wear a Visor!", - "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", - "EnigmaCluePet1": "The Killer does have a Pet!", - "EnigmaCluePet2": "The Killer does not have a Pet!", - "EnigmaCluePet3": "The Killer has {0} as a Pet!", - "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", - "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", - "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", - "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", - "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", - "EnigmaClueColor1": "The Killer has a light color!", - "EnigmaClueColor2": "The Killer has a dark color!", - "EnigmaClueColor3": "The Killer's color is {0}!", - "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", - "EnigmaClueStatus1": "The Killer is currently inside a Vent!", - "EnigmaClueStatus2": "The Killer is currently on a Ladder!", - "EnigmaClueStatus3": "The Killer is already Dead!", - "EnigmaClueStatus4": "The Killer is still Alive!", - "EnigmaClueRole1": "The Killer is an Impostor!", - "EnigmaClueRole2": "The Killer is a Neutral!", - "EnigmaClueRole3": "The Killer is a Crewmate!", - "EnigmaClueRole4": "The Killer's Role is {0}!", - "EnigmaClueLevel1": "The Killer's Level is above 50!", - "EnigmaClueLevel2": "The Killer's Level is below 50!", - "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", - "EnigmaClueLevel4": "The Killer's Level is {0}!", - "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", - "EnigmaClueHatTitle": "Enigma Hat Clue!", - "EnigmaClueVisorTitle": "Enigma Visor Clue!", - "EnigmaClueSkinTitle": "Enigma Skin Clue!", - "EnigmaCluePetTitle": "Enigma Pet Clue!", - "EnigmaClueNameTitle": "Enigma Name Clue!", - "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", - "EnigmaClueColorTitle": "Enigma Color Clue!", - "EnigmaClueLocationTitle": "Enigma Location Clue!", - "EnigmaClueStatusTitle": "Enigma Status Clue!", - "EnigmaClueRoleTitle": "Enigma Role Clue!", - "EnigmaClueLevelTitle": "Enigma Level Clue!", - "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", - - "ChiefOfPoliceSkillCooldown": "Cooldown for recruiting sheriffs", - "PolicCanImpostorAndNeutarl": "You can recruit Impostor or Kill Neutral to become sheriffs", - "SheriffSuccessfullyRecruited": "You recruited a sheriff.", - "BeSheriffByPolice": "You've been recruited by the police chief! Serve the crew!", - "ChiefOfPoliceKillButtonText": "Recruitment", - "VotesPerKill": "Votes gained for each kill", - "PickpocketGetVote": "You've got {0} votes", - "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "VultureNumberOfReportsToWin": "Bodies needed to win", - "VultureReportBody": "Body eaten!", - "VultureEatButtonText": "Consume", - "VultureReportCooldown": "Eat Cooldown", - "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", - "VultureCooldownUp": "Eat Cooldown finished", - - "TasksMarkPerRound": "Number of tasks that can be marked in one round", - "TaskinatorBombPlanted": "Bomb has been planted", - - "ShieldDuration": "Shield duration", - "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", - "BenefactorTaskMarked": "Task marked successfully", - "BenefactorTargetGotShield": "You got shield by Benefactor", - - "PirateTryHideMsg": "Hide Pirate's commands", - "SuccessfulDuelsToWin": "Number of successful duels needed to win", - "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the duel if you choose the same option as the target", - "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", - "PirateTitle": "PIRATE ", - "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", - "PirateDead": "Ye be dead. Ye cannot duel anymore.", - "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", - "DuelDone": "Ye 'ave chosen yer option fer the duel.\nWait fer the meetin' to end to see the result.", - "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", - "PirateDuelButtonText": "Duel", - "DuelCooldown": "Duel Cooldown", - "Rock": "Rock", - "Paper": "Paper", - "Scissors": "Scissors", - "Heads": "Heads", - "Tails": "Tails", - "SpyRedNameDur": "Colored Name Duration", - "SpyInteractionBlocked": "Block kill button interaction", - "AgitaterBombCooldown": "Agitator bomb cooldown", - "AgitaterPassCooldown": "Bomb pass cooldown", - "BombExplodeCooldown": "Bomb explode cooldown", - "AgitaterPassNotify": "Bomb successfully passed", - "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", - "AgitaterCanGetBombed": "Agitator can get bomb", - "AgitaterAutoReportBait": "Agitator Auto Report Bait", - - "SeekerPointsToWin": "Number of points required to win", - "SeekerTagCooldown": "Tag Cooldown", - "SeekerNotify": "Your target is {0}", - "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", - - "PixiePointsToWin": "Number of points required to win", - "MaxTargets": "Maximum number of targets per round", - "MarkCooldown": "Mark cooldown", - "PixieSuicide": "Pixie suicides if target is not voted out", - "PixieMaxTargetReached": "You have already selected all the targets this round", - "PixieTargetAlreadySelected": "Target is already selected", - "PixieButtonText": "Mark", - - "PlagueBearerCD": "Plague cooldown", - "PestilenceCD": "Pestilence Kill cooldown", - "PlagueBearerAlreadyPlagued": "Player has already been plagued", - "PlagueBearerToPestilence": "You have turned into Pestilence!!", - "PestilenceCanVent": "Pestilence Can Vent", - "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", - "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", - "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", - "RomanticBetCooldown": "Pick Partner Cooldown", - "RomanticProtectCooldown": "Protect Cooldown", - "RomanticBetPlayer": "You picked your partner", - "RomanticBetOnYou": "The Romantic chose you as their Partner!", - "VengefulKCD": "Vengeful Romantic Kill Cooldown", - "VengefulCanVent": "Vengeful Romantic Can Vent", - "RuthlessKCD": "Ruthless Romantic Kill Cooldown", - "RuthlessCanVent": "Ruthless Romantic Can Vent", - "RomanticProtectPartner": "Your partner is under protection", - "RomanticIsProtectingYou": "The Romantic is protecting you", - "ProtectingOver": "Shield expired", - "RomanticProtectDuration": "Protect Duration", - "RomanticKnowTargetRole": "Romantic knows their target's role", - "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", - - "GuessMasterMisguess": "{0} misguessed", - "GuessMasterTargetRole": "Someone tried to guess {0}", - "GuessMasterTitle": "Guess Master ", - - "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", - "DCanGuessImpostors": "Can Guess Impostors", - "DCanGuessCrewmates": "Can Guess Crewmates", - "DCanGuessNeutrals": "Can Guess Neutrals", - "DCanGuessAdt": "Can Guess Add-Ons", - "DoomsayerAdvancedSettings": "Advanced Settings", - "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", - "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", - "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", - "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", - "DoomsayerTryHideMsg": "Hide Doomsayer's commands", - "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", - "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", - "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", - "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", - "DoomsayerGuessCountTitle": "DOOMSAYER", - "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", - - "EveryoneCanKnowMini": "Everyone can see the Mini", - "CanBeEvil": "Mini can be an Impostor", - "EvilMiniSpawnChances": "Probability of Mini being an Impostor", - "GuessMini": "Sorry, you can't hurt a kid Mini.", - "GrowUpDuration": "Time required to grow (s)", - "MajorCooldown": "Kill Cooldown when over 18", - "UpDateAge": "Display age change in real-time", - "Cantkillkid": "You can't kill a Mini that hasn't grown up.", - "CantEat": "You can't eat a Mini that hasn't grown up", - "CantShroud": "You can't control a Mini that hasn't grown up.", - "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", - "CantRecruit": "You can't recruit a Mini that hasn't grown up.", - "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", - "MiniUp": "You're a year older!", - "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilty while you can no longer guess.\nYou can guess again after you have grown up.", - "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", - "CountMeetingTime": "Meeting time can continue to grow", - "YouKillRandomizer1": "You kill Randomizer, Self report!", - "YouKillRandomizer2": "You kill Randomizer, Cannot move!", - "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", - "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", - "MadmateCanBeHurried": "Madmate can be Hurried on game start", - "TaskBasedCrewCanBeHurried": "Task based Crews can be Hurried", - "HurriedCanBeConverted": "Hurried can be recruited in game (excludes madmate)", - "Developer": "Developer", - "Sponsor": "Sponsor", - "Booster": "Server Booster", - "Translator": "Translator", - "NoAccess": "Unauthorized Access!!!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", - "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", - "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", - "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", - "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", - "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", - "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", - "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", - "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", - "DCNotify.Inactivity": "The lobby closed due to inactivity.", - "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", - "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", - "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", - "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", - "RoleType.VanillaRoles": "★ Vanilla Roles", - "RoleType.ImpKilling": "★ Impostor Killing Roles", - "RoleType.ImpSupport": "★ Impostor Support Roles", - "RoleType.ImpConcealing": "★ Impostor Concealing Roles", - "RoleType.ImpHindering": "★ Impostor Hindering Roles", - "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", - "RoleType.Madmate": "★ Madmate Roles", - "RoleType.CrewSupport": "★ Crewmate Support Roles", - "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", - "RoleType.CrewPower": "★ Crewmate Power Roles", - "RoleType.CrewKilling": "★ Crewmate Killing Roles", - "RoleType.CrewBasic": "★ Crewmate Basic Roles", - "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", - "RoleType.NeutralEvil": "★ Neutral Evil Roles", - "RoleType.NeutralBenign": "★ Neutral Benign Roles", - "RoleType.NeutralChaos": "★ Neutral Chaos Roles", - "RoleType.NeutralKilling": "★ Neutral Killing Roles", - "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", - "RoleType.Harmful": "★ Harmful Add-ons", - "RoleType.Support": "★ Supportive Add-ons", - "RoleType.Helpful": "★ Helpful Add-ons", - "RoleType.Mixed": "★ Mixed Add-ons", - "RoleType.Misc": "★ Miscellaneous Add-ons", - "RoleType.Impostor": "★ Impostor Add-ons", - "RoleType.Neut": "★ Neutral Add-ons", - "SubType.Impostor": "★ Impostors", - "SubType.Shapeshifter": "★ Shapeshifters", - "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", - "SubType.Madmate": "★ Madmates", - "SubType.CrewmateKilling": "★ Crewmate Killings", - "SubType.Crewmate": "★ Regular Crewmates", - "SubType.New": "★ New!", - "CrewmateRoles": "★ Crewmate Roles ★", - "ImpostorRoles": "★ Impostor Roles ★", - "NeutralRoles": "★ Neutral Roles ★", - "AddonRoles": "★ Add-ons ★", - "WinnerRoleText.Impostor": "Impostors Win!", - "WinnerRoleText.Crewmate": "Crewmates Win!", - "WinnerRoleText.Apocalypse": "Apocalypse Wins!", - "WinnerRoleText.Terrorist": "Terrorist Wins!", - "WinnerRoleText.Jester": "Jester Wins!", - "WinnerRoleText.Lovers": "Lovers Win!", - "WinnerRoleText.Executioner": "Executioner Wins!", - "WinnerRoleText.Arsonist": "Arsonist Wins!", - "WinnerRoleText.Revolutionist": "Revolutionist Wins!", - "WinnerRoleText.Jackal": "Jackals Win!", - "WinnerRoleText.God": "God Wins!", - "WinnerRoleText.Vector": "Vector Wins!", - "WinnerRoleText.Innocent": "Innocent Wins!", - "WinnerRoleText.Pelican": "Pelican Wins!", - "WinnerRoleText.Youtuber": "YouTuber Wins!", - "WinnerRoleText.Necromancer": "Necromancer Wins!", - "WinnerRoleText.Egoist": "Egoists Win!", - "WinnerRoleText.Demon": "Demon Wins!", - "WinnerRoleText.Stalker": "Stalker Wins!", - "WinnerRoleText.Workaholic": "Workaholic Wins!", - "WinnerRoleText.Collector": "Collector Wins!", - "WinnerRoleText.BloodKnight": "Blood Knight Wins!", - "WinnerRoleText.Poisoner": "Poisoner Wins!", - "WinnerRoleText.Huntsman": "Huntsman Wins!", - "WinnerRoleText.HexMaster": "Hex Master Wins!", - "WinnerRoleText.Cultist": "Cultist Wins!", - "WinnerRoleText.Wraith": "Wraith Wins!", - "WinnerRoleText.SerialKiller": "Serial Killers Win!", - "WinnerRoleText.Juggernaut": "Juggernaut Wins!", - "WinnerRoleText.Infectious": "Infectious Wins!", - "WinnerRoleText.Virus": "Virus Wins!", - "WinnerRoleText.Phantom": "Phantom Wins!", - "WinnerRoleText.Jinx": "Jinx Wins!", - "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", - "WinnerRoleText.PotionMaster": "Potion Master Wins!", - "WinnerRoleText.Pickpocket": "Pickpocket Wins!", - "WinnerRoleText.Traitor": "Traitor Wins!", - "WinnerRoleText.Vulture": "Vulture Wins!", - "WinnerRoleText.Medusa": "Medusa Wins!", - "WinnerRoleText.Famine": "Famine Wins!", - "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", - "WinnerRoleText.Glitch": "Glitch Wins!", - "WinnerRoleText.Pestilence": "Pestilence Wins!", - "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", - "WinnerRoleText.Masochist": "Masochist Wins!", - "WinnerRoleText.Doomsayer": "Doomsayer Wins!", - "WinnerRoleText.Pirate": "Pirate Wins!", - "WinnerRoleText.Shroud": "Shroud Wins!", - "WinnerRoleText.Werewolf": "Werewolf Wins!", - "WinnerRoleText.Seeker": "Seeker Wins!", - "WinnerRoleText.Agitater": "Agitator Wins!", - "WinnerRoleText.Occultist": "Occultist Wins!", - "WinnerRoleText.SoulCollector": "Soul Collector Wins!", - "WinnerRoleText.NiceMini": "Nice Mini Wins!", - "WinnerRoleText.Mini": "Nice Mini was killed", - "WinnerRoleText.Bandit": "Bandit Wins!", - "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", - "WinnerRoleText.Solsticer": "Solsticer Wins!", - "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", - "WinnerRoleText.Doppelganger": "Doppelganger Wins!", - "AdditionalWinnerRoleText.Sidekick": "Sidekick", - "AdditionalWinnerRoleText.Taskinator": "Taskinator", - "AdditionalWinnerRoleText.Opportunist": "Opportunist", - "AdditionalWinnerRoleText.Lawyer": "Lawyer", - "AdditionalWinnerRoleText.Hater": "Hater", - "AdditionalWinnerRoleText.Provocateur": "Provocateur", - "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", - "AdditionalWinnerRoleText.Follower": "Follower", - "AdditionalWinnerRoleText.Pursuer": "Pursuer", - "AdditionalWinnerRoleText.Jester": "Jester", - "AdditionalWinnerRoleText.Lovers": "Lovers", - "AdditionalWinnerRoleText.Executioner": "Executioner", - "AdditionalWinnerRoleText.Phantom": "Phantom", - "AdditionalWinnerRoleText.Maverick": "Maverick", - "AdditionalWinnerRoleText.Shaman": "Shaman", - "AdditionalWinnerRoleText.Pixie": "Pixie", - "AdditionalWinnerRoleText.NiceMini": "Nice Mini", - "AdditionalWinnerRoleText.Romantic": "Romantic", - "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", - "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", - "ErrorEndText": "An error occurred", - "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", - "ForceEnd": "Aborted", - "EveryoneDied": "Everyone died", - "ForceEndText": "Host has aborted the game", - "NiceMiniDied": "Nice Mini was killed", - "HaterMisFireKillTarget": "Hater kills target when misfire", - "HaterChooseConverted": "Select addons that Hater can kill", - "HaterCanKillMadmate": "Can kill madmate", - "HaterCanKillCharmed": "Can kill charmed", - "HaterCanKillLovers": "Can kill lovers", - "HaterCanKillSidekick": "Can kill jackal team", - "HaterCanKillEgoist": "Can kill egoist", - "HaterCanKillInfected": "Can kill infected team", - "HaterCanKillContagious": "Can kill virus team", - "HaterCanKillAdmired": "Can kill admirer", - "AutoMuteUs": "Enable it if you use AutoMuteUs", - "HorseMode": "Enable to become a horse", - "LongMode": "Enable to have a long neck", - "InfluencedChangeVote": "oops!You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", - - - "FFA": "Free For All", - "ModeFFA": "Gamemode: FFA", - "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the mean time!", - "FFA_GameTime": "Maximum Game Length", - "FFA_KCD": "Kill Cooldown", - "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", - "FFA_EnableRandomAbilities": "Enable Random Events", - "FFA_ShieldDuration": "Shield Duration", - "FFA_IncreasedSpeed": "Increased Speed", - "FFA_DecreasedSpeed": "Decreased Speed", - "FFA_ModifiedSpeedDuration": "Modified Speed Duration", - "FFA_LowerVision": "Lowered Vision", - "FFA_ModifiedVisionDuration": "Lowered Vision Duration", - "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", - "FFA-Event-GetShield": "You have a temporary shield!", - "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", - "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", - "FFA-Event-GetHighKCD": "You got a higher kill cooldown", - "FFA-Event-GetLowVision": "You have lower vision temporarily", - "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", - "FFA-Event-GetTP": "You got teleported to a random vent!", - "FFA-Event-RandomTP": "Everyone was swapped with someone", - "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", - "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", - "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", - "FFA_TargetIsShielded": "The player you tried to kill is shielded!", - "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", - "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", - "Killer": "FREE FOR ALL", - "KillerInfo": "Kill Everyone to Win", - - "Hide&SeekTOHE": "Hide & Seek", - "MenuTitle.Hide&Seek": "Hide & Seek Settings", - "NumImpostorsHnS": "Num Impostors", - - "EveryOneKnowSolsticer": "Every One Know who is Solsticer", - "SolsticerKnowItsKiller": "Solsticer knows the role of whom used kill button on it", - "SolsticerSpeed": "Movement speed of Solsticer", - "SolsticerRemainingTaskWarned": "Remaining tasks to be known", - "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", - "SolsticerMurdered": "{0} attempted to murder you!", - "MurderSolsticer": "You stopped Solsticer this round!", - "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", - "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", - "SolsticerTitle": "Solsticer", - "GuessSolsticer": "Sorry, but you can not guess Solsticer!", - "VoteSolsticer": "Sorry, but you can not vote Solsticer!", - "SolsticerTasksReset": "Your tasks get reset!", - "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", - "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", - - "VoteDead": "The player you voted for was exhiled before the meeting concluded. Your vote was rescinded.", - - "ImpCanBeSilent": "Impostors can become Silent", - "CrewCanBeSilent": "Crewmates can become Silent", - "NeutralCanBeSilent": "Neutrals can become Silent", - "LastMessageReplay": "Last System Message Replay", - "Contributor": "Contributor", - - "dbConnect.InitFailure": "Error while connecting to TOHE api, pls check your network connection and retry login!", - "dbConnect.nullFriendCode": "This build of TOHE is not aviliable to users with no friendcode!", - - "ImpCanBeSusceptible": "Impostors can become Susceptible", - "CrewCanBeSusceptible": "Crewmates can become Susceptible", - "NeutralCanBeSusceptible": "Neutrals can become Susceptible", - - "Quizmaster": "Quizmaster", - "QuizmasterInfo": "Quiz people to kill them in meetings", - "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will \"?!\" next to their name. If the player answers a question wrongly, or doesn't answer, they will die. If the Quizmaster was killed/ejected in the same meeting, the player will live.\nThe Quizmaster cannot mark multiple people in the same round", - "QuizmasterKillButtonText": "Quiz", - - "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", - "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", - "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", - "QuizmasterChat.CorrectTarget": "Correct", - "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", - "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", - "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", - "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", - "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", - "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die", - "QuizmasterChat.Title": "Quizmaster Information", - "QuizmasterChat.CantAnswer": "As the quizmaster you can't answer questions", - "QuizmasterChat.AnswerNotValid": "Your answer must be A, B or C", - "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", - - "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", - "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", - "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", - "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", - "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", - - "Quizmaster.None": "None", - - "QuizmasterSabotages.Lights": "Lights", - "QuizmasterSabotages.Reactor": "Reactor", - "QuizmasterSabotages.Communications": "Communications", - "QuizmasterSabotages.O2": "O2", - "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", - "QuizmasterAnswers.One": "One", - "QuizmasterAnswers.Two": "Two", - "QuizmasterAnswers.Three": "Three", - "QuizmasterAnswers.Four": "Four", - "QuizmasterAnswers.Five": "Five", - "QuizmasterAnswers.Pacifist": "Pacifist", - "QuizmasterAnswers.Vampire": "Vampire", - "QuizmasterAnswers.Snitch": "Snitch", - "QuizmasterAnswers.Vigilante": "Vigilante", - "QuizmasterAnswers.Jackal": "Jackal", - "QuizmasterAnswers.Mole": "Mole", - "QuizmasterAnswers.Sniper": "Sniper", - "QuizmasterAnswers.Coven": "Coven", - "QuizmasterAnswers.Sabotuer": "Sabotuer", - "QuizmasterAnswers.Sorcerers": "Sorcerers", - "QuizmasterAnswers.Killer": "Killer", - "QuizmasterAnswers.Edition": "Edition", - "QuizmasterAnswers.Experimental": "Experimental", - "QuizmasterAnswers.Enhanced": "Enhanced", - "QuizmasterAnswers.Edited": "Edited", - - "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", - "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", - "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", - "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", - "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called last meeting before this meeting?", - "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", - "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", - "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", - "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", - "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed an update later?", - "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", - "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", - "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", - "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", - "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", - "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", - "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", - - "DeathReason.WrongAnswer": "Wrong Quiz Answer", - - "TPCooldown": "Teleport Cooldown", - "RiftsTooClose": "Location too close to the first rift", - "RiftCreated": "Rift made successfully", - "RiftsDestroyed": "All rifts Destroyed", - "RiftRadius": "Rift Radius", - - "TiredVision": "Vision When Tired", - "TiredSpeed": "Speed When Tired", - "TiredDur": "Tired Duration", - "ImpCanBeTired": "Impostors can become Tired", - "CrewCanBeTired": "Crewmates can become Tired", - "NeutralCanBeTired": "Neutrals can become Tired", - - "TiredNotify": "Zzz..", - - "PlagueDoctorInfectLimit": "Infect Limit", - "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", - "PlagueDoctorInfectTime": "Infect Time", - "PlagueDoctorInfectDistance": "Infect Distance", - "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", - "PlagueDoctorCanInfectSelf": "Can Infect Self", - "PlagueDoctorCanInfectVent": "Can Infect While In Vent", - "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", - - "StatueSlow": "Statue Slowness", - "StatuePeopleToSlow": "People Needed To Slow", - - "ImpCanBeStatue": "Impostors can become Statue", - "CrewCanBeStatue": "Crewmates can become Statue", - "NeutralCanBeStatue": "Neutrals can become Statue", - - "WardenIncreaseSpeed": "Increase Speed By", - "WardenWarn": "DANGER! RUN!", - - "MinionAbilityTime": "Ability Duration" + "LanguageID": "0", + "HostText": "Host", + "HostColor": "#902efd", + "IconColor": "#4bf4ff", + "Icon": "♥", + "HideHostText": "Hide 'Host♥' Text", + "NameColor": "#ffc0cb", + "kofi": "Ko-Fi", + "update": "Update", + "GitHub": "GitHub", + "Discord": "Discord", + "Website": "Website", + "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", + + "SubText.Crewmate": "Find and exile the Impostors", + "SubText.Impostor": "Sabotage and kill everyone", + "SubText.Neutral": "Work alone to achieve your victory", + "SubText.Madmate": "Help the Impostors", + + "TypeImpostor": "Impostors", + "TypeCrewmate": "Crewmates", + "TypeNeutral": "Neutrals", + "TypeAddon": "Add-ons", + "GuesserMode": "Guesser Mode", + + "TeamImpostor": "Impostor", + "TeamNeutral": "Neutral", + "TeamCrewmate": "Crewmate", + "TeamMadmate": "Madmate", + + "YouAreCrewmate": "You are a Crewmate", + "YouAreImpostor": "You are an Impostor", + "YouAreNeutral": "You are a Neutral", + "YouAreMadmate": "You are a Madmate", + + + "Role_Crewmate": "Crewmate", + "Role_Jester": "Jester", + "Role_Opportunist": "Opportunist", + "Role_Celebrity": "Celebrity", + "Role_Bodyguard": "Bodyguard", + "Role_Dictator": "Dictator", + "Role_Mayor": "Mayor", + "Role_Doctor": "Doctor", + "Role_Maverick": "Maverick", + "Role_Pursuer": "Pursuer", + "Role_Follower": "Follower", + "Role_Amnesiac": "Amnesiac", + "Role_Imitator": "Imitator", + "Role_Sheriff": "Sheriff", + "Role_Knight": "Knight", + "Role_Deputy": "Deputy", + "Role_NoChange": "Don't change the role", + + "CrewmatesCanGuess": "Crewmates can guess", + "ImpostorsCanGuess": "Impostors can guess", + "NeutralKillersCanGuess": "Neutral Killers can guess", + "PassiveNeutralsCanGuess": "Passive Neutrals can guess", + + "CanGuessAddons": "Can Guess Add-ons", + "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", + "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", + "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", + "GuessImmune": "Sorry, but target is immune to being guessed!", + + + "GM": "Game Master", + "Sunnyboy": "Sunnyboy", + "Bard": "Bard", + "Nuker": "Nuker", + "Crewmate": "Crewmate", + "CrewmateTOHE": "Crewmate", + "Engineer": "Engineer", + "EngineerTOHE": "Engineer", + "Scientist": "Scientist", + "ScientistTOHE": "Scientist", + "GuardianAngel": "Guardian Angel", + "GuardianAngelTOHE": "Guardian Angel", + "Impostor": "Impostor", + "ImpostorTOHE": "Impostor", + "Shapeshifter": "Shapeshifter", + "ShapeshifterTOHE": "Shapeshifter", + + "BountyHunter": "Bounty Hunter", + "Fireworker": "Fireworker", + "Mercenary": "Mercenary", + "ShapeMaster": "Shapemaster", + "Vampire": "Vampire", + "Vampiress": "Vampiress", + "Warlock": "Warlock", + "Ninja": "Ninja", + "Zombie": "Zombie", + "Anonymous": "Anonymous", + "Miner": "Miner", + "KillingMachine": "Killing Machine", + "Escapist": "Escapist", + "Witch": "Witch", + "Nemesis": "Nemesis", + "Bloodmoon": "Bloodmoon", + "Puppeteer": "Puppeteer", + "Mastermind": "Mastermind", + "TimeThief": "Time Thief", + "Sniper": "Sniper", + "Undertaker": "Undertaker", + "RiftMaker": "Rift Maker", + "EvilTracker": "Evil Tracker", + "EvilGuesser": "Evil Guesser", + "AntiAdminer": "Anti Adminer", + "Arrogance": "Arrogance", + "Bomber": "Bomber", + "Scavenger": "Scavenger", + "Trapster": "Trapster", + "Gangster": "Gangster", + "Cleaner": "Cleaner", + "Lightning": "Lightning", + "Greedy": "Greedy", + "CursedWolf": "Cursed Wolf", + "SoulCatcher": "Soul Catcher", + "QuickShooter": "Quick Shooter", + "Camouflager": "Camouflager", + "Eraser": "Eraser", + "Butcher": "Butcher", + "Hangman": "Hangman", + "Swooper": "Swooper", + "Crewpostor": "Crewpostor", + "Wildling": "Wildling", + "Trickster": "Trickster", + "Vindicator": "Vindicator", + "Parasite": "Parasite", + "Disperser": "Disperser", + "Inhibitor": "Inhibitor", + "Saboteur": "Saboteur", + "Councillor": "Councillor", + "Dazzler": "Dazzler", + "Deathpact": "Deathpact", + "Devourer": "Devourer", + "Consigliere": "Consigliere", + "Morphling": "Morphling", + "Twister": "Twister", + "Lurker": "Lurker", + "Visionary": "Visionary", + "Refugee": "Refugee", + "Underdog": "Underdog", + "Ludopath": "Ludopath", + "Godfather": "Godfather", + "Chronomancer": "Chronomancer", + "Pitfall": "Pitfall", + "EvilMini": "Evil Mini", + "Blackmailer": "Blackmailer", + "Instigator": "Instigator", + "LazyGuy": "Lazy Guy", + "SuperStar": "Super Star", + "Celebrity": "Celebrity", + "Cleanser": "Cleanser", + "Keeper": "Keeper", + "Knight": "Knight", + "Mayor": "Mayor", + "Psychic": "Psychic", + "Mechanic": "Mechanic", + "Sheriff": "Sheriff", + "Vigilante": "Vigilante", + "Jailer": "Jailer", + "CopyCat": "Copycat", + "Snitch": "Snitch", + "Marshall": "Marshall", + "SpeedBooster": "Speed Booster", + "Doctor": "Doctor", + "Dictator": "Dictator", + "Detective": "Detective", + "NiceGuesser": "Nice Guesser", + "GuessMaster": "Guess Master", + "Transporter": "Transporter", + "TimeManager": "Time Manager", + "Veteran": "Veteran", + "Bastion": "Bastion", + "Bodyguard": "Bodyguard", + "Deceiver": "Deceiver", + "Grenadier": "Grenadier", + "Medic": "Medic", + "FortuneTeller": "Fortune Teller", + "Judge": "Judge", + "Mortician": "Mortician", + "Medium": "Medium", + "Pacifist": "Pacifist", + "Observer": "Observer", + "Monarch": "Monarch", + "Overseer": "Overseer", + "Coroner": "Coroner", + "Tracker": "Tracker", + "Merchant": "Merchant", + "President": "President", + "Hawk": "Hawk", + "Retributionist": "Retributionist", + "Deputy": "Deputy", + "Investigator": "Investigator", + "Guardian": "Guardian", + "Addict": "Addict", + "Mole": "Mole", + "Alchemist": "Alchemist", + "Tracefinder": "Tracefinder", + "Oracle": "Oracle", + "Spiritualist": "Spiritualist", + "Chameleon": "Chameleon", + "Inspector": "Inspector", + "Captain": "Captain", + "Admirer": "Admirer", + "TimeMaster": "Time Master", + "Crusader": "Crusader", + "Reverie": "Reverie", + "Lookout": "Lookout", + "Telecommunication": "Telecommunication", + "Lighter": "Lighter", + "TaskManager": "Task Manager", + "Witness": "Witness", + "Swapper": "Swapper", + "ChiefOfPolice": "Police Commissioner", + "NiceMini": "Nice Mini", + "Mini": "Mini", + "Spy": "Spy", + "Randomizer": "Randomizer", + "Enigma": "Enigma", + "Jester": "Jester", + "Arsonist": "Arsonist", + "Pyromaniac": "Pyromaniac", + "Kamikaze": "Kamikaze", + "Huntsman": "Huntsman", + "Terrorist": "Terrorist", + "Executioner": "Executioner", + "Lawyer": "Lawyer", + "Opportunist": "Opportunist", + "Vector": "Vector", + "Jackal": "Jackal", + "God": "God", + "Innocent": "Innocent", + "Stealth": "Stealth", + "Penguin": "Penguin", + "Pelican": "Pelican", + "PlagueDoctor": "Plague Scientist", + "Revolutionist": "Revolutionist", + "Hater": "Hater", + "Demon": "Demon", + "Stalker": "Stalker", + "Workaholic": "Workaholic", + "Solsticer": "Solsticer", + "Collector": "Collector", + "Provocateur": "Provocateur", + "BloodKnight": "Blood Knight", + "Apocalypse": "Apocalypse Team", + "PlagueBearer": "Plaguebearer", + "Pestilence": "Pestilence", + "SoulCollector": "Soul Collector", + "Death": "Death", + "Baker": "Baker", + "Famine": "Famine", + "Berserker": "Berserker", + "War": "War", + "Glitch": "Glitch", + "Sidekick": "Sidekick", + "Follower": "Follower", + "Cultist": "Cultist", + "SerialKiller": "Serial Killer", + "Juggernaut": "Juggernaut", + "Infectious": "Infectious", + "Virus": "Virus", + "Pursuer": "Pursuer", + "Phantom": "Phantom", + "Pirate": "Pirate", + "Agitater": "Agitator", + "Maverick": "Maverick", + "CursedSoul": "Cursed Soul", + "Pickpocket": "Pickpocket", + "Traitor": "Traitor", + "Vulture": "Vulture", + "Taskinator": "Taskinator", + "Benefactor": "Benefactor", + "Medusa": "Medusa", + "Spiritcaller": "Spiritcaller", + "Amnesiac": "Amnesiac", + "Imitator": "Imitator", + "Bandit": "Bandit", + "Doppelganger": "Doppelganger", + "Masochist": "Masochist", + "Doomsayer": "Doomsayer", + "Shroud": "Shroud", + "Werewolf": "Werewolf", + "Shaman": "Shaman", + "Seeker": "Seeker", + "Pixie": "Pixie", + "Occultist": "Occultist", + "SchrodingersCat": "Schrodingers Cat", + "Romantic": "Romantic", + "VengefulRomantic": "Vengeful Romantic", + "RuthlessRomantic": "Ruthless Romantic", + "Poisoner": "Poisoner", + "HexMaster": "Hex Master", + "Wraith": "Wraith", + "Jinx": "Jinx", + "PotionMaster": "Potion Master", + "Necromancer": "Necromancer", + "Warden": "Warden", + "Minion": "Minion", + "LastImpostor": "Last Impostor", + "Overclocked": "Overclocked", + "Lovers": "Lovers", + "Madmate": "Madmate", + "Ntr": "Neptune", + "Watcher": "Watcher", + "Flash": "Flash", + "Torch": "Torch", + "Seer": "Seer", + "Tiebreaker": "Tiebreaker", + "Oblivious": "Oblivious", + "Bewilder": "Bewilder", + "Sunglasses": "Sunglasses", + "Workhorse": "Workhorse", + "Fool": "Fool", + "Avanger": "Avenger", + "Youtuber": "YouTuber", + "Egoist": "Egoist", + "TicketsStealer": "Stealer", + "Schizophrenic": "Schizophrenic", + "Mimic": "Mimic", + "Guesser": "Guesser", + "Necroview": "Necroview", + "Reach": "Reach", + "Charmed": "Charmed", + "Cleansed": "Cleansed", + "Bait": "Bait", + "Trapper": "Beartrap", + "Infected": "Infected", + "Onbound": "Onbound", + "Rebound": "Rebound", + "Mundane": "Mundane", + "Knighted": "Knighted", + "Unreportable": "Disregarded", + "Contagious": "Contagious", + "Lucky": "Lucky", + "Unlucky": "Unlucky", + "VoidBallot": "Void Ballot", + "Aware": "Aware", + "Fragile": "Fragile", + "DoubleShot": "Double Shot", + "Rascal": "Rascal", + "Soulless": "Soulless", + "Gravestone": "Gravestone", + "Lazy": "Lazy", + "Autopsy": "Autopsy", + "Loyal": "Loyal", + "EvilSpirit": "Evil Spirit", + "Recruit": "Recruit", + "Admired": "Admired", + "Glow": "Glow", + "Diseased": "Diseased", + "Antidote": "Antidote", + "Stubborn": "Stubborn", + "Swift": "Swift", + "Ghoul": "Ghoul", + "Bloodlust": "Bloodlust", + "Mare": "Mare", + "Burst": "Burst", + "Sleuth": "Sleuth", + "Clumsy": "Clumsy", + "Nimble": "Nimble", + "Circumvent": "Circumvent", + "Cyber": "Cyber", + "Hurried": "Hurried", + "Oiiai": "OIIAI", + "Influenced": "Influenced", + "Silent": "Silent", + "Susceptible": "Susceptible", + "Tricky": "Tricky", + "Rainbow": "Rainbow", + "Tired": "Tired", + "Statue": "Statue", + "BracketAddons": "Add Brackets To Add-ons", + "EngineerTOHEInfo": "Use the vents to catch the Impostors", + "ScientistTOHEInfo": "Access portable vitals from anywhere", + "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", + "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", + "ImpostorTOHEInfo": "Kill and sabotage", + "CrewmateTOHEInfo": "Search for the Impostors", + "BountyHunterInfo": "Eliminate your target", + "FireworkerInfo": "Go out with a BANG", + "MercenaryInfo": "Keep killing, else you suicide", + "ShapeMasterInfo": "Swiftly kill with no shift cooldown", + "VampireInfo": "Your kills are delayed", + "VampiressInfo": "Your kills are delayed and direct", + "WarlockInfo": "Curse crewmates then shift to make them kill", + "NinjaInfo": "Mark a target, then shift to kill", + "ZombieInfo": "You are very slow", + "AnonymousInfo": "Force a player to report a body", + "MinerInfo": "Warp to your last used vent by shifting", + "KillingMachineInfo": "You can ONLY kill, but low cooldown", + "EscapistInfo": "Shift to mark places and warp back to them", + "WitchInfo": "Spell crewmates to kill them in meetings", + "NemesisInfo": "Kill when you're the last Impostor", + "BeforeNemesisInfo": "You can't kill yet", + "AfterNemesisInfo": "Now start killing", + "BloodmoonInfo": "Seek havoc upon the crewmates", + "PuppeteerInfo": "Make players kill for you", + "MastermindInfo": "Make others kill for you", + "TimeThiefInfo": "Lower meeting time by killing", + "SniperInfo": "Snipe players from a distance by shifting", + "UndertakerInfo": "Teleport dead body to a marked location", + "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", + "EvilTrackerInfo": "Track players by shifting", + "AntiAdminerInfo": "Know when players are near devices", + "ArroganceInfo": "With each kill you make, your cooldown decreases", + "BomberInfo": "Shapeshift to explode", + "TrapsterInfo": "Trap your kills", + "ScavengerInfo": "Your kills are unreportable", + "EvilGuesserInfo": "Guess crew roles in meetings to kill", + "GangsterInfo": "Convert players to your side", + "CleanerInfo": "Report bodies to make them unreportable", + "LightningInfo": "Convert players to Quantum Ghosts", + "GreedyInfo": "Your kill cooldown shifts", + "CursedWolfInfo": "You survive a few kill attempts", + "SoulCatcherInfo": "You swap places with your shift target", + "QuickShooterInfo": "Store ammo to offset kill cooldown", + "CamouflagerInfo": "Camouflage everyone for easy kills", + "EraserInfo": "Erase the role of your vote target", + "ButcherInfo": "Enjoy my beautiful work", + "HangmanInfo": "I will decide when your life will end", + "SwooperInfo": "Turn invisible temporarily", + "CrewpostorInfo": "Kill by completing tasks", + "WildlingInfo": "Kill with strength and disguise", + "TricksterInfo": "Kill and trick the crew", + "VindicatorInfo": "Use your extra votes to kill everyone", + "ParasiteInfo": "Help the Impostors kill the crew", + "DisperserInfo": "Teleport everyone to random vents", + "InhibitorInfo": "You cannot kill during sabotages", + "SaboteurInfo": "You can only kill during sabotages", + "CouncillorInfo": "Kill off crewmates during meetings", + "DazzlerInfo": "Reduce the vision of the crew", + "DeathpactInfo": "Assign players to a death pact", + "DevourerInfo": "Consume the skin of the crew", + "ConsigliereInfo": "Discover the roles of other players", + "MorphlingInfo": "You can only kill while shapeshifted", + "TwisterInfo": "Swap all player positions", + "LurkerInfo": "Reduce your kill cooldown by venting", + "ConvictInfo": "Your target died, now help the Impostors", + "VisionaryInfo": "You see the alignments of the living", + "RefugeeInfo": "Help the Impostors kill off the crew", + "UnderdogInfo": "Start killing on a low player count", + "LudopathInfo": "Your kill cooldown is random", + "GodfatherInfo": "Convert players to Refugees by voting", + "ChronomancerInfo": "Kill in bursts", + "PitfallInfo": "Setup traps around the map", + "EvilMiniInfo": "No one can hurt you until you grow up", + "BlackmailerInfo": "Silence other players", + "InstigatorInfo": "Sow discord among the crewmates", + "LazyGuyInfo": "You're too lazy", + "SuperStarInfo": "Everyone knows you", + "CleanserInfo": "Erase All Addons of your vote target", + "KeeperInfo": "Reject the Eject, Keeper Protect!", + "MayorInfo": "Your vote counts multiple times", + "PsychicInfo": "One of the red names are evil", + "MechanicInfo": "Vent around and fix sabotages", + "SheriffInfo": "Shoot the Impostors", + "VigilanteInfo": "Not the hero we deserved but the hero we needed", + "JailerInfo": "Jail suspicious players", + "CopyCatInfo": "Use kill button to copy target's role", + "SnitchInfo": "Finish your tasks to find the Impostors", + "MarshallInfo": "Finish your tasks to prove your innocence", + "SpeedBoosterInfo": "Boost your speed", + "DoctorInfo": "Know how each player died", + "DictatorInfo": "Exile a player based on your own judgement", + "DetectiveInfo": "Gain extra info from your body reports", + "UndercoverInfo": "Impostors see you as their partner", + "KnightInfo": "You can kill 1 player", + "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", + "GuessMasterInfo": "Whispers heard, every guessed word.", + "TransporterInfo": "Do tasks to swap 2 players' locations", + "TimeManagerInfo": "Increase meeting time by doing tasks", + "VeteranInfo": "Alert to kill anyone who interacts with you", + "BastionInfo": "Bomb vents", + "BodyguardInfo": "Prevent nearby kills", + "DeceiverInfo": "Try to fool the players", + "GrenadierInfo": "Reduce Impostors' vision by venting", + "MedicInfo": "Cast a shield onto a player", + "FortuneTellerInfo": "Get clues to people's roles", + "JudgeInfo": "Silence in the courtroom!", + "MorticianInfo": "Locate dead bodies", + "MediumInfo": "Talk with ghosts", + "ObserverInfo": "You can see all shield-animations", + "PacifistInfo": "Vent to reset kill cooldowns", + "MonarchInfo": "Give your crew extra voting power!", + "StealthInfo": "Killing Blinds Everyone in the Room", + "PenguinInfo": "Drag your victims", + "OverseerInfo": "Reveal roles of other players", + "CoronerInfo": "Find corpses and their killers", + "TrackerInfo": "Keep track of other players", + "PresidentInfo": "You are in charge of the meeting", + "MerchantInfo": "Sell add-ons and bribe killers", + "RetributionistInfo": "Help the crew after you die", + "HawkInfo": "Seek murdering the bad guys!", + "DeputyInfo": "Handcuff killers to increase their cooldowns", + "InvestigatorInfo": "Find potential evils", + "GuardianInfo": "Complete your tasks to become immortal", + "AddictInfo": "Vent to become invulnerable, or you'll die", + "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", + "AlchemistInfo": "Brew potions by completing tasks", + "TracefinderInfo": "Sense the location of dead bodies", + "OracleInfo": "Vote a player to see their alignment", + "SpiritualistInfo": "Be guided by the ghostly life", + "ChameleonInfo": "Vent to disguise into your surroundings", + "InspectorInfo": "Validate the alignments of two players", + "CaptainInfo": "Sail with the Captain, lest addons be abandoned.", + "AdmirerInfo": "Choose a player to side with you", + "TimeMasterInfo": "Rewind time!", + "CrusaderInfo": "Kill a player's attacker", + "ReverieInfo": "With each kill, your cooldown decreases", + "LookoutInfo": "See through disguises", + "TelecommunicationInfo": "Track device usage", + "LighterInfo": "Catch killers with your enhanced vision", + "TaskManagerInfo": "See the total tasks completed in real time", + "WitnessInfo": "Find out if someone killed recently", + "SwapperInfo": "Swap the votes of two players", + "ChiefOfPoliceInfo": "Recruit as a Sheriff by killing players with knives", + "NiceMiniInfo": "No one can hurt you until you grow up.", + "ArsonistInfo": "Douse everyone and ignite", + "PyromaniacInfo": "Douse and kill everyone", + "HuntsmanInfo": "Kill your targets for a low cooldown", + "SpyInfo": "You know who interacts with you", + "RandomizerInfo": "You're going to be someone's burden when you die?", + "EnigmaInfo": "Get Clues about Killers", + "JesterInfo": "Get voted out", + "OpportunistInfo": "Stay alive until the end", + "TerroristInfo": "Finish your tasks, THEN die", + "ExecutionerInfo": "Get your target voted out", + "LawyerInfo": "Help your target win!", + "VectorInfo": "Jump in! Jump out!", + "JackalInfo": "Murder everyone", + "GodInfo": "Everything is under your control", + "InnocentInfo": "Get someone ejected by making them kill you", + "PelicanInfo": "Eat all players", + "RevolutionistInfo": "Recruit players to win with you", + "HaterInfo": "Kill Lovers and Neptunes", + "DemonInfo": "Consume blood volumes", + "StalkerInfo": "Descend into the darkness, release fear!", + "WorkaholicInfo": "Finish all tasks to solo win!", + "SolsticerInfo": "Speed run all your tasks!", + "CollectorInfo": "Collect votes from players", + "ProvocateurInfo": "Victory with help target", + "BloodKnightInfo": "Killing gives you a temporary shield", + "PlagueBearerInfo": "Plague everyone to turn into Pestilence", + "PestilenceInfo": "Obliterate everyone!", + "SoulCollectorInfo": "Predict deaths to collect souls", + "DeathInfo": "Enact Armageddon", + "BakerInfo": "Feed Players Bread in order to become Famine", + "FamineInfo": "Starve Everyone", + "BerserkerInfo": "Kill to increase your level", + "WarInfo": "Destroy everything", + "GlitchInfo": "Hack and kill everyone", + "SidekickInfo": "Help the Jackal kill everyone", + "FollowerInfo": "Follow a player and help them", + "CultistInfo": "Charm everyone", + "SerialKillerInfo": "Kill off everyone to win!", + "JuggernautInfo": "With each kill, your cooldown decreases", + "InfectiousInfo": "Infect everyone", + "VirusInfo": "Kill and infect everyone", + "PursuerInfo": "Protect yourself and live to the end!", + "PlagueDoctorInfo": "Spread the infection!", + "PhantomInfo": "Get killed and finish your tasks to win!", + "PirateInfo": "Successfully plunder players to win", + "AgitaterInfo": "Pass a Bomb onto others", + "MaverickInfo": "Kill and survive to the end", + "CursedSoulInfo": "Snatch souls and steal the win", + "PickpocketInfo": "Steal votes from your kills", + "TraitorInfo": "Eliminate the Impostors, then win", + "VultureInfo": "Eat bodies by reporting to win", + "TaskinatorInfo": "Silent tasks, deadly blasts", + "BenefactorInfo": "Task complete, shield elite!", + "MedusaInfo": "Stone bodies by reporting them", + "SpiritcallerInfo": "Turn Players into Evil Spirits", + "AmnesiacInfo": "Remember the role of a dead body", + "ImitatorInfo": "Imitate a player's role", + "BanditInfo": "Rob a player's add-on", + "DoppelgangerInfo": "Steal your target's identity", + "MasochistInfo": "Get attacked a few times to win!", + "KamikazeInfo": "Kill players with a suicidal mission", + "DoomsayerInfo": "Successfully guess players to win", + "ShroudInfo": "Shroud players to make them kill", + "WerewolfInfo": "Kill crewmates in groups", + "ShamanInfo": "Deflect all the attacks on Voodoo doll", + "SeekerInfo": "Play Hide and Seek with your target", + "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", + "OccultistInfo": "Kill and curse your enemies", + "SchrodingersCatInfo": "The cat is both alive and dead until observed.", + "RomanticInfo": "Protect your partner to win together", + "VengefulRomanticInfo": "Revenge your partner to win together", + "RuthlessRomanticInfo": "Kill everyone to win with your partner", + "PoisonerInfo": "Kill everyone with delayed kills", + "HexMasterInfo": "Hex players to kill them in meetings", + "WraithInfo": "Vent to temporarily go invisible", + "JinxInfo": "Reflect attacks onto your attackers", + "PotionMasterInfo": "Use your potions to your advantage", + "NecromancerInfo": "Kill your killer to defy death", + "WardenInfo": "(Ghost) Alert about danger", + "MinionInfo": "(Ghost) Blind enemies", + "LoversInfo": "Stay alive and win together", + "MadmateInfo": "Help the Impostors", + "NtrInfo": "Everyone sees you as their Lover", + "WatcherInfo": "You see all the colors of votes", + "LastImpostorInfo": "Lower kill cooldown", + "OverclockedInfo": "Lower cooldown", + "FlashInfo": "You're faster", + "TorchInfo": "You have enhanced vision!", + "SeerInfo": "You are alerted when somebody is killed", + "TiebreakerInfo": "Break tied votes", + "ObliviousInfo": "You can't report bodies", + "BewilderInfo": "A twist of vision, a web of confusion", + "WorkhorseInfo": "Be the first to complete all tasks and get more", + "FoolInfo": "You can't fix sabotages", + "AvangerInfo": "You take someone with you upon death", + "YoutuberInfo": "Get killed first to win", + "EgoistInfo": "Win on your own", + "TicketsStealerInfo": "Gain votes with kills", + "SchizophrenicInfo": "You're dead and alive simultaneously", + "MimicInfo": "Reveal killed players' roles to impostors upon death", + "GuesserInfo": "Guess roles of players in meetings to kill", + "NecroviewInfo": "See the team of the dead", + "ReachInfo": "You have a longer kill range", + "BaitInfo": "Your killer self-reports your body", + "TrapperInfo": "Freeze your killer for a few seconds", + "OnboundInfo": "You can't be guessed", + "ReboundInfo": "Guess me right, and face your plight!", + "MundaneInfo": "Tasks all done, guessing's begun.", + "UnreportableInfo": "Your body can't be reported", + "LuckyInfo": "Dodge attackers", + "DoubleShotInfo": "You have an extra life when guessing", + "RascalInfo": "You appear evil in some cases", + "SoullessInfo": "You have no soul", + "GravestoneInfo": "Your role is revealed when you die", + "LazyInfo": "You're too lazy", + "AutopsyInfo": "You see how others died", + "LoyalInfo": "You cannot be recruited", + "EvilSpiritInfo": "You are an evil Spirit", + "RecruitInfo": "Help the Jackal", + "AdmiredInfo": "The Admirer chose you as their love", + "GlowInfo": "You glow in the dark", + "DiseasedInfo": "Increase the cooldown of player who interacts with you", + "AntidoteInfo": "Decrease the cooldown of player who interacts with you", + "StubbornInfo": "Protect your role and addons", + "SwiftInfo": "Your kills don't cause a lunge", + "UnluckyInfo": "Doing things has a chance to kill you", + "VoidBallotInfo": "Your vote count is 0", + "AwareInfo": "Know who revealed your role", + "FragileInfo": "Die instantly if someone uses kill button on you", + "GhoulInfo": "Kill your killer after dying", + "BloodlustInfo": "Unleash your bloodlust and kill", + "SunglassesInfo": "You have reduced vision!", + "MareInfo": "Kill in the darkness", + "BurstInfo": "Make your killer burst!", + "SleuthInfo": "Gain info from dead bodies", + "ClumsyInfo": "You have a chance to miss your kill", + "NimbleInfo": "You can vent!", + "CircumventInfo": "You can no longer vent", + "OiiaiInfo": "OIIAIOIIIAI", + "CyberInfo": "You're popular!", + "HurriedInfo": "God I got too much stuffs!", + "InfluencedInfo": "You lack decisiveness!", + "SilentInfo": "Vote like a Ghost!", + "SusceptibleInfo": "Deathreason lotto!", + "TrickyInfo": "Tricky slays, in mysterious ways.", + "TiredInfo": "Labor makes you rest Zzz..", + "StatueInfo": "You're still as a rock nearby people", + "GMInfo": "Spectate the chaos!", + "NotAssignedInfo": "No assigned role", + "SunnyboyInfo": "Shine, shine my sunshine!", + "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in dark embrace.", + "NukerInfo": "Shapeshift to nuke everyone", + "RainbowInfo": "Colorful melodies! You don't even know your own color.", + "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", + "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time which shows you who is alive and who is dead.", + "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", + "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", + "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", + "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", + "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow, if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased.The Target swaps after a certain amount of time.", + "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworker, up to the max amount set by host.\nWhen you are the last Impostor and all Fireworker have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworker, it's considered an Impostor victory.", + "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", + "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", + "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. If a meeting is called first, your target still dies. If you bite a Bait, you kill normally and report the body.", + "VampiressInfoLong": "(Impostors):\nAs the Vampiress, you can Bite players like a Vampire (single click) or kill normally (double click).", + "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", + "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown, but moves very slowly and has very little vision. Zombie will not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", + "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", + "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", + "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", + "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown, but cannot vent, have Crewmate vision, cannot sabotage, cannot report, and cannot call emergency meetings.\n\nNote: You will bypass any and all shield, killing bait and beartrap won't take any effect", + "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport, be careful).", + "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", + "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID is typed. Use /id to show the IDs of all players, or look next to their names.", + "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon attack the enemies to make them drip blood, this means they will die in a time set by host, and will be aware of it.", + "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", + "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button. But if the target has a kill button of any time, they'll be told after a delay that they were manipulated and they must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", + "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", + "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill normally until you use up all of your ammo.", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", + "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out and all the rifts will be destroyed.\n\nNote: Up to two rifts can be placed at a time, if you try to place a third, it removes the first one.", + "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other people, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", + "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies, and if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog and/or other devices. Note: Anti Adminer does not know for sure if the player is using the device while near it, they only know that someone is near the device.", + "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", + "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill-flash when the Bomber explodes.", + "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", + "TrapsterInfoLong": "(Impostors):\nAs the Trapster, your main method of killing is by body reports.\nWhen someone tries to report a body you killed, they'll die.", + "GangsterInfoLong": "(Impostors):\nThe Gangster can attempt to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", + "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned up body cannot be reported (including bait's).", + "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they come into contact with to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", + "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always an odd kill.", + "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The max of times you can counterattack is set by the host)", + "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", + "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets he can use one to bypass kill cooldown, he will kill even if it's still on cooldown, and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (Number is set by the host).", + "CamouflagerInfoLong": "(Impostors):\nWhen Camouflager uses Shapeshift, all players start to look exactly the same. This state ends when Camouflager reverts its shape-shifting. Note: the skills of communication sabotage camouflage and skills of Camouflager can be superimposed.\nSkill will be invalid if a meeting is held during the skill activation of the Camouflager", + "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players whose skills are erased will always be considered a vanilla role, including the game result page.\nA player can only be erased once(include Oiiai)", + "ButcherInfoLong": "(Impostors):\nButcher kills (including passive kills) have multiple dead bodies on targets, making it impossible to accurately identify other dead body when reporting. Note: Due to the principle of implementation, the killed target has to repeatedly display the animation of being killed. This animation cannot be skipped and cannot participate in the meeting normally during this period. In addition, if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", + "HangmanInfoLong": "(Impostors):\nThe killing method of the Hangman during the shapeshifting is strangling. Strangling ignores any status of the target, such as the shield of the Medic, the protection of the Bodyguard, the skills of the Super Star, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), in addition, Seer will not be prompted.", + "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", + "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you complete a task.", + "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but lack the ability to vent.\nWhen you kill, you temporarily become immune to attacks.", + "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", + "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", + "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", + "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain target by pressing the kill button, and drag around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period of time.\nPress the kill button twice for a direct kill.", + "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", + "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not be teleported with shapeshift and players who are in the vent cannot be teleported.", + "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (eg Lights or Reactor), you cannot kill.", + "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (eg Comms or O2), then you can kill.", + "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", + "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, the targets of your shapeshifting are marked for a deathpact.\nIf enough players are marked for a death pact, the marked players must meet within a defined period of time; if they fail to do so, they die.\nIf a marked player dies before the death pact is completed, the pact is withdrawn.", + "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to permanently change the appearance of the target of the shapeshift. Additionally, for each player's appearance changed, your kill cooldown is reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", + "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshift.", + "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shape-shifting to randomly swap the position of all players. The swap happens twice, once when you start your shape shift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone and players who are in vents cannot be teleported.", + "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown is reset to its original value.", + "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following info will be displayed on the player.:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in range of the infected player becomes infected themselves.\nInfection progress is cumulative, and does not reset with distance or after meetings.", + "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor, or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", + "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", + "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", + "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", + "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round if someone kills the target, the killer will turn into a Refugee.", + "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you can charge up your kill button. Once activated the Chronomancer can use their kill button infinitely until they run out of charge.", + "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized for a short period of time and their vision will be affected.", + "EvilMiniInfoLong": "(Impostors):\nAs an Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which is drastically shortened as you grow up.", + "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target you will blackmail that player, and the blackmailed player cannot speak.\n\nSpeaking by the blackmailed player will trigger the confusion command, please do not speak when the blackmailed player sees his icon", + "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate is voted out in a meeting, as long as you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The number of additional players dying is determined by the host.", + "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task In addition, Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for the Anonymous, marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", + "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only be killed when the Murderer is alone with the Super Star (regular kills only). In addition, the Super Star cannot be guessed by Guessers.", + "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", + "CleanserInfoLong": "(Crewmates):\nCleanser can vote for any target at the meeting to erase the target's Add-ons, and the erasure will take effect after the meeting ends. Depending on the settings cleansed player may never get add on in future", + "KeeperInfoLong": "(Crewmates):\nAs keeper you can vote someone to protect them from being ejected. You can only do this a configurable amount of times.", + "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. As a setting, these votes can be hidden, you can vent to call a meeting at any time, and you are revealed as Mayor upon tasks completion.", + "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting, at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", + "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, Communications by using only one side. Lights can be fixed by flicking only one switch. Opening a door will open all doors in the map.", + "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", + "VigilanteInfoLong": "(Crewmates):\nThe Vigilante is tasked with eliminating potential threats to the crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster can not convert Vigilante into madmate.", + "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or be voted (vote count will be 0). The Jailer may choose to execute the prisoner by voting them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote : Jailed players cannot be guessed or judged and jailed players can only guess Jailer.", + "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see Impostors names being displayed in red on meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", + "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", + "SpeedBoosterInfoLong": "(Crewmates):\nSpeed Booster increase their movement speed every time they complete a task. Note: due to technical limitations, the Speed Booster appears to be at a normal speed to others, so they look like glitch.", + "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, Doctor can access vitals wherever you are while he still have battery.", + "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes someone, the meeting will end on the spot and the player they voted will be ejected. The moment the Dictator vote someone out, Dictator will also die.", + "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", + "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", + "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", + "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role guesser tried to guess, and you will also be notified in case of a misguess.", + "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill any person but they can only do it once the whole game.", + "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", + "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", + "VeteranInfoLong": "(Crewmates):\nVeteran can enter the alert state by venting. If a player tries to kill the veteran in the alert state, the veteran will kill the murderer instead. Veteran will see a shield-animation on their body and a text displayed above their head as a reminder when they enter and exit the alert state.", + "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though, crewmates can also be killed with the bombs.", + "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil that has a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to copycat after every meeting", + "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate and the murderer is an Impostor, the Bodyguard will not activate the skill.", + "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the kill ability has the counterfeit, he will suicide when he tries to kill someone next time.", + "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", + "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game, when the Medic dies, the target's shield will be removed. The Medic can also see if someone is trying to break the target's shield.\nDepending on the host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", + "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote:- If the setting to give random active players as hint is on, you will not be able to check same player multiple times", + "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the host), and if it is wrong, the judge commits suicide.\nThe judgment command is: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", + "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", + "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after their dead body is reported. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", + "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. This typically indicates the use of some role ability, so look out for this.", + "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has extra votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exhiled.", + "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", + "OverseerInfoLong": "(Crewmates):\nAs a Overseer you have very limited vision but you can use your kill button to reveal the role of a nearby player. Use the kill button to start the reveal, a 「○」 will be displayed next to the reveal target. Stay near the target for a defined time to reveal his role, if you move too far away from the target the reveal will be aborted.", + "CoronerInfoLong": "(Crewmates):\nAs a Coroner you can't report corpses, instead after trying to report the corpse you will see an arrow leading you to the killer. If a meeting is called, the arrows disappear. Depending on the setting, the body you found cannot be reported.", + "TrackerInfoLong": "(Crewmates):\nAs a Tracker, you can vote for a player in the meeting, which will mark their position for you in the game with arrows. In addition, at the beginning of a meeting you will be shown in which room the player was last, if the option is activated.", + "PresidentInfoLong": "(Crewmates):\nThe President has 2 abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", + "MerchantInfoLong": "(Crewmates):\\As a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can avert the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The bribe money used is lost and is not available for additional bribes.", + "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", + "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk you can kill a limited amount of players decided by host, tough there's a chance you miss, slicing someone multiple times increases the chances.", + "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", + "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in either red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when a meeting is called.", + "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon tasks completion. You can't even be guessed in meetings.", + "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is at 0 seconds, you still have a short time to vent.\nIf you don't make it you die, if you make it the suicide timer is reset.\nAlso, after you are ventilated, no one can interact with you for a defined period of time.\nAfter this period is over, you are immobilized for another defined period of time and cannot report any bodies.", + "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you come out of the vent, you will spawn near a random vent in the map (Except the one you just used).", + "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", + "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by host.", + "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", + "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability, if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", + "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible.", + "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message If they are in the same team, or a denial message if they are not in the same team.\n\nAll neutrals and converted playes are counted in the same team. Trickster is counted as crew and Rascal is counted as Impostor.\nChecking command : /cmp [player id 1] [player id 2]", + "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non crew role. Crewmates can see ☆ besides captain's name.\n\nIf anyone betrays the captain's trust by voting captain out, they will lose an addon.", + "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", + "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will be rewinded back to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", + "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", + "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the host's setting you may misfire on reaching the max kill cooldown and your target dies with you. \n\nYou win with other crewmates.", + "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", + "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, doorlogs, or admin.", + "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", + "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real time.", + "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings)", + "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", + "ChiefOfPoliceInfoLong": "(Crewmates):\nPlayers with swords can be recruited to join the sheriff's team to serve the crew, but players without swords cannot be recruited.\n note: only one recruitment opportunity", + "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, you can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses.", + "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability that is used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you have no ability uses left, you won't see orange names at all!\nNote: If the kill button interaction is blocked the player's cooldown will reset to 10s'", + "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player", + "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse by clicking the kill button on the player and following them for a few seconds. When the dousing starts and it's successful, a shield animation will be displayed as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he failed to kill everyone, he loses.", + "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting, depending on the setting, you may have to report the body to receive a clue. The more tasks you complete the more precise the clues get.", + "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", + "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double click to kill normally. When you die all marked also die, with death reason Targeted.", + "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you have a certain amount of targets that reset every meeting. If you kill one of your targets, your kill cooldown decreases by the set amount permanately. If you kill someone else other than any of your targets, your kill cooldown permanately increases by the set amount. You see your targets with a colored name.", + "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini is two roles. Either a Nice Mini or an Evil Mini is chosen.\n\nUse '/r nicemini' and '/r evilmini' respectively for more details.", + "JesterInfoLong": "(Neutrals):\nIf the Jester get voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", + "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", + "ExecutionerInfoLong": "(Neutrals):\nExecutioner has an execution target, which will be indicated by a diamond 「♦」 next to their name. If the execution target is killed, the Executioner will be changed to Crewmate, Jester or Opportunist according to the settings. If the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", + "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", + "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", + "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", + "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill normally (i.e. don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, then they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", + "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you snatch the win, i.e. everyone else loses and you win.", + "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target is voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", + "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those who are swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", + "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by host, to kill players (though they are still recruited). When the required number of players are recruited, (displayed next to your name) you must vent within the specified time in order to win the game immediately with all of your recruits. If you do not vent in time, you lose and die.", + "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, you can only kill Lovers, and other recruiting roles and add-ons, depending on the settings. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", + "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", + "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause electricity sabotage (if electricity is already sabotaged, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker win alone.", + "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on host's settings, you can only win if alive and/or you are revealed to everyone at the beginning (these settings are almost never both on).", + "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting is finished, your tasks get reset, and you need to start all over again.\nVotes on the Solsticer will be directly cancelled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in game.", + "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required number of votes, the game ends and you win alone, even if you voted a Jester or Executioner's target out.", + "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect, and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", + "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", + "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", + "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill that makes them immortal for a few seconds.", + "ApocalypseInfoLong": "(Apocalypse):\nEvery role of the Apocalypse Team has their own objective to carry out in order to transform.\nTransformed Apocalypse members are immortal, but everyone will be notified that they have transformed.\n\nRoles: Plaguebearer, Soul Collector, Baker, Berserker\nTransformed: Pestilence, Death, Famine, War\n\n(if you got this as a role, you have bugged the game, good job)Your presence is announced to everyone the meeting after you transform.", + "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you vote players to predict their death. If the prediction is correct and the target dies in the next round you collect their soul. \n\nOnce you collect the configurable amount of souls, you become Death.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Depending on the host's settings, a meeting may or may not be called immediately. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", + "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", + "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone the meeting after you transform.", + "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", + "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", + "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive. Depending on settings, you will not be impacted by any harmful interactions and may have a teammate.", + "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", + "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you then can simply outnumber the crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", + "VirusInfoLong": "(Neutrals):\nThe task of the virus is to kill or infect all other players. When the virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected and joins the virus team or dies at the end of the meeting if the virus won't get voted out, dependent on the settings. If there are more players on the Virus team than on the Crewmate team, the Virus team wins.", + "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, just survive to the end of the game.", + "PhantomInfoLong": "(Neutrals):\nAs the Phantom, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", + "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both Pirate and the target chooses same number, Pirate wins.\nAdditionally, if Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command:- /duel X (where X can be 0, 1 or 2)\n\nYou win after winning a certain number of duels set by the host.\n\nNote: If target did not participate in duel, the kill will not count towards pirate victory", + "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", + "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", + "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you snatch the victory if you survive to the end of the game.\n\nYou can snatch the win from a Jester or Executioner.\n\nAdditionally, you can snatch the souls of other players.\nSoulless players win with you and count as dead.", + "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\nThese votes are hidden.\n\nKill everyone to win.", + "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors but they don't know you.\nThe twist? They can kill you but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", + "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", + "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you complete a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", + "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you complete a task, the task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks", + "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", + "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players for a short time and/or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", + "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", + "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee or some Neutral", + "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button once to steal a player's addon and twice to kill. Depending on the settings, you may instantly steal the addon or after the meeting starts. After the max number of steals are reached you will kill normally. Additionally, if there are no stealable addons present on the target or the target is stubborn you will kill the target.\n\nKill everyone to win.\n\nNote:- Cleansed, Last Impostor and Lovers can not be stolen.\nNote:- If Bandit can vent is on, Nimble will become unstealable", + "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote:- You can not steal the identity of the target when Camouflage is active.", + "MasochistInfoLong": "(Neutrals):\nAs the Masochist, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", + "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings) then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themself after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", + "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", + "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.", + "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If seeker tags wrong player a point is deducted and if seeker tags correct player a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target\n\n The seeker needs to collect certain number of points set by the host to win", + "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark upto x amount of targets each round by using kill button on them. When the meeting starts, your job is to have one of the marked targets ejected. If unsuccessful you will suicide, except if you didn't mark any targets or all the targets are dead. The selected targets resets to 0 after the meeting ends. If you succeed you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the host.", + "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use their kill button on you, the interaction is not blocked, and you will die.", + "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield which protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote : If your role changes your win condition will be changed accordingly", + "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As Ruthless Romantic, you win if you kill everyone and be the last one standing. If you win your dead partner also wins with you.", + "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non neutral killer) is killed. As a Vengeful Romantic, Your goal is to avenge your partner, which means you have to kill the killer of your partner. If you succeed to do so, then both you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", + "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", + "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", + "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to temporarily Vanish. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", + "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you are attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", + "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons defaults to killing.", + "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally, when someone tries to kill you, the kill will be blocked and you will be teleported to a random vent. You will have a limited time to kill your killer. If you succeed to do so, you live. If the time runs out before you kill your killer, you die permanately. If you try to kill someone else other than your killer, you will die.", + "LastImpostorInfoLong": "(Add-ons):\nThis effect is given to the last surviving Impostor. Reduces their kill cooldown.", + "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nOnly assigned to roles with a kill button.", + "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when only the Lovers are left. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 mark next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", + "MadmateInfoLong": "(Add-ons):\nOnly Crewmate can become Madmate. Madmate's task is to help the Impostors win the game, Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone.", + "NtrInfoLong": "(Add-ons):\nWhen there is Neptune, all players will see that they are Lovers with Neptune, and they will not die in love together and will not change the win conditions. Note: Lovers won't become Neptune, and Neptune won't become Lovers.", + "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", + "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the host)", + "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", + "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", + "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreaker choose different tie targets at the same time, the skills of the Tiebreaker will not take effect.", + "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still be reported automatically, and Oblivious can still be used as a scapegoat for the Anonymous.", + "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder is killed, the murderer's vision may become the same as the Bewilder's vision depending on the settings.", + "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, Workhorse will give the player extra tasks. The amount of additional tasks are set by the host.", + "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", + "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out and unconventional kills are not counted), the Avenger will revenge a random player.", + "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to be killed in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc. will not trigger the skills of the YouTuber.", + "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", + "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the vote number is set by the host, and the decimal is rounded down). Also, extra votes from the Stealer are hidden during meeting.", + "SchizophrenicInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Schizophrenic, you will be considered as two players in the game for the purpose of determining when the game ends due to killers having majority. Additionally, this grants you an extra vote, depending on options.", + "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called, this message will include information on roles who were killed by the Mimic.", + "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess roles of players in meetings to kill them.\nGuessing incorrectly kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. You have the longest kill range possible in the game, unlike everyone else.", + "BaitInfoLong": "(Add-ons):\nWhen the Bait is killed, the murderer who killed the Bait will be forced to self-report the Bait's body. However, this won't happen when the Bait is killed by a Scavenger, Cleaner, Swooper, Wraith, or Killing Machine. The report may have a delay according to Host's settings.", + "TrapperInfoLong": "(Add-ons):\nWhen Beartrap is killed, Beartrap immobilize killer for a configurable amount of time.", + "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", + "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", + "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", + "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", + "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", + "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess after all your tasks has been finished.", + "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", + "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse cannot be reported.", + "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", + "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on there is a probability for you to evade the kill, the specific probability is set by the host. When the evasion takes effect, the killer will see the shield-animation, but you not know anything.", + "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", + "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", + "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul snatches your soul, you get this add-on.\n\nYou are not counted as alive.", + "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", + "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", + "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", + "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", + "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to temporarily give the Spiritcaller a shield against a kill attempt.", + "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", + "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", + "GlowInfoLong": "(Add-ons):\nAs the Glow, your name is colored during a lights sabotage.", + "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be increased by configurable amount of time", + "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use kill button on you, their cooldown will be decreased by configurable amount of time", + "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add on, Eraser can’t erase your role, Cleanser can't cleanse you, Bandit can't steal from you and Monarch can't knight you.\nAdditionally, you can’t gain any new addons from the merchant", + "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.", + "UnluckyInfoLong": "(Add-ons):\nAs the Unlucky, doing tasks, killing, or venting as a chance to kill you.", + "VoidBallotInfoLong": "(Add-ons):\nHolder of this addon will have 0 vote count", + "AwareInfoLong": "(Add-ons):\nAs the Aware, you will be notified in the next meeting if a revealing role had interacted with you", + "FragileInfoLong": "(Add-ons):\nAs Fragile, you will die instantly if someone tries to use kill button on you (even if the role can not directly kill)", + "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on tasks completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nOnly assigned to crewmates, and not crewmates with no tasks or are task based.", + "BloodlustInfoLong": "(Add-ons):\nAs the Bloodlust, doing tasks allows you to kill.\nWhen you complete a task, the next player you come in contact with dies.\n\nYour bloodlust remains after a meeting.\nUpon making a kill, your bloodlust clears till the next task you complete.\nBloodlusts do not stack.\n\nOnly assigned to crewmates with tasks.", + "SunglassesInfoLong": "(Add-ons):\nAs the Sunglasses, your vision is reduced.", + "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", + "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", + "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", + "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset and the target remains untouched.\n\nOnly assigned to killers.", + "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", + "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", + "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced,then the vote result won't shift\nCollector cannot become influenced.", + "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", + "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", + "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", + "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they complete a task, they will temporarily get lower vision & lower speed.", + "StatueInfoLong": "(Add-ons):\nWhenever many people are near Statue, the Statue is completely frozen or slowed down dependand on settings.", + "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot be killed while in a group.\nAdditionally, your death will be known.", + "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you failed with your tasks, you lose.\nHurried hurries to his goal so it won't get madmate,charmed or so.", + "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", + "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy", + "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence has no effect on the game, and all players know who the Game Master is. The Game Master role will be assigned to the host, who will automatically become a ghost at the start of the game.", + "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. When you are alive, the game will not end due to killers gaining majority.\nAdditionally, you have access to portable vitals.", + "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown is permanently halved.", + "NukerInfoLong": "(Impostors):\nAs the Nuker, you're a stronger Bomber.\n\nShapeshift to nuke everyone.", + "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", + "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", + "ShowTextOverlay": "Text Overlay", + "Overlay.GuesserMode": "Guesser Mode", + "Overlay.NoGameEnd": "No Game End", + "Overlay.DebugMode": "Debug Mode", + "Overlay.LowLoadMode": "Low Load Mode", + "Overlay.AllowConsole": "Console", + "DisableShieldAnimations": "Disable Unnecessary Shield Animations", + "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", + "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", + "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", + "AbilityUseLimit": "Initial Ability Use Limit", + "ShowArrows": "Has Arrows pointing toward bodies", + "ArrowDelayMin": "Minimum Arrow show-up delay", + "ArrowDelayMax": "Maximum Arrow show-up delay", + "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", + "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", + + "AbilityCD": "Ability Cooldown", + "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", + "ShowSpecificRole": "Know specific roles on Task Completion", + "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", + "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", + "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", + "DisableMeeting": "Disable Meetings", + "DisableCloseDoor": "Disable Doors Sabotage", + "DisableSabotage": "Disable Sabotages", + "NoGameEnd": "No Game End", + "AllowConsole": "BepInEx Console", + "DebugMode": "Debug Mode", + "SyncButtonMode": "Sync Buttons Mode", + "RandomMapsMode": "Random Maps Mode", + "SyncedButtonCount": "Max Number of Emergency Meetings Allowed", + "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", + "HHFailureKCDIncrease": "Kill cooldown increase on killing others", + "HHNumOfTargets": "Number of targets", + "Targets": "Targets: ", + "HHMaxKCD": "Maximum kill cooldown", + "HHMinKCD": "Minimum kill cooldown", + "AllAliveMeeting": "Meeting When No One is Dead", + "AllAliveMeetingTime": "Meeting Time When No One is Dead", + "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", + "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", + "AdditionalEmergencyCooldownTime": "Additional Cooldown", + "LadderDeath": "Fall From Ladders", + "LadderDeathChance": "Fall To Death Chance", + "DisableSwipeCardTask": "Disable Swipe Card Task", + "DisableSubmitScanTask": "Disable Submit Scan Task", + "DisableUnlockSafeTask": "Disable Unlock Safe Task", + "DisableUploadDataTask": "Disable Upload Data Task", + "DisableStartReactorTask": "Disable Start Reactor Task", + "DisableResetBreakerTask": "Disable Reset Breakers Task", + "DisableShortTasks": "Disable Short Tasks", + "DisableCleanVent": "Disable Clean Vent Task", + "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", + "DisableChartCourse": "Disable Chart Course Task", + "DisableStabilizeSteering": "Disable Stabilize Steering Task", + "DisableCleanO2Filter": "Disable Clean O2 Filter Task", + "DisableUnlockManifolds": "Disable Unlock Manifolds Task", + "DisablePrimeShields": "Disable Prime Shields Task", + "DisableMeasureWeather": "Disable Measure Weather", + "DisableBuyBeverage": "Disable Buy Beverage", + "DisableAssembleArtifact": "Disable Assemble Artifact Task", + "DisableSortSamples": "Disable Sort Samples Task", + "DisableProcessData": "Disable Process Data Task", + "DisableRunDiagnostics": "Disable Run Diagnostics Task", + "DisableRepairDrill": "Disable Repair Drill Task", + "DisableAlignTelescope": "Disable Align Telescope Task", + "DisableRecordTemperature": "Disable Record Temperature Task", + "DisableFillCanisters": "Disable Fill Canisters Task", + "DisableMonitorTree": "Disable Monitor Tree Task", + "DisableStoreArtifacts": "Disable Store Artifacts Task", + "DisablePutAwayPistols": "Disable Put Away Pistols Task", + "DisablePutAwayRifles": "Disable Put Away Rifles Task", + "DisableMakeBurger": "Disable Make Burger Task", + "DisableCleanToilet": "Disable Clean Toilet Task", + "DisableDecontaminate": "Disable Decontaminate Task", + "DisableSortRecords": "Disable Sort Records Task", + "DisableFixShower": "Disable Fix Shower Task", + "DisablePickUpTowels": "Disable Pick Up Towels Task", + "DisablePolishRuby": "Disable Polish Ruby Task", + "DisableDressMannequin": "Disable Dress Mannequin Task", + "DisableCommonTasks": "Disable Common Tasks", + "DisableFixWiring": "Disable Fix Wiring Task", + "DisableEnterIdCode": "Disable Enter ID Code Task", + "DisableInsertKeys": "Disable Insert Keys Task", + "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", + "DisableLongTasks": "Disable Long Tasks", + "DisableAlignEngineOutput": "Disable Align Engine Output Task", + "DisableInspectSample": "Disable Inspect Sample Task", + "DisableEmptyChute": "Disable Empty Chute Task", + "DisableClearAsteroids": "Disable Clear Asteroids Task", + "DisableWaterPlants": "Disable Water Plants Task", + "DisableOpenWaterways": "Disable Open Waterways Task", + "DisableReplaceWaterJug": "Disable Replace Water Jug Task", + "DisableRebootWifi": "Disable Reboot Wifi Task", + "DisableDevelopPhotos": "Disable Develop Photos Task", + "DisableRewindTapes": "Disable Rewind Tapes Task", + "DisableStartFans": "Disable Start Fans Task", + "DisableOtherTasks": "Disable Situational Tasks", + "DisableEmptyGarbage": "Disable Empty Garbage Task", + "DisableFuelEngines": "Disable Fuel Engines Task", + "DisableDivertPower": "Disable Divert Power Task", + "DisableActivateWeatherNodes": "Disable Weather Nodes Task", + "DisableRoastMarshmallow": "Disable Roast Marshmallow", + "DisableCollectSamples": "Disable Collect Samples", + "DisableReplaceParts": "Disable Replace Parts", + "DisableCollectVegetables": "Disable Collect Vegetables", + "DisableMineOres": "Disable Mine Ores", + "DisableExtractFuel": "Disable Extract Fuel", + "DisableCatchFish": "Disable Catch Fish", + "DisablePolishGem": "Disable Polish Gem", + "DisableHelpCritter": "Disable Help Critter", + "DisableHoistSupplies": "Disable Hoist Supplies", + "DisableFixAntenna": "Disable Fix Antenna", + "DisableBuildSandcastle": "Disable Build Sandcastle", + "DisableCrankGenerator": "Disable Crank Generator", + "DisableMonitorMushroom": "Disable Monitor Mushroom", + "DisablePlayVideoGame": "Disable Play Video Game", + "DisableFindSignal": "Disable Find Signal", + "DisableThrowFisbee": "Disable Throw Frisbee", + "DisableLiftWeights": "Disable Lift Weights", + "DisableCollectShells": "Disable Collect Shells", + "SuffixMode": "Suffix", + "SuffixMode.None": "None", + "SuffixMode.Version": "Version", + "SuffixMode.Streaming": "Streaming", + "SuffixMode.Recording": "Recording", + "SuffixMode.RoomHost": "Room Host", + "SuffixMode.OriginalName": "Original Name", + "SuffixMode.DoNotKillMe": "Don't kill me", + "SuffixMode.NoAndroidPlz": "No phones", + "SuffixMode.AutoHost": "Auto-Host", + "SuffixModeText.DoNotKillMe": "Don't kill me", + "SuffixModeText.NoAndroidPlz": "No phones please", + "SuffixModeText.AutoHost": "Auto-hosting", + "FormatNameMode": "Player Name Mode", + "FormatNameModes.None": "Disable", + "FormatNameModes.Color": "Color", + "FormatNameModes.Snacks": "Random", + "DisableEmojiName": "Disable Emoji in names", + "FixFirstKillCooldown": "Override Starting Kill Cooldown", + "FixKillCooldownValue": "Starting Kill Cooldown", + "OverclockedReduction": "Kill Cooldown Reduction", + "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", + "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", + "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", + "GhostIgnoreTasks": "Ghosts Exempt From Tasks", + "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", + "MaxImpGhostRole": "Max Impostor Ghost-Roles", + "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", + "DefaultAngelCooldown": "Default Ability Cooldown", + "DisableTaskWin": "Disable Task Win", + "HideGameSettings": "Hide Game Settings", + "DIYGameSettings": "Enable only custom /n messages", + "Settings:": "Settings:", + "PlayerCanSetColor": "Players can use the /color command", + "PlayerCanSetName": "Players can use the /rn command", + "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", + "PlayerCanUseTP": "Players can use the /tpin and /tpout command", + "CanPlayMiniGames": "Players can play mini games", + "KPDCamouflageMode": "Camouflage Appearance", + "RoleOptions": "Role Options", + "DarkTheme": "Enable Dark Theme", + "AutoStart": "Auto start", + "EnableCustomButton": "Enable Custom Button Images", + "EnableCustomSoundEffect": "Enable Custom Sound Effects", + "SwitchVanilla": "Switch Vanilla", + "ModeForSmallScreen": "Small Screen Mode", + "UnlockFPS": "Unlock FPS", + "ForceOwnLanguage": "Force mod to use your language if possible", + "ForceOwnLanguageRoleName": "Force role names in your language if possible", + "VersionCheat": "Bypass version synchronization check", + "GodMode": "God Mode", + + "AutoDisplayKillLog": "Display Kill-log", + "AutoDisplayLastRoles": "Display Last Roles", + "AutoDisplayLastResult": "Auto Display Last Result", + "RevertOldKillLog": "Revert to old kill-log", + + "HideExileChat": "Hide exile (lava) chat", + "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", + + "EnableYTPlan": "Enable Youtuber Plan", + "KickLowLevelPlayer": "Kick players whose level is lower than", + "TempBanLowLevelPlayer": "Temporarily ban low-level players", + "ApplyWhiteList": "Turn on Whitelist to bypass level kick", + "AllowOnlyWhiteList": "Allow only whitelisted players to join", + "AutoKickStart": "Kick players that say start", + "AutoKickStartTimes": "Number of warnings before kick", + "AutoKickStartAsBan": "Block a player after they're kicked", + "AutoKickStopWords": "Kick players who write banned words", + "AutoKickStopWordsTimes": "Number of warnings for banned words", + "AutoKickStopWordsAsBan": "Block a player after they're kicked", + "AutoWarnStopWords": "Warning to those who write banned words", + "TempBanPlayersWhoKeepQuitting": "Temporarily ban players who leave and join repeatedly", + "QuitTimesTillTempBan": "The quit frequency needed for temp ban", + "KickOtherPlatformPlayer": "Kick Non-PC players", + "OptKickAndroidPlayer": "Kick Android players", + "OptKickIphonePlayer": "Kick iOS players", + "OptKickXboxPlayer": "Kick Xbox players", + "OptKickPlayStationPlayer": "Kick PlayStation players", + "OptKickNintendoPlayer": "Kick Nintendo Switch players", + "ShareLobby": "Allow TOHE-Chan Shares Lobby Code To Discord", + "ShareLobbyMinPlayer": "Share Lobby Code When The Number Of Players Reaches", + "DisableVanillaRoles": "Disable Vanilla Roles", + "VoteMode": "Voting Mode", + "WhenSkipVote": "If the Player Skipped", + "WhenSkipVoteIgnoreFirstMeeting": "Ignore the First Meeting", + "WhenSkipVoteIgnoreNoDeadBody": "Ignore When No Dead Body", + "WhenSkipVoteIgnoreEmergency": "Ignore at Emergency Meetings", + "WhenNonVote": "If the Player didn't vote", + "Default": "No vote", + "Suicide": "Suicide", + "SelfVote": "Self Vote", + "Skip": "Skip", + "WhenTie": "When Tied Vote", + "TieMode.Default": "No ejects", + "TieMode.All": "Eject All", + "TieMode.Random": "Eject Random", + "DisableDevices": "Disable Devices", + "DisableSkeldDevices": "Disable Skeld Devices", + "DisableMiraHQDevices": "Disable MIRA HQ Devices", + "DisablePolusDevices": "Disable Polus Devices", + "DisableAirshipDevices": "Disable Airship Devices", + "DisableFungleDevices": "Disable Fungle Devices", + "DisableSkeldAdmin": "Disable Admin", + "DisableMiraHQAdmin": "Disable Admin", + "DisablePolusAdmin": "Disable Admin", + "DisableAirshipCockpitAdmin": "Disable Cockpit Admin", + "DisableAirshipRecordsAdmin": "Disable Records Admin", + "DisableSkeldCamera": "Disable Cameras", + "DisablePolusCamera": "Disable Cameras", + "DisableAirshipCamera": "Disable Cameras", + "DisableMiraHQDoorLog": "Disable DoorLog", + "DisablePolusVital": "Disable Vitals", + "DisableAirshipVital": "Disable Vitals", + "DisableFungleVital": "Disable Vitals", + "DisableFungleBinoculars": "Disable Binoculars (Not work for vanilla)", + "IgnoreConditions": "Ignore Conditions", + "IgnoreImpostors": "Ignore Impostors", + "IgnoreNeutrals": "Ignore Neutrals", + "IgnoreCrewmates": "Ignore Crewmates", + "IgnoreAfterAnyoneDied": "Ignore After First Death", + "LightsOutSpecialSettings": "Fix Lights Special Settings", + "BlockDisturbancesToSwitches": "Block Switches When They Are Up", + "DisableAirshipViewingDeckLightsPanel": "Disable Viewing Deck Lights Panel (Airship)", + "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", + "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", + "RandomSpawnMode": "Random Spawns Mode", + "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", + "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", + "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", + "CommsCamouflage": "Camouflage during Comms Sabotage", + "DisableOnSomeMaps": "Disable comms camouflage on some maps", + "DisableOnSkeld": "Disable on The Skeld", + "DisableOnMira": "Disable on MIRA HQ", + "DisableOnPolus": "Disable on Polus", + "DisableOnDleks": "Disable on dlekS ehT", + "DisableOnAirship": "Disable on Airship", + "DisableOnFungle": "Disable on The Fungle", + "DisableReportWhenCC": "Disable body reporting while camouflaged", + "EnableDebugMode": "Enable Debug Mode", + "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", + "SendRoleDescriptionFirstMeeting": "Show Role Descriptions to Unmodded Clients at First Meeting", + "RoleAssigningAlgorithm": "Role Assigning Algorithm", + "RoleAssigningAlgorithm.Default": "Default", + "RoleAssigningAlgorithm.NetRandom": ".NET System.Random", + "RoleAssigningAlgorithm.HashRandom": "HashRandom", + "RoleAssigningAlgorithm.Xorshift": "Xorshift", + "RoleAssigningAlgorithm.MersenneTwister": "MersenneTwister", + "MapModification": "Map Modifications", + "DisableAirshipMovingPlatform": "Disable Moving Platform (Airship)", + "AirshipVariableElectrical": "Variable Electrical (Airship)", + "DisableSporeTriggerOnFungle": "Disable Spore Trigger (Fungle)", + "DisableZiplineOnFungle": "Disable Zipline (Fungle)", + "DisableZiplineFromTop": "Disable Use From Top", + "DisableZiplineFromUnder": "Disable Use From Under", + "ResetDoorsEveryTurns": "Reset Doors After Meeting (Airship/Polus/Fungle)", + "DoorsResetMode": "Reset Doors Mode", + "AllOpen": "All Open", + "AllClosed": "All Closed", + "RandomByDoor": "Closed Random", + "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", + "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", + "DecontaminationTimeOnPolus": "Decontamination Time On Polus", + "ApplyDenyNameList": "Apply DenyName List", + "KickPlayerFriendCodeNotExist": "Kick players without a friend code", + "TempBanPlayerFriendCodeNotExist": "Temp Ban players without a friend code", + "ApplyBanList": "Apply BanList", + "EndWhenPlayerBug": "End the game when a player has a critical error", + "RemovePetsAtDeadPlayers": "Remove pets at dead players", + "KillFlashDuration": "Kill-Flash Duration", + "ConfirmEjectionsMode": "Confirm Ejections Mode", + "ConfirmEjections.None": "None", + "ConfirmEjections.Team": "Team", + "ConfirmEjections.Role": "Role", + "ShowImpRemainOnEject": "Show remaining Impostors on ejects", + "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", + "ConfirmEgoistOnEject": "Confirm Egoists on ejection", + "ConfirmLoversOnEject": "Confirm Lovers on ejection", + "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", + "HideBittenRolesOnEject": "Hide roles of bitten players on ejection", + "ShowTeamNextToRoleNameOnEject": "Show what team the ejected player's role is on", + "Ban": "Ban", + "Kick": "Kick", + "NoticeMe": "Notify me", + "NoticeEveryone": "Notify everyone", + "TempBan": "Temporary Ban", + "OnlyCancel": "Only Cancel the cheat actions", + "CheatResponses": "When a cheating player is found", + "NeutralRoleWinTogether": "Neutrals win together", + "NeutralWinTogether": "All Neutrals win together", + "MenuTitle.Disable": "★ Disable ★", + "MenuTitle.MapsSettings": "★ Maps ★", + "MenuTitle.Sabotage": "★ Sabotage ★", + "MenuTitle.Meeting": "★ Meeting ★", + "MenuTitle.Ghost": "★ Ghost ★", + "MenuTitle.Other": "★ Different ★", + "MenuTitle.Ejections": "★ Ejection ★", + "MenuTitle.Settings": "★ Settings ★", + "MenuTitle.TaskSettings": "★ Task Management ★", + "MenuTitle.Guessers": "★ Guesser Mode ★", + "MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★", + "ShieldPersonDiedFirst": "Shield player who dead first in the last game", + "LegacyNemesis": "Use Legacy Version", + "ArsonistKeepsGameGoing": "Arsonist keeps the game going", + "ArsonistCanIgniteAnytime": "Can Ignite Anytime", + "ArsonistMinPlayersToIgnite": "Minimum doused needed for ignite", + "ArsonistMaxPlayersToIgnite": "Maximum doused needed for ignite", + "PuppeteerDoubleKills": "Puppet dies alongside victim", + "MastermindCD": "Manipulate Cooldown", + "MastermindTimeLimit": "Time limit to kill someone", + "MastermindDelay": "Manipulation notification delay", + "ManipulateNotify": "Kill someone in {0}s or die!", + "ManipulatedKilled": "{0} has killed someone", + "SurvivedManipulation": "You survived the Mastermind's manipulation!", + "Glitch_HackCooldown": "Hack Cooldown", + "Glitch_HackDuration": "Hack Duration", + "Glitch_MimicCooldown": "Mimic Cooldown", + "Glitch_MimicDuration": "Mimic Duration", + "Glitch_MimicButtonText": "Mimic", + "Glitch_MimicDur": "Mimic Duration: {0}s", + "Glitch_HackCD": "Hack Cooldown: {0}s", + "Glitch_KCD": "Kill Cooldown: {0}s", + "Glitch_MimicCD": "Mimic Cooldown: {0}s", + "HackedByGlitch": "You are hacked by the Glitch, you can't {0}.", + "GlitchKill": "kill", + "GlitchReport": "report", + "GlitchVent": "vent", + "ShowFPS": "Show FPS", + "FPSGame": "FPS: ", + "Cooldown": "Cooldown", + "KillCooldown": "Kill Cooldown", + "AbilityCooldown": "Ability Cooldown", + "VentCooldown": "Vent Cooldown", + "ControlCooldown": "Control Cooldown", + "PoisonCooldown": "Poison Cooldown", + "PoisonerKillDelay": "Poison Kill Delay", + "WardenNotifyLimit": "Max number of alerts", + "CanVent": "Can Vent", + "CanKill": "Can Kill", + "CanGuess": "Can Guess in Guesser Mode or as Guesser", + "BombCooldown": "Bomb Cooldown", + "ImpostorVision": "Has Impostor Vision", + "CanUseSabotage": "Can Sabotage", + "CanKillAllies": "Can Kill Impostors", + "CanKillSelf": "Can Kill Themself", + "CrewpostorKnowsAllies": "Knows Impostors", + "AlliesKnowCrewpostor": "Known to Impostors", + "CrewpostorLungeKill": "Crewpostor lunges on kill", + "CrewpostorKillAfterTask": "Number of tasks completed to make 1 kill", + + "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", + "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", + "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", + "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", + "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", + "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", + "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", + "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", + "ImpKnowWhosMadmate": "Impostors know Madmates", + "MadmateKnowWhosImp": "Madmates know Impostors", + "MadmateKnowWhosMadmate": "Madmates know each other", + "ImpCanKillMadmate": "Impostors can kill Madmates", + "MadmateCanKillImp": "Madmates can kill Impostors", + "MadmateHasImpostorVision": "Madmates Have Impostor Vision", + "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", + "EGCanGuessImp": "Can Guess Impostor Roles", + "GGCanGuessCrew": "Can Guess Crewmate Roles", + "EGCanGuessAdt": "Can Guess Add-Ons", + "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "GGCanGuessAdt": "Can Guess Add-Ons", + "GuesserCanGuessTimes": "Maximum number of guesses", + "GuesserTryHideMsg": "Try to hide guesser's command", + "GCanGuessImp": "Impostor can guess Impostor roles", + "GCanGuessCrew": "Crewmate can guess Crewmate roles", + "GCanGuessAdt": "Can guess Add-ons", + "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "BountyTargetChangeTime": "Time Until Target Swaps", + "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", + "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", + "BountyShowTargetArrow": "Show arrow pointing towards target", + "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", + "ShapeshiftDuration": "Shapeshift Duration", + "ShapeshiftCooldown": "Shapeshift Cooldown", + "VitalsDuration": "Vitals Duration", + "VitalsCooldown": "Vitals Cooldown", + "DeadImpCantSabotage": "Impostors can't sabotage after they've died", + "VampireKillDelay": "Bite Kill Delay", + + "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", + "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", + + "MechanicSkillLimit": "Initial repair use limit", + "MechanicFixesDoors": "Can open all doors in the same building", + "MechanicFixesReactors": "Can Fix Both Reactors Alone", + "MechanicFixesOxygens": "Can Fix Both O2 Alone", + "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", + "MechanicFixesElectrical": "Can Fix Lights With One Switch", + + "SheriffShowShotLimit": "Display Shot Limit next to Role Name", + "SheriffCanKill%role%": "Can Kill %role%", + "SheriffCanKillNeutrals": "Can Kill Neutrals", + "SheriffCanKillNeutralsMode": "Neutral Configuration", + "SheriffCanKillAll": "All ON", + "SheriffCanKillSeparately": "Individual Settings", + "In%team%": "(Team %team%)", + "SheriffMisfireKillsTarget": "Misfire Kills Target", + "SheriffShotLimit": "Max number of Kills", + "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", + "SheriffCanKillCharmed": "Can kill Charmed players", + "SheriffCanKillEgoist": "Can Kill Egoists", + "SheriffCanKillSidekick": "Can Kill Sidekicks", + "SheriffCanKillLovers": "Can Kill Lovers", + "SheriffCanKillMadmate": "Can Kill Madmates", + "SheriffCanKillInfected": "Can Kill Infected players", + "SheriffCanKillContagious": "Can Kill Contagious players", + "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", + "SheriffMadCanKillImp": "Can kill Impostors", + "SheriffMadCanKillNeutral": "Can kill Neutrals", + "SheriffMadCanKillCrew": "Can kill Crewmates", + + "ReverieIncreaseKillCooldown": "Increase kill cooldown", + "ReverieMaxKillCooldown": "Max kill cooldown", + "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", + "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", + "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", + + "VigilanteNotify": "You have become the very thing you swore to destroy", + + "DoctorTaskCompletedBatteryCharge": "Battery Duration", + "SnitchEnableTargetArrow": "See Arrow Towards Target", + "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", + "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", + "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", + "SnitchCanFindMadmate": "Can Find Madmates", + "SnitchRemainingTaskFound": "Remaining tasks to be known", + "SpeedBoosterUpSpeed": "Increase Speed by", + "SpeedBoosterTimes": "Max Boosts", + "MayorAdditionalVote": "Additional Votes Count", + "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", + "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", + "MayorHideVote": "Hide additional vote(s)", + "HideJesterVote": "Hide Jester's vote", + "MeetingsNeededForWin": "Meetings needed to win", + "ExecutionerCanTargetImpostor": "Can Target Impostors", + "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", + "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", + "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", + "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", + "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", + "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", + "LawyerCanTargetImpostor": "Can Target Impostors", + "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", + "LawyerCanTargetCrewmate": "Can Target Crewmates", + "LawyerCanTargetJester": "Can Target Jester", + "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", + "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", + "LawyerTargetDeadInMeeting": "Your target was killed while meeting./nYour role may change depending on the settings.", + + "MercenaryLimit": "Time Until Suicide", + "ArsonistDouseTime": "Douse Duration", + "CanTerroristSuicideWin": "Can Win By Suicide", + "FireworkerMaxCount": "Fireworker Count", + "FireworkerRadius": "Firework Explosion Radius", + "SniperCanKill": "Sniper can kill with bullets remaining", + "SniperBulletCount": "Ammo", + "SniperPrecisionShooting": "Precise Shooting", + "SniperAimAssist": "Aim Assist", + "SniperAimAssistOneshot": "One shot Assist", + + "PyroDouseCooldown": "Douse cooldown", + "PyroBurnCooldown": "Kill cooldown after killing a doused player", + + "UndertakerFreezeDuration": "Freeze Duration", + + "NameDisplayAddons": "Display Add-Ons next to the role name", + "YourAddon": "Your Addons:", + "NoLimitAddonsNumMax": "Max Add-ons Per Player", + "LoverSpawnChances": "Spawn Chance of Lovers", + "AdditionRolesSpawnRate": "Spawn Chance", + "TorchVision": "Torch Vision", + "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", + "BewilderVision": "Bewilder Vision", + "JesterVision": "Jester Vision", + "LawyerVision": "Lawyer Vision", + "FlashSpeed": "Flash Speed", + "LoverSuicide": "Lovers die together", + "NumberOfLovers": "Number of Lover Pairs (x2 members)", + "LoverKnowRoles": "Lovers know the roles of each other", + "TrapperBlockMoveTime": "Freeze time", + "BecomeTrapperBlockMoveTime": "Freeze time", + "ImpCanBeTrapper": "Impostors can become Beartrap", + "CrewCanBeTrapper": "Crewmates can become Beartrap", + "NeutralCanBeTrapper": "Neutrals can become Beartrap", + "ImpCanBeGravestone": "Impostors can become Gravestone", + "CrewCanBeGravestone": "Crewmates can become Gravestone", + "NeutralCanBeGravestone": "Neutrals can become Gravestone", + "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", + "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", + "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", + "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", + "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", + "EvilTrackerTargetMode": "Can Set Target", + "EvilTrackerTargetMode.Never": "Never", + "EvilTrackerTargetMode.OnceInGame": "Once in game", + "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", + "EvilTrackerTargetMode.Always": "Any time", + "WitchModeSwitchAction": "Switch Action via", + "NBareRed": "Neutral Benign can be red", + "NEareRed": "Neutral Evil can be red", + "NCareRed": "Neutral Chaos can be red", + "NAareRed": "Neutral Apocalypse can be red", + "CrewKillingRed": "Crewmate Killings can be red", + "PsychicCanSeeNum": "Max number of red names", + "PsychicFresh": "New red names every meeting", + "DetectiveCanknowKiller": "Can find the killer's role", + "EveryOneKnowSuperStar": "Everyone knows the Super Star", + "HackLimit": "Ability Use Count", + "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", + "NemesisCanKillNum": "Max number of revenges", + "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", + "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", + "JesterCanUseButton": "Can call emergency meetings", + "VectorVentNumWin": "Number of Vents to win", + "CanCheckCamera": "Can track camera usage", + "Arrogance/Juggernaut___DefaultKillCooldown": "Starting kill cooldown", + "Arrogance/Juggernaut___ReduceKillCooldown": "Reduce kill cooldown by", + "Arrogance/Juggernaut___MinKillCooldown": "Minimum kill cooldown", + "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", + "NotifyGodAlive": "Inform players at meetings that God is still alive", + "TransporterTeleportMax": "Max number of teleports", + "TriggerKill": "Kill", + "TriggerVent": "Vent", + "TriggerDouble": "Double Click", + "TimeManagerIncreaseMeetingTime": "Increase voting time by", + "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", + "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", + "AssignOnlyToCrewmate": "Assign only to Crewmates", + "WorkhorseNumLongTasks": "Additional Long Tasks", + "WorkhorseNumShortTasks": "Additional Short Tasks", + "SnitchCanBeWorkhorse": "Snitch can become Workhorse", + "InnocentCanWinByImp": "If their target was an Impostor then they win with them", + "ImpCanBeSchizophrenic": "Impostors can become Schizophrenic", + "CrewCanBeSchizophrenic": "Crewmates can become Schizophrenic", + "DualVotes": "Duplicate votes", + "ProtectCooldown": "Protect Cooldown", + "ProtectDur": "Protection Duration", + "ProtectVisToImp": "Protect Visible To Impostors", + "VeteranSkillCooldown": "Alert Cooldown", + "VeteranSkillDuration": "Alert Duration", + "BodyguardProtectRadius": "Protect Radius", + "ImpCanBeEgoist": "An Impostor can become Egoist", + "CrewCanBeEgoist": "Crewmates can become Egoist", + "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", + "EgoistCountAsConverted": "Egoist count as converted neutral", + "ImpCanBeSeer": "Impostors can become Seer", + "CrewCanBeSeer": "Crewmates can become Seer", + "NeutralCanBeSeer": "Neutrals can become Seer", + "ImpCanBeGuesser": "Impostors can become Guesser", + "CrewCanBeGuesser": "Crewmates can become Guesser", + "NeutralCanBeGuesser": "Neutrals can become Guesser", + "ImpCanBeWatcher": "Impostors can become Watcher", + "CrewCanBeWatcher": "Crewmates can become Watcher", + "NeutralCanBeWatcher": "Neutrals can become Watcher", + "ImpCanBeBait": "Impostors can become Bait", + "CrewCanBeBait": "Crewmates can become Bait", + "NeutralCanBeBait": "Neutrals can become Bait", + "ImpCanBeRainbow": "Impostors can become Rainbow", + "NeutralCanBeRainbow": "Neutrals can become Rainbow", + "CrewCanBeRainbow": "Crewmates can become Rainbow", + "GuessRainbow": "He seems too obvious, doesn't he?", + "RainbowColorChangeCoolDown": "The cooldown for changing colors", + "RainbowInCamouflage": "Rainbow color changes during Camouflage", + "BaitDelayMin": "Minimum Report Delay", + "BaitDelayMax": "Maximum Report Delay", + "BaitDelayNotify": "Warn the killer about the upcoming self-report", + "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", + "BaitNotification": "Reveal Bait at the first meeting", + "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self report.", + "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if meeting is disabled during comms sabotage", + "DeceiverSkillCooldown": "Ability cooldown", + "DeceiverSkillLimitTimes": "Max number of uses", + "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", + "PursuerSkillCooldown": "Ability cooldown", + "PursuerSkillLimitTimes": "Max number of uses", + "AddictSuicideTimer": "Time Until Suicide", + "GrenadierSkillCooldown": "Grenade Cooldown", + "GrenadierSkillDuration": "Grenade Duration", + "GrenadierCauseVision": "Lowered vision", + "GrenadierCanAffectNeutral": "Can affect Neutrals", + "TicketsPerKill": "Votes Increase Amount Per Kill", + "GangsterRecruitCooldown": "Recruit cooldown", + "GangsterRecruitLimit": "Recruit limit", + "KamikazeMaxMarked": "Max Marked", + "RevolutionistDrawTime": "Tag Duration", + "RevolutionistCooldown": "Tag Cooldown", + "RevolutionistDrawCount": "Amount of Players needed to Tag", + "RevolutionistKillProbability": "Tagged player sacrifice probability", + "RevolutionistVentCountDown": "Time to Vent", + "PelicanKillCooldown": "Eat Cooldown", + "Pelican.TargetCannotBeEaten": "Target cannot be eaten", + "MadSnitchTasks": "Snitch Tasks", + "MedicWhoCanSeeProtect": "Who can see shield", + "MedicKnowShieldBroken": "Who sees kill attempt", + "Medic_SeeMedicAndTarget": "Medic+Shielded", + "Medic_SeeMedic": "Medic", + "Medic_SeeTarget": "Shielded", + "Medic_SeeNoOne": "Nothing", + "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", + "MedicShielDeactivationIsVisible": "Shield deactivation is visible", + "MedicShieldDeactivationIsVisible_DeactivationImmediately": "Immediately", + "MedicShieldDeactivationIsVisible_DeactivationAfterMeeting": "After Meeting", + "MedicShieldDeactivationIsVisible_DeactivationIsVisibleOFF": "OFF", + "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", + "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", + "FortuneTellerSkillLimit": "Max number of ability uses", + "MadmateSpawnMode": "Madmate spawning mode", + "MadmateSpawnMode.Assign": "Assign", + "MadmateSpawnMode.FirstKill": "First Kill", + "MadmateSpawnMode.SelfVote": "Self Vote", + "MadmateCountMode": "Madmates count as", + "MadmateCountMode.None": "Nothing", + "MadmateCountMode.Imp": "Impostors", + "MadmateCountMode.Original": "Original Team", + + "SnatchesWin": "Snatches victory", + "DemonKillCooldown": "Attack Cooldown", + "DemonHealthMax": "Player max health", + "DemonDamage": "Damage ", + "DemonSelfHealthMax": "Demon max health", + "DemonSelfDamage": "Demon damage received", + "LightningConvertTime": "Duration of the transformation to Quantum Ghost", + "LightningKillCooldown": "Lightning Cooldown", + "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", + "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", + "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", + "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", + "WorkaholicCannotWinAtDeath": "Can't win after they died", + "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", + "WorkaholicGiveAdviceAlive": "Advice at first meeting if alive, can win after death, ghost tasks ON", + "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", + "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", + "Jinx/CursedWolf___KillAttacker": "Kill attacker when ability is remaining", + "JinxSpellTimes": "Amount of Jinx Spells", + "CollectorCollectAmount": "Required number of votes", + "GlitchCanVote": "Can vote", + "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", + "MeetingReserved": "Max Bullets reserved for a meeting", + "AccurateCheckMode": "Can know specific role when tasks are not done", + "RandomActiveRoles": "Show random active roles in Fortune Teller hints", + "CamouflageCooldown": "Camouflage Cooldown", + "CamouflageDuration": "Camouflage Duration", + "EraseLimit": "Max Erases", + "EraserHideVote": "Hide Eraser Votes", + "NinjaMarkCooldown": "Mark Cooldown", + "NinjaAssassinateCooldown": "Ass​assinate Cooldown", + "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", + "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", + "JudgeCanTrialNeutralB": "Can trial Neutral Benign", + "JudgeCanTrialNeutralK": "Can trial Neutral Killing", + "JudgeCanTrialNeutralE": "Can trial Neutral Evil", + "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", + "JudgeCanTrialSidekick": "Can trial Sidekick", + "JudgeCanTrialInfected": "Can trial Infected", + "JudgeCanTrialContagious": "Can trial Contagious", + "JudgeTryHideMsg": "Hide Judge's commands", + "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", + "JudgeCanTrialMadmate": "Can trial Madmates", + "JudgeCanTrialCharmed": "Can trial Charmed players", + "JudgeDead": "Sorry, you can't trial after death.", + "JudgeTrialMax": "\nNo more trials left!", + "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", + "Judge_TrialKill": "{0} was judged.", + "Judge_TrialKillTitle": "COURT", + "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Judge_TrialNull": "Please choose a living player for the trial", + "VeteranSkillMaxOfUseage": "Max number of Alerts", + "SwooperCooldown": "Swoop Cooldown", + "SwooperDuration": "Swoop Duration", + "WraithCooldown": "Vanish Cooldown", + "WraithDuration": "Vanish Duration", + "BastionNotify": "A bomb was set off", + "EnteredBombedVent": "That vent was bombed!", + "BastionVentButtonText": "Bomb", + "BombsClearAfterMeeting": "Bombs clear after meetings", + "BastionMaxBombs": "(Initial) Maximum bombs", + "VentBombSuccess": "Bomb has been planted", + "LowLoadMode": "Low Load Mode", + "ShowLobbyCode": "Show lobby code in Discord status", + "BKProtectDuration": "Protection Duration", + "FollowerMaxBetTimes": "Maximum Number of Follows", + "FollowerBetCooldown": "Follow Cooldown", + "FollowerMaxBetCooldown": "Maximum Follow Cooldown", + "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", + "FollowerKnowTargetRole": "Follower knows their target's role", + "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", + "FortuneTellerHideVote": "Hide Fortune Teller's Votes", + "CultistCharmCooldown": "Charm Cooldown", + "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", + "CultistCharmMax": "Maximum Number Of Charm", + "CultistKnowTargetRole": "Know Charmed Player's Role", + "CultistTargetKnowOtherTarget": "Charmed players know each other", + "CultistCanCharmNeutral": "Neutral Roles can be Charmed", + "InfectiousBiteCooldown": "Infect Cooldown", + "KnowTargetRole": "Knows role of target", + "TargetKnowsLawyer": "Target knows their Lawyer", + "InfectiousBiteMax": "Maximum Infections", + "InfectiousKnowTargetRole": "Know infected player's role", + "InfectiousTargetKnowOtherTarget": "Infected players know each other", + "DoubleClickKill": "Double click to kill the target", + + "VirusInfectMax": "Maximum Number Of Spreads", + "VirusKnowTargetRole": "Know Contagious Player's Role", + "VirusTargetKnowOtherTarget": "Contagious players know each other", + "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", + "Virus_ContagiousCountMode": "Contagious players count as", + "Virus_ContagiousCountMode_None": "Nothing", + "Virus_ContagiousCountMode_Virus": "Virus", + "Virus_ContagiousCountMode_Original": "Original Team", + "VirusNoticeTitle": "[ Infected Corpse! ]", + "VirusNoticeMessage": "The body your reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", + "VirusNoticeMessage2": "The body your reported was infected by the Virus! Vote the Virus out this meeting or you will die.", + + "Cultist_CharmedCountMode": "Charmed players count as", + "Cultist_CharmedCountMode_None": "Nothing", + "Cultist_CharmedCountMode_Cultist": "Cultist", + "Cultist_CharmedCountMode_Original": "Original Team", + + "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", + "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", + "JackalResetKillCooldownOn": "Kill Cooldown On Reset", + "JackalCanRecruitSidekick": "Can recruit Sidekick", + "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", + "Jackal_SidekickCountMode": "Sidekicks count as", + "Jackal_SidekickCountMode_None": "Nothing", + "Jackal_SidekickCountMode_Jackal": "Jackal", + "Jackal_SidekickCountMode_Original": "Original Team", + "Jackal_SidekickAssignMode": "Sidekick Assign Mode", + "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", + "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", + "Jackal_SidekickAssignMode_Recruit": "Recruit Only", + "JackalWinWithSidekick": "Jackal can win with Sidekick's team", + "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", + "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", + "JackalCanKillSidekick": "Jackal can kill Sidekick", + + "ImpCanBeNecroview": "Impostors can become Necroview", + "CrewCanBeNecroview": "Crewmates can become Necroview", + "NeutralCanBeNecroview": "Neutrals can become Necroview", + "ImpCanBeInLove": "Impostors can be in love", + "CrewCanBeInLove": "Crewmates can be in love", + "NeutralCanBeInLove": "Neutrals can be in love", + "ImpCanBeOblivious": "Impostors can become Oblivious", + "CrewCanBeOblivious": "Crewmates can become Oblivious", + "NeutralCanBeOblivious": "Neutrals can become Oblivious", + "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", + "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", + "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", + "HexesLookLikeSpells": "Hexes appear as spells", + "HexButtonText": "Hex", + "ObliviousBaitImmune": "Immune to Bait", + "ImpCanBeOnbound": "Impostors can become Onbound", + "CrewCanBeOnbound": "Crewmates can become Onbound", + "NeutralCanBeOnbound": "Neutrals can become Onbound", + + "ImpCanBeRebound": "Impostors can become Rebound", + "CrewCanBeRebound": "Crewmates can become Rebound", + "NeutralCanBeRebound": "Neutrals can become Rebound", + + "CrewCanBeMundane": "Crewmates can become Mundane", + "NeutralCanBeMundane": "Neutrals can become Mundane", + "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", + + "ImpCanBeUnreportable": "Impostors can become Disregarded", + "CrewCanBeUnreportable": "Crewmates can become Disregarded", + "NeutralCanBeUnreportable": "Neutrals can become Disregarded", + "PacifistCooldown": "Ability Cooldown", + "PacifistMaxOfUseage": "Max Number of Ability Uses", + "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", + "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", + "TrackerHideVote": "Hide Tracker Votes", + "TrackerCanGetArrowColor": "Can See Colored Arrows", + + "PresidentAbilityUses": "Max Number of Ability Uses", + "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", + "HidePresidentEndCommand": "Hide President's commands", + "NeutralsSeePresident": "Neutrals can see revealed President", + "MadmatesSeePresident": "Madmates can see revealed President", + "ImpsSeePresident": "Impostors can see revealed President", + "PresidentDead": "Sorry, you can't force end the meeting after death.", + "PresidentEndMax": "No more force end meeting uses left!", + "PresidentRevealMax": "You have already revealed yourself...", + "PresidentRevealed": "[{0}] has chose to reveal themselves as President!", + "GuessPresident": "President has revealed themselves, you can't guess them.", + "PresidentRevealTitle": "PRESIDENT REVEAL", + + "LuckyProbability": "Probability of surviving a kill", + "ImpCanBeLucky": "Impostors can become Lucky", + "CrewCanBeLucky": "Crewmates can become Lucky", + "NeutralCanBeLucky": "Neutrals can become Lucky", + "ImpCanBeFool": "Impostors can become Fool", + "CrewCanBeFool": "Crewmates can become Fool", + "NeutralCanBeFool": "Neutrals can become Fool", + "ImpCanBeDoubleShot": "Impostors can have Double Shot", + "CrewCanBeDoubleShot": "Crewmates can have Double Shot", + "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", + "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", + "DisableReportWhenCamouflageIsActive": "Disable body reporting when сamouflage is active", + "CanUseCommsSabotage": "Can use comms sabotage", + "ModTag": "Moderator♥", + "ApplyModeratorList": "Apply Moderator List", + "VipTag": "VIP★", + "ApplyVipList": "Apply VIP List", + "AllowSayCommand": "Allow moderators to use /say command", + "KickCommandDisabled": "The kick command is currently disabled.", + "KickCommandNoAccess": "You do not have access to the kick command.", + "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reseaon]' to kick a player.\nExample :- /kick 5 not following rules", + "KickCommandKickHost": "You are not permitted to kick the host.", + "KickCommandKickMod": "You are not permitted to kick other moderators.", + "KickCommandKicked": "was kicked from the game by ", + "KickCommandKickedRole": "Their role was", + "BanCommandDisabled": "The ban command is currently disabled.", + "BanCommandNoAccess": "You do not have access to the ban command.", + "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", + "BanCommandBanHost": "You are not permitted to ban the host.", + "BanCommandBanMod": "You are not permitted to ban other moderators.", + "BanCommandBanned": "was banned from the game by ", + "BanCommandBannedRole": "Their role was", + "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", + "ColorCommandDisabled": "The modcolor command is currently disabled.", + "ColorCommandNoAccess": "You do not have access to the modcolor command.", + "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff", + "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", + "VipColorCommandDisabled": "The vipcolor command is currently disabled.", + "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", + "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff", + "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", + "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change color of your tag.\nExample :- /tagcolor ff00ff", + "midCommandDisabled": "The mid command is currently disabled.", + "midCommandNoAccess": "You do not have access to the mid command.", + "DisableVoteBan": "Disable VoteKick System", + "WarnCommandDisabled": "The warn command is currently disabled.", + "WarnCommandNoAccess": "You do not have access to the warn command.", + "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", + "WarnCommandWarnHost": "You are not permitted to warn the host.", + "WarnCommandWarnMod": "You are not permitted to warn other moderators.", + "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", + "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", + "SayCommandDisabled": "The say command is currently disabled.", + "MessageFromModerator": "MODERATOR", + "DeathReason.Kill": "Kill", + "DeathReason.Vote": "Ejected", + "DeathReason.Suicide": "Suicide", + "DeathReason.Spell": "Spelled", + "DeathReason.Cursed": "Cursed", + "DeathReason.Hex": "Hexed", + "DeathReason.Bite": "Bitten", + "DeathReason.Poison": "Poisoned", + "DeathReason.Gambled": "Guessed", + "DeathReason.FollowingSuicide": "Heartbroken", + "DeathReason.Bombed": "Exploded", + "DeathReason.Misfire": "Misfire", + "DeathReason.Torched": "Burned", + "DeathReason.Sniped": "Sniped", + "DeathReason.Execution": "Executed", + "DeathReason.Disconnected": "Disconnected", + "DeathReason.Fall": "Fall", + "DeathReason.Revenge": "Revenge", + "DeathReason.Eaten": "Eaten", + "DeathReason.Sacrifice": "Victim", + "DeathReason.Quantization": "Quantization", + "DeathReason.Overtired": "Overtired", + "DeathReason.Ashamed": "Ashamed", + "DeathReason.PissedOff": "Destroyed", + "DeathReason.Dismembered": "Dismembered", + "DeathReason.LossOfHead": "Strangled", + "DeathReason.Trialed": "Judged", + "DeathReason.Infected": "Infected", + "DeathReason.Jinx": "Jinxed", + "DeathReason.Pirate": "Plundered", + "DeathReason.Shrouded": "Shrouded", + "DeathReason.etc": "Other", + "DeathReason.Mauled": "Mauled", + "DeathReason.Hack": "Hacked", + "DeathReason.Curse": "Cursed", + "DeathReason.Drained": "Drained", + "DeathReason.Shattered": "Shattered", + "DeathReason.Trap": "Trapped", + "DeathReason.Targeted": "Targeted", + "DeathReason.Retribution": "Retribution", + "DeathReason.Slice": "Sliced", + "DeathReason.BloodLet": "Bleed", + "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", + "Alive": "Alive", + "Win": " Wins!", + + "Last-": "Last ", + "Madmate-": "Madmate ", + "Recruit-": "Recruit ", + "Charmed-": "Charmed ", + "Soulless-": "Soulless ", + "Infected-": "Infected ", + "Contagious-": "Contagious ", + "Admired-": "Admired ", + + "DeputyHandcuffCooldown": "Handcuff Cooldown", + "DeputyHandcuffMax": "Maximum Handcuffs", + "DeputyHandcuffedPlayer": "Handcuffed target", + "HandcuffedByDeputy": "You were handcuffed!", + "DeputyInvalidTarget": "Target cannot be handcuffed", + "DeputyHandcuffText": "Handcuff", + "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", + + "RejectShapeshift.AbilityWasUsed": "Ability was used", + "ShowShapeshiftAnimations": "Show Shapeshift animations", + + "EscapisMtarkedPosition": "You marked self position", + + "InvestigateCooldown": "Investigate Cooldown", + "InvestigateMax": "Maximum Investigations", + "InvestigateRoundMax": "Maximum Investigations in one round", + + "Color.Red": "Red", + "Color.Green": "Green", + "Color.Gray": "Gray", + "InvestigatorInvestigatedPlayer": "Player Investigated", + "InvestigatorInvalidTarget": "Can not investigate", + "InvestigatorButtonText": "Check", + + "Investigator.Suspicion": "Suspicion", + "Investigator.Role": "Role", + "SabotageCooldownControl": "Sabotage Cooldown Control", + "SabotageCooldown": "Sabotage Cooldown", + "SabotageTimeControl": "Sabotage Duration Control", + "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", + "SkeldO2TimeLimit": "The Skeld O2 Time Limit", + "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", + "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", + "PolusReactorTimeLimit": "Polus Reactor Time Limit", + "AirshipReactorTimeLimit": "Airship Reactor Time Limit", + "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", + "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", + "CommandList": "★ Command list:", + "Command.now": "→ Display active Settings", + "Command.roles": "[RoleName] → Display Role description", + "Command.myrole": "→ Displays a description of your role", + "Command.lastresult": "→ Display match results", + "Command.winner": "→ Display winners", + "CommandOtherList": "● Other commands:", + "Command.color": "[Color] → Change your color", + "Command.rename": "[Name] → Change Host Name", + "Command.quit": "→ I don't want to enter this lobby anymore", + "CommandHostList": "▲ Host Commands:", + "Command.say": "[Content] → Send message as Host", + "Command.mw": "[Seconds] → Set the message waiting duration", + "Command.solvecover": "→ Fix an issue where role names overlap the messages", + "Command.kill": "[Player ID] → Kill assigned player", + "Command.exe": "[Player ID] → Eject assigned player", + "Command.level": "[Level] → Change your in-game level", + "Command.idlist": "→ Display a list of player IDs", + "Command.qq": "→ Lobby will be posted on QQ website (China only)", + "Command.dump": "→ Output Log to Desktop", + "Command.death": "→ Display info on how you died", + "Command.icons": "Icon Meanings\n† - This player was spelled by a Witch and will die if the Witch is not killed by the end of the meeting\n乂 - This player was hexed by a Hex Master and will die if the Hex Master is not killed by the end of the meeting\n❖ - This player was hexed by an Occultist and will die if the Occultist is not killed by the end of the meeting\n◈ - This player was shrouded by a Shroud and will die if the Shroud is not killed by the end of the meeting\n⦿ - This player is being dueled by a Pirate\n♥ - This player is a Lover\n⚠ - This player is a Snitch who has finished their tasks", + "Command.iconinfo": "→ Display info on in-meeting icons", + "Command.iconhelp": "→ Display info on in-meeting icons to everyone", + "Remaining.ImpostorCount": "Impostors left: ", + "Remaining.NeutralCount": "Neutral Killers left: ", + "EnableKillerLeftCommand": "Enable use of /kcount command", + "SeeEjectedRolesInMeeting": "See ejected roles in meetings", + + "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", + "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", + "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", + "NemesisKillDead": "Choose a living player to take revenge", + "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", + "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", + + "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period of time!", + "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", + "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", + "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", + "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer, this is most likely suicide.", + "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", + "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", + "GuessDead": "Sorry, but it's impossible to guess roles after your death", + "GuessSuperStar": "The Super Star can't be guessed.... you thought it would be that easy, right?", + "GuessNotifiedBait": "Bait can't be guessed because it was announced, you thought it would be that easy, right?", + "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", + "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", + "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", + "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", + "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", + "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", + "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", + "GuessKill": "{0} was guessed", + "GuessNull": "Please select an ID of a living player to guess their role", + "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "GGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", + "EGGuessMax": "You've reached the limit of maximum guesses, you can't guess anymore!", + "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all of their tasks are done? Nice try.... You're not getting out of this that easily.", + "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot so you get another chance!", + "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", + "GuessDisabled": "Sorry, the host restricted guessing for your role.", + "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", + "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", + "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", + "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", + "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", + "GuessShielded": "Sorry, you can't guess the player who is shielded by Medic", + "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", + "MimicDeadMsg": "Mimic's hint: ", + "FortuneTellerCheck": "According to your fortune...", + "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", + "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", + "FortuneTellerCheckReachLimit": "You've run out of fortunes.", + "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", + "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", + "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", + "EraserEraseSelf": "Unfortunately, you can't erase yourself.... Wait, why would you do that in the first place?!", + "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", + "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", + + "MediumContactLimit": "Max number of contacts (ability uses)", + "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", + "MediumTitle": "MEDIUM", + "MediumHelp": "/ms yes to agree\n/ms no to disagree", + "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", + "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", + "MediumDone": "You successfully responded to the Medium.", + "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", + "MediumNotifySelf": "You established contact with {0}, please ask questions to them and wait for them to respond.\n\nRemaining ability uses: {1}", + "MediumKnowPlayerDead": "Someone died somewhere", + + "ByBard": "by Bard", + "ByBardGetFailed": "oops, I seem to be out of inspiration.", + "GangsterSuccessfullyRecruited": "You successfully recruited a player", + "GangsterRecruitmentFailure": "Target cannot be recruited", + "BeRecruitedByGangster": "You have been recruited by the Gangster", + "KamikazeHostage": "Can't hold target hostage", + "VeteranOnGuard": "Ability in use", + "VeteranOffGuard": "Ability expired, {0} uses remain", + "VeteranMaxUsage": "Ability use limit reached", + "GrenadierSkillInUse": "Ability in use", + "GrenadierSkillStop": "Ability expired", + "TicketsStealerGetTicket": "You've got {0} votes", + "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", + "SpeedBoosterTaskDone": "Your speed is {0} now", + "SpeedBoosterSpeedLimit": "You reached your maximum speed (3x)", + "CleanerCleanBody": "The body has been cleaned", + "QuickShooterStoraging": "Bullets stored successfully", + "VampireTargetDead": "Target died", + "PoisonerTargetDead": "Target died", + "BloodlustAdded": "Your bloodlust is now active!", + "WarlockNoTarget": "Manipulation failed due to no target", + "WarlockNoTargetYet": "You haven't mark a target.", + "WarlockTargetDead": "Manipulation failed due to target dead", + "WarlockControlKill": "Target died", + "OnCelebrityDead": "Warning: Celebrity death!", + "OnCyberDead": "Warning: Cyber died!", + "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", + "TeleportedByTransporter": "Swapping places with: {0}", + "ErrorTeleport": "Teleport failed", + "LostRoleByEraser": "You lost your role because of the Eraser", + "KilledByScavenger": "You were killed by the Scavenger and thus teleported off-map", + "SnitchDoneTasks": "Call a meeting to find the impostors", + "SwooperCanVent": "Vent to turn invisible", + "SwooperInvisState": "You're invisible", + "SwooperInvisStateOut": "You're now visible", + "SwooperInvisInCooldown": "Swoop cooldown isn't up yet, swooping failed", + "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", + "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", + "WraithCanVent": "Vent to turn invisible", + "WraithInvisState": "You are invisible", + "WraithInvisStateOut": "You are visible again", + "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", + "WraithInvisStateCountdown": "Invisibility will expire in {0}s", + "WraithInvisCooldownRemain": "{0}s left in invisibility", + "BKInProtect": "Currently immortal", + "BKProtectOut": "Shield expired", + "BKSkillTimeRemain": "You're immune for {0} seconds", + "BKSkillNotice": "Kill a player to enter immune status", + "BKOffsetKill": "Someone tried killing you", + "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", + "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", + "FollowerBetPlayer": "You're now following your target", + "FollowerBetOnYou": "The Follower is now following you", + "CultistCharmedPlayer": "You successfully charmed a player", + "CharmedByCultist": "You have been charmed by the Cultist", + "CultistInvalidTarget": "Target cannot be charmed", + "KillBaitNotify": "You'll self-report in {0}s", + "InfectiousInvalidTarget": "Target cannot be infected", + "BittenByInfectious": "You were infected by the Infectious!", + "InfectiousBittenPlayer": "You successfully infected a player", + "GuessNotAllowed": "Sorry, your role does not have access to guessing.", + "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", + "GuessPhantom": "You can't guess a Phantom, that allows them to win!", + "PacifistOnGuard": "Ability used, {0} uses remain", + "PacifistMaxUsage": "Ability use limit reached", + "PacifistSkillNotify": "Pacifist reset your kill cooldown", + "BeRecruitedByJackal": "You have been recruited by the Jackal", + "CoronerTrackRecorded": "Track recorded", + "CoronerNoTrack": "Nothing to track", + "CoronerIsTrackingYou": "The Coroner is tracking you!", + "TrackerLastRoomMessage": "The evaluation of your tracking showed that the last room in which your target was located was:[{0}]", + "MerchantAddonDelivered": "Add-on sold", + "MerchantAddonSell": "The Merchant sold you a new Add-on", + "MerchantAddonSellFail": "Could not sell an Add-on", + "BribedByMerchant": "The Merchant bribed you, you can't kill him", + "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", + "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", + "TrapTrapsterBody": "Trap Trapster's body", + "TrapConsecutiveBodies": "Trap consecutive bodies", + "HauntedByEvilSpirit": "Haunted by an Evil Spirit", + "MonarchKnightCooldown": "Knight Cooldown", + "MonarchKnightMax": "Maximum Knights", + "MonarchKnightedPlayer": "You successfully knighted a player!", + "KnightedByMonarch": "You have been knighted by a Monarch!", + "MonarchInvalidTarget": "Target cannot be knighted", + "GhostTransformTitle": "Your Role Has Transformed!", + "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", + "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", + "OverseerRevealCooldown": "Reveal Cooldown", + "OverseerRevealTime": "Reveal Time", + "OverseerVision": "Overseer Vision", + "MerchantMaxSell": "Max number of Add-ons to sell", + "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", + "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", + "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", + "MerchantTargetCrew": "Can sell to Crewmates", + "MerchantTargetImpostor": "Can sell to Impostors", + + "MerchantTargetNeutral": "Can sell to Neutrals", + "MerchantSellHelpful": "Can sell Helpful Add-ons", + "MerchantSellHarmful": "Can sell Harmful Add-ons", + "MerchantSellMixed": "Can sell Mixed Add-ons", + "MerchantSellExperimental": "Can sell experimental Add-ons", + "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", + "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", + "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", + + "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", + "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", + "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", + "SpiritcallerProtectTime": "Evil Spirit ability protect time", + "SpiritcallerCauseVision": "Evil Spirit ability caused vision", + "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", + "Message.SetToSeconds": "Set to [{0}] seconds.", + "Message.MessageWaitHelp": "Specify the first argument in seconds.", + "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", + "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", + "Message.SyncButtonLeft": "There are {0} more emergency buttons left", + "Message.Executed": "{0} was executed", + "Message.HideGameSettings": "Game settings have been hidden by the host.", + "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", + "Message.NoDescription": "No description", + "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", + "Message.BannedByBanList": "{0} was banned because they were banned in the past.", + "Message.BannedByEACList": "{0} has been banned because he is in EAC list of Banned people.", + "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", + "Message.DumpcmdUsed": "{0} used /dump command.", + "Message.KickedByNoFriendCode": "{0} was kicked because their friend code does not exist.", + "Message.TempBannedByNoFriendCode": "{0} was temporary banned because their friend code does not exist.", + "Message.AddedPlayerToBanList": "Added {0} to the ban list", + "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", + "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", + "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", + "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", + "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", + "Message.NoticeByEAC": "[{0}]Detected:{1}", + "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", + "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", + "Message.KickedByWhiteList": "{0} kicked because friendcode not found in WhiteList.txt", + "Message.SetLevel": "Your game level is set to: {0}", + "Message.SetColor": "Your color is set to: {0}", + "Message.SetName": "Your name is set to: {0}", + "Message.AllowLevelRange": "The game level can be set in the range: 0-100", + "Message.AllowNameLength": "Nickname can be set length: 1-10", + "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", + "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", + "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", + "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", + "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", + "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", + "Message.HostLeftGameInGame": "★Warning★ Host left the game, in next time game wouldn't start normally. Please, exit the lobby or wait until new Host opens a lobby.", + "Message.HostLeftGameInLobby": "★Warning★ Host left the game, in next time game wouldn't start normally. If new host has TOHE, you need to re-enter the lobby to play normally.", + "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, just start a game and end it immediately to reset the lobby!", + "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please, exit the lobby or wait until new Host opens a lobby.", + "Message.LobbyShared": "The lobby has successfully been shared!", + "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", + "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", + "Message.YTPlanSelected": "In the next game, your role will be {0}", + "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", + "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. In case the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", + "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", + "Message.MaxPlayers": "Maximum players set to ", + "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little bit about ghost roles...\n\nGhost roles drastically impact the game, so not recommended for smaller lobbies, if you're unfamiliar.\n\nSpawning:\nGhost-roles only spawn after death, the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g sheriff), your tasks as a ghost-role aren't needed for task-win", + + "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", + "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", + "WarningTitle": "Warning!", + "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", + "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", + + "AntiBlackoutProtectionTitle": "Anti Blackout", + "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normaly", + "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in last meeting.\n", + "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", + + "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE installed.", + "Warning.NoModHost": "TOHE is not installed on the host", + "Warning.MismatchedVersion": "{0} has a different version of {1}", + "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", + "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", + "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", + "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", + "Error.InvalidColor": "Error: Only default colors are available", + "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors, otherwise it will result in a serious error", + "ErrorLevel1": "Bugs may occur.", + "ErrorLevel2": "This may be a bug.", + "ErrorLevel3": "This version shouldn't have been released.", + "TerminateCommand": "Abort Command", + "ERR-000-000-0": "No Error", + "ERR-000-900-0": "Test Error Lv.0", + "ERR-000-910-1": "Test Error Lv.1", + "ERR-000-920-2": "Test Error Lv.2", + "ERR-000-930-3": "Test Error Lv.3", + "ERR-000-804-1": "TownofHost-Enhanced does not support the Vanilla HnS, so unloaded.", + "ERR-001-000-3": "Main dictionary has duplicated keys.", + "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", + "DefaultSystemMessageTitle": "SYSTEM MESSAGE", + "MessageFromTheHost": "HOST MESSAGE", + "MessageFromEAC": "EAC", + "DetectiveNoticeTitle": "INVESTIGATION", + "SleuthNoticeTitle": "SLEUTH", + "GuessKillTitle": "GUESSING INFO", + "CelebrityNewsTitle": "CELEBRITY", + "CyberNewsTitle": "CYBER", + "GodAliveTitle": "GOD ", + "WorkaholicAliveTitle": "WORKAHOLIC", + "BaitAliveTitle": "BAIT", + "MessageFromKPD": "KARPED1EM ", + "MessageFromSponsor": "SPONSOR MESSAGE ", + "MessageFromDev": "DEVELOPER MESSAGE ", + "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", + "MimicMsgTitle": "MIMIC", + "EraserEraseMsgTitle": "ERASER", + "MorticianCheckTitle": "CORPSE EXAMINATION", + "NemesisRevengeTitle": "NEMESIS", + "RetributionistRevengeTitle": "RETRIBUTIONIST", + "TabGroup.SystemSettings": "System Settings", + "TabGroup.GameSettings": "Game Settings", + "TabGroup.CrewmateRoles": "Crewmate Roles", + "TabGroup.NeutralRoles": "Neutral Roles", + "TabGroup.ImpostorRoles": "Impostor Roles", + "TabGroup.Addons": "Add-Ons", + "TabGroup.OtherRoles": "Experimental Roles (Not recommended)", + "TabGroup.TaskSettings": "Game Modifiers", + "OtherRoles.CrewmateRoles": "★ Crewmate Roles", + "OtherRoles.NeutralRoles": "★ Neutral Roles", + "OtherRoles.ImpostorRoles": "★ Impostor Roles", + "OtherRoles.Addons": "★ Add-Ons", + "ActiveRolesList": "Active Roles List", + "ForExample": "Example Use", + "updateButton": "Update", + "updatePleaseWait": "Please Wait...", + "updateManually": "Update failed.\nPlease Update Manually.", + "updateRestart": "Update Finished!\nPlease restart the game.", + "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease update.", + "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", + "UnsupportedVersion": "Unsupported Among Us version.\nPlease update Among Us", + "DisabledByProgram": "Public rooms have been disabled by the program", + "EnterVentToWin": "Enter Vent to Win!!", + "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", + "FireworkerPutPhase": "{0} Fireworker Left", + "FireworkerWaitPhase": "Wait for it...", + "FireworkerReadyFirePhase": "Fire!", + "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", + "On": "ON", + "Off": "OFF", + "ColoredOn": "ON", + "ColoredOff": "OFF", + "CurrentActiveSettingsHelp": "Current Active Settings Help", + "WitchCurrentMode": "Current Mode", + "WitchModeKill": "Kill", + "WitchModeSpell": "Spell", + "HexMasterModeHex": "Hex", + "HexMasterModeKill": "Kill", + "PoisonerPoisonButtonText": "Poison", + "WitchModeDouble": "Double Click = Kill, Single Click = Spell", + "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", + "BountyCurrentTarget": "Current Target", + "Roles": "Roles", + "Settings": "Settings", + "Addons": "Add-Ons", + "LastResult": "★ Match Results", + "LastEndReason": "★ End Reason", + "KillLog": "Kill Log", + "Maximum": "Max", + "RoleRate": "ON", + "RoleOn": "ALWAYS", + "RoleOff": "OFF", + "Chance0": "0%", + "Chance5": "5%", + "Chance10": "10%", + "Chance15": "15%", + "Chance20": "20%", + "Chance25": "25%", + "Chance30": "30%", + "Chance35": "35%", + "Chance40": "40%", + "Chance45": "45%", + "Chance50": "50%", + "Chance55": "55%", + "Chance60": "60%", + "Chance65": "65%", + "Chance70": "70%", + "Chance75": "75%", + "Chance80": "80%", + "Chance85": "85%", + "Chance90": "90%", + "Chance95": "95%", + "Chance100": "100%", + "Preset": "Preset", + "Preset_1": "Preset 1", + "Preset_2": "Preset 2", + "Preset_3": "Preset 3", + "Preset_4": "Preset 4", + "Preset_5": "Preset 5", + "Standard": "Standard", + "GameMode": "Game Mode", + "PressTabToNextPage": "Press Tab or Number for Next Page...", + "RoleSummaryText": "Role Summary:", + "doOverride": "Override %role%'s Tasks", + "assignCommonTasks": "%role% has Common Tasks", + "roleLongTasksNum": "Amount of Long Tasks for %role%", + "roleShortTasksNum": "Amount of Short Tasks for %role%", + "Format.Players": "{0}", + "Format.Seconds": "{0}s", + "Format.Percent": "{0}%", + "Format.Times": "{0}", + "Format.Multiplier": "{0}x", + "Format.Votes": "{0}", + "Format.Pieces": "{0}", + "Format.Health": "{0}", + "Format.Level": "{0}", + "KillButtonText": "Kill", + "ReportButtonText": "Report", + "VentButtonText": "Vent", + "SabotageButtonText": "Sabotage", + "SniperSnipeButtonText": "Snipe", + "FireworkerExplosionButtonText": "Detonate", + "FireworkerInstallAtionButtonText": "Install", + "MercenarySuicideButtonText": "Suicide Timer", + "WarlockCurseButtonText": "Curse", + "NinjaShapeshiftText": "Kill", + "NinjaMarkButtonText": "Mark", + "WitchSpellButtonText": "Spell", + "VampireBiteButtonText": "Bite", + "MinerTeleButtonText": "Warp", + "ArsonistDouseButtonText": "Douse", + "PuppeteerOperateButtonText": "Manipulate", + "WarlockShapeshiftButtonText": "Spell", + "BountyHunterChangeButtonText": "Swap", + "EvilTrackerChangeButtonText": "Track", + "InnocentButtonText": "Frame", + "PelicanButtonText": "Eat", + "DeceiverButtonText": "Cheat", + "PursuerButtonText": "Trick", + "GangsterButtonText": "Recruit", + "RevolutionistDrawButtonText": "Win over", + "HaterButtonText": "Hatred", + "MedicalerButtonText": "Protect", + "DemonButtonText": "Attack", + "SoulCatcherButtonText": "Teleport", + "LightningButtonText": "Evaporate", + "ProvocateurButtonText": "Greet", + "ButcherButtonText": "Dismember", + "BomberShapeshiftText": "Explode", + "QuickShooterShapeshiftText": "Keep", + "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", + "CamouflagerShapeshiftTextAfterDisguise": "Duration", + "AnonymousShapeshiftText": "Hack", + "DefaultShapeshiftText": "Shift", + "CleanerReportButtonText": "Clean", + "SwooperVentButtonText": "Swoop", + "SwooperRevertVentButtonText": "Expose", + "WraithVentButtonText": "Vanish", + "WraithRevertVentButtonText": "Expose", + "VectorVentButtonText": "Hop", + "VeteranVentButtonText": "Alert", + "GrenadierVentButtonText": "Flash", + "MayorVentButtonText": "Button", + "SheriffKillButtonText": "Shoot", + "UndertakerButtonText": "Mark", + "ArsonistVentButtonText": "Ignite", + "RevolutionistVentButtonText": "Revolution", + "FollowerKillButtonText": "Follow", + "PacifistVentButtonText": "Reset", + "CultistKillButtonText": "Charm", + "InfectiousKillButtonText": "Infect", + "MonarchKillButtonText": "Knight", + "OverseerKillButtonText": "Reveal", + "DisabledBySettings": "Disabled by Settings", + "Disabled": "Disabled", + "FailToTrack": "Failed To Track", + "KillCount": "Kills: {0}", + "CantUse.lastroles": "Unable to use /lastroles during a game.", + "CantUse.killlog": "Unable to use /killlog during a game.", + "CantUse.lastresult": "Unable to use /lastresult during a game.", + "IllegalColor": "Please enter the correct color", + "DisableUseCommand": "The Host's settings do not allow this command to be used.", + "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", + "PlayerIdList": "List of player IDs: ", + "CancelStartCountDown": "The starting countdown was cancelled", + "RestTOHESetting": "TOHE settings have been restored to default", + "FPSSetTo": "FPS Set To: {0}", + "HostKillSelfByCommand": "The lobby Host decided to commit suicide", + "SyncCustomSettingsRPC": "Synchronized RPC", + "Mode": "Mode", + "Target": "Target", + "PlayerInfo": "Player Info", + "NoInfoExists": "No Info Exists", + "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", + "PlayerLeftByError": "Game will auto-end to prevent black screens.", + "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", + "KickBecauseLowLevel": "{0} was kicked because their level was too low", + "TempBannedBecauseLowLevel": "{0} was temporary banned because their level was too low", + "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", + + "FFADisplayScore": "Ranking: {0} Score: {1}", + "FFATimeRemain": "Time Remaining: {0} second(s)", + + "GameOver": "Game Over", + "TOHEOptions": "TOHE Options", + "Cancel": "Cancel", + "Back": "Back", + "Yes": "Yes", + "No": "No", + "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", + "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, game will end to prevent black screen.", + "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred, to prevent black screen, turn off [{1}] in settings.", + "AntiBlackOutNotifyInLobby": "An unknown error occurred, to prevent black screen. Sadly, this error exists in all Town of Host versions. Automatic ending game is required, or the game will not start.", + "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent black screen.", + "AntiBlackOutRequestHostToForceEnd": "You were the reason of the black screen, you are asking the host to stop the game...", + "AntiBlackOutHostRejectForceEnd": "You were the reason of the black screen, and the host is not going to end the game, we will disconnect you soon.", + "NextPage": "Next Page", + "PreviousPage": "Previous Page", + "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", + "PressF1ShowMainRoleDes": "Press F1: Show Role Description", + "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", + "FakeTask": "Fake Tasks:", + "PVP.ATK": "Attack", + "PVP.DF": "Defend", + "PVP.RCO": "Recover", + "SettingsAreLoading": "Loading\nsettings...", + "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", + "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", + "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", + "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", + "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", + "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", + "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", + "IsGood": "{0} was a good guy", + "BelongTo": "{0} belongs to {1}", + "PlayerIsRole": "{0} was The {1}", + "PlayerExiled": "{0} was ejected", + "NoImpRemain": "0 Impostors remain", + "OneImpRemain": "1 Impostor remains", + "TwoImpRemain": "2 Impostors remain", + "ThreeImpRemain": "3 Impostors remain", + "ImpRemain": "{0} Impostors remaining", + "NeutralRemain": "\n{0} Neutral Killers remain", + "OneNeutralRemain": "\n{0} Neutral Killer remains", + "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", + "GameOverReason.HumansByTask": "The Crewmates completed all tasks", + "GameOverReason.HumansDisconnect": "Crewmates disconnected", + "GameOverReason.ImpostorByVote": "The Crewmates were ejected", + "GameOverReason.ImpostorByKill": "The Impostors killed everyone", + "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", + "GameOverReason.ImpostorDisconnect": "Impostors disconnected", + "FortuneTellerCheck.Honest": "Looks like [{0}] is being honest.", + "FortuneTellerCheck.Impulse": "Looks like [{0}] is suppressing some kind of impulse.", + "FortuneTellerCheck.Weirdo": "Looks like [{0}] is mixed in crowd.", + "FortuneTellerCheck.Blockbuster": "Looks like [{0}] got big desires to change something.", + "FortuneTellerCheck.Strong": "Looks like [{0}] has a strong power but is dim in society.", + "FortuneTellerCheck.Incomprehensible": "Looks like [{0}] is being misunderstood.", + "FortuneTellerCheck.Enthusiasm": "Looks like [{0}] speaks with everyone very enthusiastic.", + "FortuneTellerCheck.Disturbed": "Looks like [{0}] is disturbed by something.", + "FortuneTellerCheck.None": "Looks like [{0}] has just an ordinary life.", + "FortuneTellerCheck.Glitch": "TV2gPZ4wUJWYr{0}05gA6T5PKzBG17", + "FortuneTellerCheck.HideMsg": "Looks like [{0}] hides secrets.", + "FortuneTellerCheck.Love": "♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥", + "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", + "DevAndSpnTitle": "TOHE family", + "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", + "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", + "SunnyboyChance": "Sunnyboy Chance", + "BardChance": "Bard Chance", + "VampiressChance": "Vampiress Chance", + "NukerChance": "Nuker Chance", + "SkeldChance": "Chance that the map is The Skeld", + "MiraChance": "Chance that the map is MIRA HQ", + "PolusChance": "Chance that the map is Polus", + "DleksChance": "Chance that the map is dlekS ehT", + "AirshipChance": "Chance that the map is Airship", + "FungleChance": "Chance that the map is The Fungle", + "UseMoreRandomMapSelection": "Use a more random map selection", + "CamouflageMode.Default": "Default", + "CamouflageMode.Host": "Host", + "CamouflageMode.Random": "Random", + "CamouflageMode.OnlyRandomColor": "Only Random Color", + "CamouflageMode.Karpe": "KARPED1EM", + "CamouflageMode.Lauryn": "Lauryn", + "CamouflageMode.Moe": "Moe", + "CamouflageMode.Pyro": "Pyro", + "CamouflageMode.ryuk": "ryuk", + "CamouflageMode.Gurge44": "Gurge44", + "CamouflageMode.TommyXL": "TommyXL", + "DeathCmd.HeyPlayer": "Hey ", + "DeathCmd.YouAreRole": ", looks like you're the ", + "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", + "DeathCmd.KillerName": "You were killed by ", + "DeathCmd.KillerRole": "Their role is ", + "DeathCmd.DeathReason": "Your cause of death was ", + "DeathCmd.YourName": "You are ", + "DeathCmd.YourRole": "Your role is ", + "DeathCmd.Ejected": "You were ejected during a meeting", + "DeathCmd.Misfired": "You misfired.", + "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", + "DeathCmd.Lovers": "Your lover had died.", + + "RpsCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", + "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", + "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", + "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I just have the world's worst luck algorithm.", + + "CoinFlipCommandInfo": "This Command can only be used when in lobby or after you die.", + "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", + + "GNoCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", + "GNoLost": "Oh, you were so close! Just one more guess, and you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", + "GNoLow": "Oh, you're really nailing this! It's so low, I almost need a shovel to dig it up!\nYou have {0} guesses left!", + "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high, I need a telescope to see it from here! \nYou have {0} guesses left!", + "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", + + "RandCommandInfo": "This Command can only be used when in lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", + "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", + + "ChanceToMiss": "Chance to miss a kill", + + "SoulCollectorPointsToWin": "Required number of souls", + "SoulCollectorTarget": "You have predicted the death of {0}", + "SoulCollectorTitle": "SOUL COLLECTOR", + "CollectOwnSoulOpt": "Can collect their own soul", + "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", + "SoulCollectorToDeath": "You have become Death!!!", + "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", + "CallMeetingIfDeath": "Call a meeting immediately after Death transforms", + "GetPassiveSouls": "Gain a passive soul every round", + "PassiveSoulGained": "You have gained a passive soul from the underworld.", + + "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", + "BakerToFamine": "You have become Famine!!!", + "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", + "BakerAlreadyBreaded": "That player already has bread!", + "BakerBreadUsedAlready": "You've already given a player bread this round!", + "BakerBreaded": "Player given bread", + "BakerBreadNeededToTransform": "Required number of bread to become Famine", + "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", + "BakerKillButtonText": "Bread", + + "ChronomancerKillCooldown": "Time to fully charge the kill button", + + "ShamanButtonText": "Voodoo", + "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", + "VoodooCooldown": "Voodoo Cooldown", + + "AdminWarning": "Admin Table in use!", + "VitalsWarning": "Vitals in use!", + "DoorlogWarning": "Doorlogs in use!", + "CameraWarning": "Cameras in use!", + "MinWaitAutoStart": "Minutes to wait before auto-starting", + "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", + "PlayerAutoStart": "Minimum Player Threshold to auto-start", + "AutoStartTimer": "Initial countdown for auto-starting", + "AutoPlayAgainCountdown": "Delay before re-entering lobby", + "AutoPlayAgain": "Auto Play Again", + "AutoRehost": "Auto Re-Host on Bad Disconnect", + "CountdownText": "Rejoining lobby in {0}s", + "TimeMasterSkillDuration": "Time Shield Duration", + "TimeMasterSkillCooldown": "Time Shield Cooldown", + "TimeMasterOnGuard": "Time Shield is active!", + "TimeMasterSkillStop": "Time Shield has ended!", + "TimeMasterVentButtonText": "Time Shield", + "BodyCannotBeReported": "Body could not be reported", + "BurstKillDelay": "Burst Kill Delay", + "BurstNotify": "That was a Burst! Get in a vent or die.", + "ImpCanBeBurst": "Impostors can become Burst", + "CrewCanBeBurst": "Crewmates can become Burst", + "NeutralCanBeBurst": "Neutrals can become Burst", + "BurstFailed": "Burst failed to bomb you", + "ShroudButtonText": "Shroud", + "ShroudCooldown": "Shroud Cooldown", + "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", + "LudopathRandomKillCD": "Maximum kill cooldown", + "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", + "GodfatherTargetCountMode": "Killer turns into", + "GodfatherCount_Refugee": "Refugee", + "GodfatherCount_Madmate": "Madmate", + "MissChance": "Chance To Miss", + "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", + "HawkMissed": "Missed!", + "HawkCanKillNum": "Max Slices", + "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", + "BloodMoonCanKillNum": "Max BloodLettings", + "BloodMoonTimeTilDie": "Time Until Death", + "DeathTimer": "Death In: {DeathTimer}s", + "BerserkerKillCooldown": "Berserker kill cooldown", + "BerserkerMax": "Max level that Berserker can reach", + "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", + "WarHasImpostorVision": "War Has Impostor Vision", + "BerserkerCanVent": "Berserker Can Vent", + "WarCanVent": "War Can Vent", + "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", + "BerserkerOneKillCooldown": "Kill cooldown after unlocking", + "BerserkerTwoCanScavenger": "Unlock scavenged kills", + "BerserkerThreeCanBomber": "Unlock bombed kills", + "BerserkerFourCanNotKill": "Become War", + "BerserkerMaxReached": "Maximum level reached!", + "BerserkerLevelChanged": "Increased level to {0}", + "BerserkerLevelRequirement": "Level requirement for unlock", + "KilledByBerserker": "Killed by Berserker", + "BerserkerToWar": "You have become War!!!", + "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", + "WarKillCooldown": "War kill cooldown", + + "ImpCanBeUnlucky": "Impostors can become Unlucky", + "CrewCanBeUnlucky": "Crewmates can become Unlucky", + "NeutralCanBeUnlucky": "Neutrals can become Unlucky", + "BlackmailerSkillCooldown": "Blackmail Cooldown", + "BlackmailerMax": "Maximum times blackmailed players may speak", + "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", + "BlackmaileKillTitle": "BLACKMAILER", + "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", + "UnluckyKillSuicideChance": "Chance to suicide from killing", + "UnluckyVentSuicideChance": "Chance to suicide from venting", + "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", + "UnluckySabotageSuicideChance": "Chance to suicide from opening a door", + "ImpCanBeVoidBallot": "Impostors can become VoidBallot", + "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", + "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", + "ImpCanBeAware": "Impostors can become Aware", + "NeutralCanBeAware": "Neutrals can become Aware", + "CrewCanBeAware": "Crewmates can become Aware", + "AwareKnowRole": "Knows the role of player", + "AwareInteracted": "{0} tried to reveal your role.", + "AwareTitle": "AWARE MESSAGE", + "LighterVentButtonText": "Light", + "LighterSkillCooldown": "Light Cooldown", + "LighterSkillDuration": "Light Duration", + "LighterVisionNormal": "Increased Vision", + "LighterVisionOnLightsOut": "Increased Vision During Lights Out", + "LighterSkillInUse": "Ability in use", + "LighterSkillStop": "Ability expired", + "StealthDarkened": "Darkened: {0}", + "StealthExcludeImpostors": "Ignore Impostors when Blinding", + "StealthDarkenDuration": "Blinding Duration", + "PenguinAbductTimerLimit": "Dragging Time", + "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", + "PenguinKillButtonText": "Drag", + "PenguinTimerText": "Drag Timer", + "PenguinTargetOnCheckMurder": "You are grabbed, Try escape that first!", + "WitnessTime": "Max Time after killing where killer appears red", + "WitnessButtonText": "Examine", + "WitnessFoundInnocent": "✓", + "WitnessFoundKiller": "⚠", + "SwapperMax": "Maximum swaps", + "CanSwapSelfVotes": "Can exchange your own votes.", + "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", + "CantSwapSelf": "Can't exchange of one's own vote", + "SwapVote": "The votes of {0} and {1} were swapped!", + "SwapDead": "Sorry, you can't swap votes after death.", + "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", + "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", + "Swap1": "Swap target 1 selected", + "Swap2": "Swap target 2 selected", + "CancelSwap": "Cleared your previous swap!", + "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your target is dead.", + "Swap1=Swap2": "The target you input is same as Swap target 1.\nPls input a different one", + "SwapTitle": "SWAPPER", + "SwapperTryHideMsg": "Try to hide Swapper's command", + "SwapperPreResult": "Currently you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", + "ImpCanBeFragile": "Impostors can become Fragile", + "NeutralCanBeFragile": "Neutrals can become Fragile", + "CrewCanBeFragile": "Crewmates can become Fragile", + "ImpCanKillFragile": "Impostors can force kill Fragile", + "NeutralCanKillFragile": "Neutrals can force kill Fragile", + "CrewCanKillFragile": "Crewmates can force kill Fragile", + "FragileKillerLunge": "Killer lunges on kill", + "CrusaderSkillLimit": "Maximum Crusades", + "CrusaderSkillCooldown": "Crusade Cooldown", + "CrusaderKillButtonText": "Crusade", + "JailorKillButtonText": "Jail", + "AgitaterKillButtonText": "Pass", + "HasSerialKillerBuddy": "Has Serial Killer buddy", + "ChanceToSpawn": "Chance to spawn", + "ChanceToSpawnAnother": "Chance to spawn another", + "BloodlustKillCD": "Bloodlust Kill Cooldown", + "BloodlustPlayerCount": "Max players alive for Bloodlust", + "ReflectHarmfulInteractions": "Reflect harmful interactions", + + "ImpCanBeDiseased": "Impostors can become Diseased", + "NeutralCanBeDiseased": "Neutrals can become Diseased", + "CrewCanBeDiseased": "Crewmates can become Diseased", + "DiseasedCDOpt": "Increase the cooldown by", + "DiseasedCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeAntidote": "Impostors can become Antidote", + "NeutralCanBeAntidote": "Neutrals can become Antidote", + "CrewCanBeAntidote": "Crewmates can become Antidote", + "AntidoteCDOpt": "Decrease the cooldown by", + "AntidoteCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeStubborn": "Impostors can become Stubborn", + "NeutralCanBeStubborn": "Neutrals can become Stubborn", + "CrewCanBeStubborn": "Crewmates can become Stubborn", + + "ImpCanBeAvanger": "Impostors can become Avenger", + "NeutralCanBeAvanger": "Neutrals can become Avenger", + "CrewCanBeAvanger": "Crewmates can become Avenger", + "ImpCanBeSleuth": "Impostors can become Sleuth", + "CrewCanBeSleuth": "Crewmates can become Sleuth", + "NeutralCanBeSleuth": "Neutrals can become Sleuth", + "SleuthCanKnowKillerRole": "Can find the role of the killer", + "SleuthNoticeKiller": "\nThe killer's role is {0}.", + "SleuthNoticeVictim": "{0}'s role is {1}.", + "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", + "BomberDiesInExplosion": "Bomber dies in their explosion", + "ImpostorsSurviveBombs": "Impostors survive bombs", + "NukeRadius": "Nuke radius (12x is very large)", + "NukeCooldown": "Nuke Cooldown", + + "MasochistKillMax": "Amount of attacks needed to win", + "GuessMasochist": "You just tried to guess a Masochist!\nThey're now one step closer to winning!\n\nDo not guess them again.", + "MasochistKill": "You were attacked!", + "SelfGuessMasochist": "You can't self guess as a Masochist, you cheater!", + "GuessMasochistBlocked": "Masochist cannot guess due to self guessing.", + + "RememberCooldown": "Imitate Cooldown", + "RefugeeKillCD": "Refugee's Kill Cooldown", + "RememberedNeutralKiller": "You remembered you were a neutral killer!", + "RememberedMaverick": "You remembered you were a Maverick!", + "RememberedPursuer": "You remembered you were a Pursuer!", + "RememberedFollower": "You remembered you were a Follower!", + "RememberedAmnesiac": "You failed to remember your role.", + "RememberedImitator": "You remmebered you were an Imitator.", + "RememberedImpostor": "You remembered you were an Impostor!", + "RememberedCrewmate": "You remembered you were a crewmate!", + "ImitatorImitated": "An Imitator imitated your role!", + "ImitatorInvalidTarget": "Imitation failed", + "RememberButtonText": "Remember", + "ImitatorKillButtonText": "Imitate", + "IncompatibleNeutralMode": "If neutral is incompatible, turn into", + "RememberedYourRole": "An Amnesiac rmembered your role!", + "YouRememberedRole": "You remembered who you were!", + + "BanditStealMode": "Steal Mode", + "BanditStealMode_OnMeeting": "On Meeting", + "BanditStealMode_Instantly": "Instantly", + "BanditMaxSteals": "Maximum Steals", + "BanditCanStealBetrayalAddon": "Can Steal Betrayal Addons", + "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", + "NoStealableAddons": "Could not steal addon from the player", + "StealCooldown": "Steal cooldown", + + "DoppelMaxSteals": "Maximum Steals", + + "NecromancerRevengeTime": "Necromancy time", + "NecromancerRevenge": "You have {0}s to kill {1}", + "NecromancerSuccess": "Necromancy complete! You live to see another day.", + "NecromancerHide": "Venting is disabled, hide from the Necromancer!", + "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", + "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", + "RetributionistKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", + "RetributionistKillDead": "Choose a living player to kill.", + "RetributionistKillSucceed": "{0} was killed by the Retributionist!", + "RetributionistKillDisable": "You can't retribute until your tasks are done.", + "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", + "RetributionistCanKillNum": "Max retributions", + "RetributionistKillTooManyDead": "Too many players are dead, you can't retribute.", + "MinimumPlayersAliveToRetri": "Maximum players needed to block retributions", + "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", + "BakerChangeChances": "Chance that Baker turns into Famine", + "BakerChange": "The Baker has turned into Famine!\nThe bread is now poisoned.\nIf the Famine is not exiled, all players with poisoned bread die.", + "BakerChangeNow": "A player has poisoned bread!\nExile the Famine or that player dies.", + "BakerChangeNONE": "The Famine has not given out poisoned bread.\nNobody will die of poison, but you should still exile the Famine.", + "PanAliveMessageTitle": "BAKER ", + "PanAlive": "The Baker has given out bread.", + "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", + + "TwisterCooldown": "Twist Cooldown", + "TwisterButtonText": "Twist", + "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", + "InstigatorAbilityLimit": "Ability Use Count", + "InstigatorKillsPerAbilityUse": "Kills per Ability use", + + "CrewCanFindCaptain": "Crewmates can find Captain", + "MadmateCanFindCaptain": "Madmates can find Captain", + "ReducedSpeed": "Reduced speed", + "ReducedSpeedTime": "Time duration for reduced speed", + "CaptainCanTargetNB": "Captain can target Neutral Benign", + "CaptainCanTargetNE": "Captain can target Neutral Evil", + "CaptainCanTargetNC": "Captain can target Neutral Chaos", + "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", + "CaptainCanTargetNK": "Captain can target Neutral Killer", + "CaptainSpeedReduced": "Captain reduced your speed", + "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", + "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", + + "InspectorTryHideMsg": "Hide Inspector's commands", + "MaxInspectCheckLimit": "Max inspections per game", + "InspectCheckLimitPerMeeting": "Max inspections per meeting", + "InspectCheckTargetKnow": "Targets know they were checked by Inspector", + "InspectCheckOtherTargetKnow": "Targets know who they were checked with", + "InspectorDead": "You can not use your power after death", + "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", + "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", + "InspectCheckSelf": "HA!! you thought it would be this easy. You can not check yourself", + "InspectCheckReveal": "HA! you thought it would be this easy. You can not check a role that is revealed", + "InspectCheckTitle": "INSPECTOR ", + "InspectCheckTrue": "{0} and {1} are in the same team!", + "InspectCheckFalse": "{0} and {1} are NOT in the same team!", + "InspectCheckTargetMsg": " were checked by Inspector.", + "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "InspectCheckNull": "Please select an ID of a living player to check their team", + "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", + "InspectCheckRevealTarget": "When tasks done, target knows team of other target", + "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", + + "EgoistCountMode.Original": "Original", + "EgoistCountMode.Neutral": "Neutral", + + "JailerJailCooldown": "Jail cooldown", + "JailerMaxExecution": "Maximum executions", + "JailerNBCanBeExe": "Can execute Neutral Benign", + "JailerNCCanBeExe": "Can execute Neutral Chaos", + "JailerNECanBeExe": "Can execute Neutral Evil", + "JailerNKCanBeExe": "Can execute Neutral Killing", + "JailerNACanBeExe": "Can execute Neutral Apocalypse", + "JailerCKCanBeExe": "Can execute Crew Killing", + "JailerTargetAlreadySelected": "You have already selected a target", + "SuccessfullyJailed": "Target successfully jailed", + "CantGuessJailed": "You can not guess the target", + "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", + "CanNotTrialJailed": "You can not trial the target.", + "notifyJailedOnMeeting": "Notify jailed player when meeting starts", + "JailedNotifyMsg": "You have been locked up in jail by the Jailer. No one can guess or judge you and you can only guess Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", + "JailerTitle": "Jailer", + + "CopyCatCopyCooldown": "Copy cooldown", + "CopyCatRoleChange": "Your role has been changed to {0}", + "CopyCatCanNotCopy": "You can not copy target's role", + "CopyButtonText": "Copy", + "CopyCrewVar": "Can copy evil variants of crew roles", + "CopyTeamChangingAddon": "Can copy team changing addon", + + "MaxCleanserUses": "Max cleanses", + "CleansedCanGetAddon": "Cleansed player can get Add-on", + "CleanserTitle": "CLEANSER", + "CleanserRemoveSelf": "You can not cleanse yourself", + "CleanserCantRemove": "Oops! the player can not be cleansed.", + "CleanserRemovedRole": "{0} has been cleansed. All their Addons will be removed after the meeting.", + "LostAddonByCleanser": "All your Addons were removed by the cleanser", + + "MaxProtections": "Max protections", + "KeeperHideVote": "Hide Keeper's vote", + "KeeperProtect": "You chose to protect {0}, your vote has been returned", + "KeeperTitle": "Keeper", + + "MaulRadius": "Maul Radius", + "ImpCanBeAutopsy": "Impostors can become Autopsy", + "CrewCanBeAutopsy": "Crewmates can become Autopsy", + "NeutralCanBeAutopsy": "Neutrals can become Autopsy", + "ImpCanBeCyber": "Impostors can become Cyber", + "CrewCanBeCyber": "Crewmates can become Cyber", + "NeutralCanBeCyber": "Neutrals can become Cyber", + "ImpKnowCyberDead": "Impostors know if Cyber died", + "CrewKnowCyberDead": "Crewmates know if Cyber died", + "NeutralKnowCyberDead": "Neutrals know if Cyber died", + "CyberKnown": "Everyone can see Cyber", + "ImpCanBeInfluenced": "Impostors can become Influenced", + "CrewCanBeInfluenced": "Crewmates can become Influenced", + "NeutralCanBeInfluenced": "Neutrals can become Influenced", + "ImpCanBeBewilder": "Impostors can become Bewilder", + "CrewCanBeBewilder": "Crewmates can become Bewilder", + "NeutralCanBeBewilder": "Neutrals can become Bewilder", + "KillerGetBewilderVision": "Killer gets Bewilder's vision", + "ImpCanBeOiiai": "Impostors can be OIIAI", + "CrewCanBeOiiai": "Crewmates can be OIIAI", + "NeutralCanBeOiiai": "Neutrals can be OIIAI", + "OiiaiCanPassOn": "OIIAI can pass on to the killer", + "NeutralChangeRolesForOiiai": "Neutrals turns to ", + "LostRoleByOiiai": "You got erased by OIIAI!", + "ImpCanBeSunglasses": "Impostors can become Sunglasses", + "CrewCanBeSunglasses": "Crewmates can become Sunglasses", + "NeutralCanBeSunglasses": "Neutrals can become Sunglasses", + "SunglassesVision": "Sunglasses Vision", + "ImpCanBeLoyal": "Impostors can become Loyal", + "CrewCanBeLoyal": "Crewmates can become Loyal", + "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", + "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", + "SheriffCanBeMadmate": "Sheriff can become Madmate", + "MayorCanBeMadmate": "Mayor can become Madmate", + "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", + "SnitchCanBeMadmate": "Snitch can become Madmate", + "JudgeCanBeMadmate": "Judge can become Madmate", + "MarshallCanBeMadmate": "Marshall can become Madmate", + "GanRetributionistCanBeMadmate": "Retributionist can be converted", + "RetributionistCanBeMadmate": "Retributionist can become Madmate", + "OverseerCanBeMadmate": "Overseer can become Madmate", + "GanSheriffCanBeMadmate": "Sheriff can be converted", + "GanMayorCanBeMadmate": "Mayor can be converted", + "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", + "GanJudgeCanBeMadmate": "Judge can be converted", + "GanMarshallCanBeMadmate": "Marshall can be converted", + "GanOverseerCanBeMadmate": "Overseer can be converted", + "RascalAppearAsMadmate": "Appear As Madmate On Ejection", + "CouncillorDead": "Sorry, you can't murder from the dead.", + "CouncillorMurderMax": "Sorry, you've reached the maximum amount of murders for the meeting.", + "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", + "Councillor_MurderKill": "{0} was judged.", + "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Councillor_MurderNull": "Please choose a living player to murder.", + "Councillor_MurderKillTitle": "COURT ", + "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", + "CouncillorCanMurderMadmate": "Can Murder Madmates", + "CouncillorCanMurderImpostor": "Can Murder Impostors", + "CouncillorTryHideMsg": "Try to hide Councillor's commands", + "DazzlerDazzled": "You were dazzled by the Dazzler!", + "DazzlerCauseVision": "Reduced vision", + "DazzlerDazzleLimit": "Max number of players affected by reduced vision", + "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", + "DazzleCooldown": "Dazzle Cooldown", + "DazzleButtonText": "Dazzle", + + "MoleVentButtonText": "Dig", + "MoleVentCooldown": "Dig cooldown", + + "AddictVentButtonText": "Get Fix", + "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", + "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerble", + + "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", + "AlchemistShieldDur": "Resistance Potion Duration", + "AlchemistInvisDur": "Invisibility Potion Duration", + "AlchemistVision": "Night Vision", + "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", + "AlchemistVisionDur": "Night Vision Potion Duration", + "AlchemistSpeed": "Speed Potion Boost", + "AlchemistVentButtonText": "Drink", + "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", + "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", + "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", + "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", + "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", + "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", + "AlchemistGotBloodlustPotion": "Potion of Harming: Kill the next player you touch", + "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", + "NoPotion": "You have no potions", + + "StoreShield": "Potion of Resistance", + "StoreSuicide": "Potion of Poison", + "StoreTP": "Potion of Warping", + "StoreSP": "Potion Of Speed", + "StoreQF": "Potion of Fixing", + "StoreBL": "Potion of Harming", + "StoreNS": "Potion of Night Vision", + "StoreNull": "None", + "PotionStore": "Potion in store: ", + "WaitQFPotion": "\nPotion of Fixing waiting for use", + + "AlchemistShielded": "Potion of Resistance started", + "AlchemistHasVision": "Potion of Night Vision started", + "AlchemistShieldOut": "Potion of Resistance ended", + "AlchemistVisionOut": "Potion of Night Vision ended", + "AlchemistPotionBloodlust": "You gained bloodlust", + "AlchemistHasSpeed": "Potion Of Speed started", + "AlchemistSpeedOut": "Potion Of Speed ended", + + "DeathpactDuration": "Death Pact duration", + "DeathPactCooldown": "Death Pact Assign Cooldown", + "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", + "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", + "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", + "DeathpactVisionWhileInPact": "Vision for players in Death Pact", + "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", + "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", + "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", + "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", + "DeathpactComplete": "Death Pact was concluded.", + "DeathpactExecuted": "Death Pact was executed.", + "DeathpactAverted": "Death Pact was averted.", + "DeathpactButtonText": "Assign", + "DevourerHideNameConsumed": "Hide the names of consumed players", + "DevourCooldown": "Devour Cooldown", + "DevourerButtonText": "Devour", + "EatenByDevourer": "Your skin was eaten by the Devourer", + "DevourerEatenSkin": "Target skin eaten", + "DevouredName": "Devoured", + "PitfallTrapCooldown": "Trap Cooldown", + "PitfallMaxTrapCount": "Number of Traps that can be set", + "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", + "PitfallTrapDuration": "Time the Trap remains active", + "PitfallTrapRadius": "Trap Radius", + "PitfallTrapFreezeTime": "Trap freeze time", + "PitfallTrapCauseVision": "Trap caused vision", + "PitfallTrapCauseVisionTime": "Trap caused vision time", + "PitfallTrap": "You have fallen into a trap!", + "ConsigliereDivinationMaxCount": "Maximum Reveals", + "RitualMaxCount": "Maximum Reveals", + "CleanserHideVote": "Hide Cleanser's vote", + "OracleSkillLimit": "Maximum Uses", + "OracleHideVote": "Hide Oracle's vote", + "OracleCheckReachLimit": "You're out of uses!", + "OracleCheckSelfMsg": "You can't even trust yourself, huh?", + "OracleCheckLimit": "Reminder: You have {0} uses left", + "OracleCheckMsgTitle": "ORACLE ", + "OracleCheck.NotCrewmate": "Appears to not be a crewmate", + "OracleCheck.Crewmate": "Appears to be a crewmate", + "OracleCheck.Neutral": "Appears to be a neutral", + "OracleCheck.Impostor": "Appears to be an Impostor", + "OracleCheck": "Target Results:", + "FailChance": "Chance of showing incorrect result", + "OracleCheckAddons": "Oracle checks add-ons", + "ChameleonCanVent": "Vent to disguise", + "ChameleonInvisState": "You are disguising!", + "ChameleonInvisStateOut": "Your disguise ended", + "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", + "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", + "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", + "ChameleonCooldown": "Disguise Cooldown", + "ChameleonDuration": "Disguise Duration", + "ChameleonRevertDisguise": "Expose", + "ChameleonDisguise": "Disguise", + "KillCooldownAfterCleaning": "Kill Cooldown On Clean", + "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", + "MedusaStoneBody": "Body stoned", + "MedusaReportButtonText": "Stone", + + "CursedSoulCurseCooldown": "Soul Snatch Cooldown", + "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", + "CursedSoulCurseMax": "Maximum Soul Snatches", + "CursedSoulKnowTargetRole": "Know the roles of Soulless players", + "CursedSoulCanCurseNeutral": "Neutral roles have souls", + "CursedSoulKillButtonText": "Snatch", + "SoullessByCursedSoul": "Your soul was snatched by a Cursed Soul", + "CursedSoulSoullessPlayer": "Soul snatched", + "CursedSoulInvalidTarget": "No soul found", + + "AdmireCooldown": "Admire Cooldown", + "AdmirerKnowTargetRole": "Know the roles of Admired players", + "AdmirerSkillLimit": "Skill Limit", + "AdmireButtonText": "Admire", + "AdmirerAdmired": "The Admirer admired you!", + "AdmiredPlayer": "Player admired", + "AdmirerInvalidTarget": "Target cannot be admired", + + "SpiritualistNoticeTitle": "SPIRITUALIST ", + "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", + "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", + "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", + "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", + "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", + "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", + "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", + "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", + "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", + "EnigmaClueHat1": "The Killer wears a Hat!", + "EnigmaClueHat2": "The Killer does not wear a Hat!", + "EnigmaClueHat3": "The Killer wears {0} as a Hat!", + "EnigmaClueSkin1": "The Killer wears a Skin!", + "EnigmaClueSkin2": "The Killer does not wear a Skin!", + "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", + "EnigmaClueVisor1": "The Killer wears a Visor!", + "EnigmaClueVisor2": "The Killer does not wear a Visor!", + "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", + "EnigmaCluePet1": "The Killer does have a Pet!", + "EnigmaCluePet2": "The Killer does not have a Pet!", + "EnigmaCluePet3": "The Killer has {0} as a Pet!", + "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", + "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", + "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", + "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", + "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", + "EnigmaClueColor1": "The Killer has a light color!", + "EnigmaClueColor2": "The Killer has a dark color!", + "EnigmaClueColor3": "The Killer's color is {0}!", + "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", + "EnigmaClueStatus1": "The Killer is currently inside a Vent!", + "EnigmaClueStatus2": "The Killer is currently on a Ladder!", + "EnigmaClueStatus3": "The Killer is already Dead!", + "EnigmaClueStatus4": "The Killer is still Alive!", + "EnigmaClueRole1": "The Killer is an Impostor!", + "EnigmaClueRole2": "The Killer is a Neutral!", + "EnigmaClueRole3": "The Killer is a Crewmate!", + "EnigmaClueRole4": "The Killer's Role is {0}!", + "EnigmaClueLevel1": "The Killer's Level is above 50!", + "EnigmaClueLevel2": "The Killer's Level is below 50!", + "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", + "EnigmaClueLevel4": "The Killer's Level is {0}!", + "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", + "EnigmaClueHatTitle": "Enigma Hat Clue!", + "EnigmaClueVisorTitle": "Enigma Visor Clue!", + "EnigmaClueSkinTitle": "Enigma Skin Clue!", + "EnigmaCluePetTitle": "Enigma Pet Clue!", + "EnigmaClueNameTitle": "Enigma Name Clue!", + "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", + "EnigmaClueColorTitle": "Enigma Color Clue!", + "EnigmaClueLocationTitle": "Enigma Location Clue!", + "EnigmaClueStatusTitle": "Enigma Status Clue!", + "EnigmaClueRoleTitle": "Enigma Role Clue!", + "EnigmaClueLevelTitle": "Enigma Level Clue!", + "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", + + "ChiefOfPoliceSkillCooldown": "Cooldown for recruiting sheriffs", + "PolicCanImpostorAndNeutarl": "You can recruit Impostor or Kill Neutral to become sheriffs", + "SheriffSuccessfullyRecruited": "You recruited a sheriff.", + "BeSheriffByPolice": "You've been recruited by the police chief! Serve the crew!", + "ChiefOfPoliceKillButtonText": "Recruitment", + "VotesPerKill": "Votes gained for each kill", + "PickpocketGetVote": "You've got {0} votes", + "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "VultureNumberOfReportsToWin": "Bodies needed to win", + "VultureReportBody": "Body eaten!", + "VultureEatButtonText": "Consume", + "VultureReportCooldown": "Eat Cooldown", + "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", + "VultureCooldownUp": "Eat Cooldown finished", + + "TasksMarkPerRound": "Number of tasks that can be marked in one round", + "TaskinatorBombPlanted": "Bomb has been planted", + + "ShieldDuration": "Shield duration", + "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", + "BenefactorTaskMarked": "Task marked successfully", + "BenefactorTargetGotShield": "You got shield by Benefactor", + + "PirateTryHideMsg": "Hide Pirate's commands", + "SuccessfulDuelsToWin": "Number of successful duels needed to win", + "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the duel if you choose the same option as the target", + "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", + "PirateTitle": "PIRATE ", + "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", + "PirateDead": "Ye be dead. Ye cannot duel anymore.", + "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", + "DuelDone": "Ye 'ave chosen yer option fer the duel.\nWait fer the meetin' to end to see the result.", + "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", + "PirateDuelButtonText": "Duel", + "DuelCooldown": "Duel Cooldown", + "Rock": "Rock", + "Paper": "Paper", + "Scissors": "Scissors", + "Heads": "Heads", + "Tails": "Tails", + "SpyRedNameDur": "Colored Name Duration", + "SpyInteractionBlocked": "Block kill button interaction", + "AgitaterBombCooldown": "Agitator bomb cooldown", + "AgitaterPassCooldown": "Bomb pass cooldown", + "BombExplodeCooldown": "Bomb explode cooldown", + "AgitaterPassNotify": "Bomb successfully passed", + "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", + "AgitaterCanGetBombed": "Agitator can get bomb", + "AgitaterAutoReportBait": "Agitator Auto Report Bait", + + "SeekerPointsToWin": "Number of points required to win", + "SeekerTagCooldown": "Tag Cooldown", + "SeekerNotify": "Your target is {0}", + "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", + + "PixiePointsToWin": "Number of points required to win", + "MaxTargets": "Maximum number of targets per round", + "MarkCooldown": "Mark cooldown", + "PixieSuicide": "Pixie suicides if target is not voted out", + "PixieMaxTargetReached": "You have already selected all the targets this round", + "PixieTargetAlreadySelected": "Target is already selected", + "PixieButtonText": "Mark", + + "PlagueBearerCD": "Plague cooldown", + "PestilenceCD": "Pestilence Kill cooldown", + "PlagueBearerAlreadyPlagued": "Player has already been plagued", + "PlagueBearerToPestilence": "You have turned into Pestilence!!", + "PestilenceCanVent": "Pestilence Can Vent", + "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", + "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", + "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", + "RomanticBetCooldown": "Pick Partner Cooldown", + "RomanticProtectCooldown": "Protect Cooldown", + "RomanticBetPlayer": "You picked your partner", + "RomanticBetOnYou": "The Romantic chose you as their Partner!", + "VengefulKCD": "Vengeful Romantic Kill Cooldown", + "VengefulCanVent": "Vengeful Romantic Can Vent", + "RuthlessKCD": "Ruthless Romantic Kill Cooldown", + "RuthlessCanVent": "Ruthless Romantic Can Vent", + "RomanticProtectPartner": "Your partner is under protection", + "RomanticIsProtectingYou": "The Romantic is protecting you", + "ProtectingOver": "Shield expired", + "RomanticProtectDuration": "Protect Duration", + "RomanticKnowTargetRole": "Romantic knows their target's role", + "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", + + "GuessMasterMisguess": "{0} misguessed", + "GuessMasterTargetRole": "Someone tried to guess {0}", + "GuessMasterTitle": "Guess Master ", + + "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", + "DCanGuessImpostors": "Can Guess Impostors", + "DCanGuessCrewmates": "Can Guess Crewmates", + "DCanGuessNeutrals": "Can Guess Neutrals", + "DCanGuessAdt": "Can Guess Add-Ons", + "DoomsayerAdvancedSettings": "Advanced Settings", + "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", + "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", + "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", + "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", + "DoomsayerTryHideMsg": "Hide Doomsayer's commands", + "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", + "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", + "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", + "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", + "DoomsayerGuessCountTitle": "DOOMSAYER", + "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", + + "EveryoneCanKnowMini": "Everyone can see the Mini", + "CanBeEvil": "Mini can be an Impostor", + "EvilMiniSpawnChances": "Probability of Mini being an Impostor", + "GuessMini": "Sorry, you can't hurt a kid Mini.", + "GrowUpDuration": "Time required to grow (s)", + "MajorCooldown": "Kill Cooldown when over 18", + "UpDateAge": "Display age change in real-time", + "Cantkillkid": "You can't kill a Mini that hasn't grown up.", + "CantEat": "You can't eat a Mini that hasn't grown up", + "CantShroud": "You can't control a Mini that hasn't grown up.", + "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", + "CantRecruit": "You can't recruit a Mini that hasn't grown up.", + "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", + "MiniUp": "You're a year older!", + "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilty while you can no longer guess.\nYou can guess again after you have grown up.", + "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", + "CountMeetingTime": "Meeting time can continue to grow", + "YouKillRandomizer1": "You kill Randomizer, Self report!", + "YouKillRandomizer2": "You kill Randomizer, Cannot move!", + "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", + "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", + "MadmateCanBeHurried": "Madmate can be Hurried on game start", + "TaskBasedCrewCanBeHurried": "Task based Crews can be Hurried", + "HurriedCanBeConverted": "Hurried can be recruited in game (excludes madmate)", + "Developer": "Developer", + "Sponsor": "Sponsor", + "Booster": "Server Booster", + "Translator": "Translator", + "NoAccess": "Unauthorized Access!!!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", + "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", + "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", + "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", + "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", + "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", + "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", + "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", + "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", + "DCNotify.Inactivity": "The lobby closed due to inactivity.", + "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", + "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", + "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", + "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", + "RoleType.VanillaRoles": "★ Vanilla Roles", + "RoleType.ImpKilling": "★ Impostor Killing Roles", + "RoleType.ImpSupport": "★ Impostor Support Roles", + "RoleType.ImpConcealing": "★ Impostor Concealing Roles", + "RoleType.ImpHindering": "★ Impostor Hindering Roles", + "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", + "RoleType.Madmate": "★ Madmate Roles", + "RoleType.CrewSupport": "★ Crewmate Support Roles", + "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", + "RoleType.CrewPower": "★ Crewmate Power Roles", + "RoleType.CrewKilling": "★ Crewmate Killing Roles", + "RoleType.CrewBasic": "★ Crewmate Basic Roles", + "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", + "RoleType.NeutralEvil": "★ Neutral Evil Roles", + "RoleType.NeutralBenign": "★ Neutral Benign Roles", + "RoleType.NeutralChaos": "★ Neutral Chaos Roles", + "RoleType.NeutralKilling": "★ Neutral Killing Roles", + "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", + "RoleType.Harmful": "★ Harmful Add-ons", + "RoleType.Support": "★ Supportive Add-ons", + "RoleType.Helpful": "★ Helpful Add-ons", + "RoleType.Mixed": "★ Mixed Add-ons", + "RoleType.Misc": "★ Miscellaneous Add-ons", + "RoleType.Impostor": "★ Impostor Add-ons", + "RoleType.Neut": "★ Neutral Add-ons", + "SubType.Impostor": "★ Impostors", + "SubType.Shapeshifter": "★ Shapeshifters", + "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", + "SubType.Madmate": "★ Madmates", + "SubType.CrewmateKilling": "★ Crewmate Killings", + "SubType.Crewmate": "★ Regular Crewmates", + "SubType.New": "★ New!", + "CrewmateRoles": "★ Crewmate Roles ★", + "ImpostorRoles": "★ Impostor Roles ★", + "NeutralRoles": "★ Neutral Roles ★", + "AddonRoles": "★ Add-ons ★", + "WinnerRoleText.Impostor": "Impostors Win!", + "WinnerRoleText.Crewmate": "Crewmates Win!", + "WinnerRoleText.Apocalypse": "Apocalypse Wins!", + "WinnerRoleText.Terrorist": "Terrorist Wins!", + "WinnerRoleText.Jester": "Jester Wins!", + "WinnerRoleText.Lovers": "Lovers Win!", + "WinnerRoleText.Executioner": "Executioner Wins!", + "WinnerRoleText.Arsonist": "Arsonist Wins!", + "WinnerRoleText.Revolutionist": "Revolutionist Wins!", + "WinnerRoleText.Jackal": "Jackals Win!", + "WinnerRoleText.God": "God Wins!", + "WinnerRoleText.Vector": "Vector Wins!", + "WinnerRoleText.Innocent": "Innocent Wins!", + "WinnerRoleText.Pelican": "Pelican Wins!", + "WinnerRoleText.Youtuber": "YouTuber Wins!", + "WinnerRoleText.Necromancer": "Necromancer Wins!", + "WinnerRoleText.Egoist": "Egoists Win!", + "WinnerRoleText.Demon": "Demon Wins!", + "WinnerRoleText.Stalker": "Stalker Wins!", + "WinnerRoleText.Workaholic": "Workaholic Wins!", + "WinnerRoleText.Collector": "Collector Wins!", + "WinnerRoleText.BloodKnight": "Blood Knight Wins!", + "WinnerRoleText.Poisoner": "Poisoner Wins!", + "WinnerRoleText.Huntsman": "Huntsman Wins!", + "WinnerRoleText.HexMaster": "Hex Master Wins!", + "WinnerRoleText.Cultist": "Cultist Wins!", + "WinnerRoleText.Wraith": "Wraith Wins!", + "WinnerRoleText.SerialKiller": "Serial Killers Win!", + "WinnerRoleText.Juggernaut": "Juggernaut Wins!", + "WinnerRoleText.Infectious": "Infectious Wins!", + "WinnerRoleText.Virus": "Virus Wins!", + "WinnerRoleText.Phantom": "Phantom Wins!", + "WinnerRoleText.Jinx": "Jinx Wins!", + "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", + "WinnerRoleText.PotionMaster": "Potion Master Wins!", + "WinnerRoleText.Pickpocket": "Pickpocket Wins!", + "WinnerRoleText.Traitor": "Traitor Wins!", + "WinnerRoleText.Vulture": "Vulture Wins!", + "WinnerRoleText.Medusa": "Medusa Wins!", + "WinnerRoleText.Famine": "Famine Wins!", + "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", + "WinnerRoleText.Glitch": "Glitch Wins!", + "WinnerRoleText.Pestilence": "Pestilence Wins!", + "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", + "WinnerRoleText.Masochist": "Masochist Wins!", + "WinnerRoleText.Doomsayer": "Doomsayer Wins!", + "WinnerRoleText.Pirate": "Pirate Wins!", + "WinnerRoleText.Shroud": "Shroud Wins!", + "WinnerRoleText.Werewolf": "Werewolf Wins!", + "WinnerRoleText.Seeker": "Seeker Wins!", + "WinnerRoleText.Agitater": "Agitator Wins!", + "WinnerRoleText.Occultist": "Occultist Wins!", + "WinnerRoleText.SoulCollector": "Soul Collector Wins!", + "WinnerRoleText.NiceMini": "Nice Mini Wins!", + "WinnerRoleText.Mini": "Nice Mini was killed", + "WinnerRoleText.Bandit": "Bandit Wins!", + "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", + "WinnerRoleText.Solsticer": "Solsticer Wins!", + "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", + "WinnerRoleText.Doppelganger": "Doppelganger Wins!", + "AdditionalWinnerRoleText.Sidekick": "Sidekick", + "AdditionalWinnerRoleText.Taskinator": "Taskinator", + "AdditionalWinnerRoleText.Opportunist": "Opportunist", + "AdditionalWinnerRoleText.Lawyer": "Lawyer", + "AdditionalWinnerRoleText.Hater": "Hater", + "AdditionalWinnerRoleText.Provocateur": "Provocateur", + "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", + "AdditionalWinnerRoleText.Follower": "Follower", + "AdditionalWinnerRoleText.Pursuer": "Pursuer", + "AdditionalWinnerRoleText.Jester": "Jester", + "AdditionalWinnerRoleText.Lovers": "Lovers", + "AdditionalWinnerRoleText.Executioner": "Executioner", + "AdditionalWinnerRoleText.Phantom": "Phantom", + "AdditionalWinnerRoleText.Maverick": "Maverick", + "AdditionalWinnerRoleText.Shaman": "Shaman", + "AdditionalWinnerRoleText.Pixie": "Pixie", + "AdditionalWinnerRoleText.NiceMini": "Nice Mini", + "AdditionalWinnerRoleText.Romantic": "Romantic", + "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", + "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", + "ErrorEndText": "An error occurred", + "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", + "ForceEnd": "Aborted", + "EveryoneDied": "Everyone died", + "ForceEndText": "Host has aborted the game", + "NiceMiniDied": "Nice Mini was killed", + "HaterMisFireKillTarget": "Hater kills target when misfire", + "HaterChooseConverted": "Select addons that Hater can kill", + "HaterCanKillMadmate": "Can kill madmate", + "HaterCanKillCharmed": "Can kill charmed", + "HaterCanKillLovers": "Can kill lovers", + "HaterCanKillSidekick": "Can kill jackal team", + "HaterCanKillEgoist": "Can kill egoist", + "HaterCanKillInfected": "Can kill infected team", + "HaterCanKillContagious": "Can kill virus team", + "HaterCanKillAdmired": "Can kill admirer", + "AutoMuteUs": "Enable it if you use AutoMuteUs", + "HorseMode": "Enable to become a horse", + "LongMode": "Enable to have a long neck", + "InfluencedChangeVote": "oops!You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", + + + "FFA": "Free For All", + "ModeFFA": "Gamemode: FFA", + "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the mean time!", + "FFA_GameTime": "Maximum Game Length", + "FFA_KCD": "Kill Cooldown", + "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", + "FFA_EnableRandomAbilities": "Enable Random Events", + "FFA_ShieldDuration": "Shield Duration", + "FFA_IncreasedSpeed": "Increased Speed", + "FFA_DecreasedSpeed": "Decreased Speed", + "FFA_ModifiedSpeedDuration": "Modified Speed Duration", + "FFA_LowerVision": "Lowered Vision", + "FFA_ModifiedVisionDuration": "Lowered Vision Duration", + "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", + "FFA-Event-GetShield": "You have a temporary shield!", + "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", + "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", + "FFA-Event-GetHighKCD": "You got a higher kill cooldown", + "FFA-Event-GetLowVision": "You have lower vision temporarily", + "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", + "FFA-Event-GetTP": "You got teleported to a random vent!", + "FFA-Event-RandomTP": "Everyone was swapped with someone", + "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", + "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", + "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", + "FFA_TargetIsShielded": "The player you tried to kill is shielded!", + "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", + "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", + "Killer": "FREE FOR ALL", + "KillerInfo": "Kill Everyone to Win", + + "Hide&SeekTOHE": "Hide & Seek", + "MenuTitle.Hide&Seek": "Hide & Seek Settings", + "NumImpostorsHnS": "Num Impostors", + + "EveryOneKnowSolsticer": "Every One Know who is Solsticer", + "SolsticerKnowItsKiller": "Solsticer knows the role of whom used kill button on it", + "SolsticerSpeed": "Movement speed of Solsticer", + "SolsticerRemainingTaskWarned": "Remaining tasks to be known", + "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", + "SolsticerMurdered": "{0} attempted to murder you!", + "MurderSolsticer": "You stopped Solsticer this round!", + "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", + "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", + "SolsticerTitle": "Solsticer", + "GuessSolsticer": "Sorry, but you can not guess Solsticer!", + "VoteSolsticer": "Sorry, but you can not vote Solsticer!", + "SolsticerTasksReset": "Your tasks get reset!", + "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", + "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", + + "VoteDead": "The player you voted for was exhiled before the meeting concluded. Your vote was rescinded.", + + "ImpCanBeSilent": "Impostors can become Silent", + "CrewCanBeSilent": "Crewmates can become Silent", + "NeutralCanBeSilent": "Neutrals can become Silent", + "LastMessageReplay": "Last System Message Replay", + "Contributor": "Contributor", + + "dbConnect.InitFailure": "Error while connecting to TOHE api, pls check your network connection and retry login!", + "dbConnect.nullFriendCode": "This build of TOHE is not aviliable to users with no friendcode!", + + "ImpCanBeSusceptible": "Impostors can become Susceptible", + "CrewCanBeSusceptible": "Crewmates can become Susceptible", + "NeutralCanBeSusceptible": "Neutrals can become Susceptible", + + "Quizmaster": "Quizmaster", + "QuizmasterInfo": "Quiz people to kill them in meetings", + "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will \"?!\" next to their name. If the player answers a question wrongly, or doesn't answer, they will die. If the Quizmaster was killed/ejected in the same meeting, the player will live.\nThe Quizmaster cannot mark multiple people in the same round", + "QuizmasterKillButtonText": "Quiz", + + "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", + "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", + "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", + "QuizmasterChat.CorrectTarget": "Correct", + "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", + "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", + "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", + "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", + "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", + "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die", + "QuizmasterChat.Title": "Quizmaster Information", + "QuizmasterChat.CantAnswer": "As the quizmaster you can't answer questions", + "QuizmasterChat.AnswerNotValid": "Your answer must be A, B or C", + "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", + + "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", + "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", + "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", + "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", + "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", + + "Quizmaster.None": "None", + + "QuizmasterSabotages.Lights": "Lights", + "QuizmasterSabotages.Reactor": "Reactor", + "QuizmasterSabotages.Communications": "Communications", + "QuizmasterSabotages.O2": "O2", + "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", + "QuizmasterAnswers.One": "One", + "QuizmasterAnswers.Two": "Two", + "QuizmasterAnswers.Three": "Three", + "QuizmasterAnswers.Four": "Four", + "QuizmasterAnswers.Five": "Five", + "QuizmasterAnswers.Pacifist": "Pacifist", + "QuizmasterAnswers.Vampire": "Vampire", + "QuizmasterAnswers.Snitch": "Snitch", + "QuizmasterAnswers.Vigilante": "Vigilante", + "QuizmasterAnswers.Jackal": "Jackal", + "QuizmasterAnswers.Mole": "Mole", + "QuizmasterAnswers.Sniper": "Sniper", + "QuizmasterAnswers.Coven": "Coven", + "QuizmasterAnswers.Sabotuer": "Sabotuer", + "QuizmasterAnswers.Sorcerers": "Sorcerers", + "QuizmasterAnswers.Killer": "Killer", + "QuizmasterAnswers.Edition": "Edition", + "QuizmasterAnswers.Experimental": "Experimental", + "QuizmasterAnswers.Enhanced": "Enhanced", + "QuizmasterAnswers.Edited": "Edited", + + "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", + "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", + "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", + "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", + "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called last meeting before this meeting?", + "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", + "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", + "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", + "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", + "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed an update later?", + "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", + "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", + "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", + "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", + "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", + "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", + "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", + + "DeathReason.WrongAnswer": "Wrong Quiz Answer", + + "TPCooldown": "Teleport Cooldown", + "RiftsTooClose": "Location too close to the first rift", + "RiftCreated": "Rift made successfully", + "RiftsDestroyed": "All rifts Destroyed", + "RiftRadius": "Rift Radius", + + "TiredVision": "Vision When Tired", + "TiredSpeed": "Speed When Tired", + "TiredDur": "Tired Duration", + "ImpCanBeTired": "Impostors can become Tired", + "CrewCanBeTired": "Crewmates can become Tired", + "NeutralCanBeTired": "Neutrals can become Tired", + + "TiredNotify": "Zzz..", + + "PlagueDoctorInfectLimit": "Infect Limit", + "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", + "PlagueDoctorInfectTime": "Infect Time", + "PlagueDoctorInfectDistance": "Infect Distance", + "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", + "PlagueDoctorCanInfectSelf": "Can Infect Self", + "PlagueDoctorCanInfectVent": "Can Infect While In Vent", + "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", + + "StatueSlow": "Statue Slowness", + "StatuePeopleToSlow": "People Needed To Slow", + + "ImpCanBeStatue": "Impostors can become Statue", + "CrewCanBeStatue": "Crewmates can become Statue", + "NeutralCanBeStatue": "Neutrals can become Statue", + + "WardenIncreaseSpeed": "Increase Speed By", + "WardenWarn": "DANGER! RUN!", + + "MinionAbilityTime": "Ability Duration" } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 4e51e49829..ab7548a22a 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -28,7 +28,7 @@ public static void SetupCustomOption() SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulCollector, 1, zeroOne: false); SoulCollectorPointsOpt = IntegerOptionItem.Create(Id + 10, "SoulCollectorPointsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]) .SetValueFormat(OptionFormat.Times); - CollectOwnSoulOpt = BooleanOptionItem.Create(Id + 11, "CollectOwnSoulOpt", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); + CollectOwnSoulOpt = BooleanOptionItem.Create(Id + 11, "SoulCollector_CollectOwnSoulOpt", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); CallMeetingIfDeath = BooleanOptionItem.Create(Id + 12, "CallMeetingIfDeath", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); /*GetPassiveSouls = BooleanOptionItem.Create(Id + 13, "GetPassiveSouls", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]);*/ } From a1eb2f1d1299391f45e8fb8985e2b2d8aeee6e27 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 14 Apr 2024 16:22:47 -0400 Subject: [PATCH 003/778] removed call meeting if death setting causes too many issues, it can call a meeting during the ejection screen and that will just mess with every client that isnt the host if it could be implemented where it doesnt, then it could be readded, but i have no idea how to do that --- Resources/Lang/en_US.json | 5 +++-- Roles/Neutral/SoulCollector.cs | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 5b469bdb0c..f1ba372498 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -849,7 +849,7 @@ "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence you will become immortal and gain the ability to kill.\nIn addition to this, after turning into Pestilence you will kill anyone who tries to kill you.\n\nTo win, turn into Pestilence and kill everyone.", "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected back towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone the meeting after you transform.", "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you vote players to predict their death. If the prediction is correct and the target dies in the next round you collect their soul. \n\nOnce you collect the configurable amount of souls, you become Death.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Depending on the host's settings, a meeting may or may not be called immediately. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nYou are invincible and your presence is announced to everyone the meeting after you transform.", "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nYou are invincible and your presence is announced to everyone the meeting after you transform.", "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", @@ -1890,6 +1890,8 @@ "DeathReason.Retribution": "Retribution", "DeathReason.Slice": "Sliced", "DeathReason.BloodLet": "Bleed", + "DeathReason.Armageddon": "Armageddon", + "DeathReason.Starved": "Starved", "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", "Alive": "Alive", "Win": " Wins!", @@ -2563,7 +2565,6 @@ "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", "SoulCollectorToDeath": "You have become Death!!!", "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", - "CallMeetingIfDeath": "Call a meeting immediately after Death transforms", "GetPassiveSouls": "Gain a passive soul every round", "PassiveSoulGained": "You have gained a passive soul from the underworld.", diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 91b351974b..95f4e1c333 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -16,7 +16,6 @@ internal class SoulCollector : RoleBase private static OptionItem SoulCollectorPointsOpt; private static OptionItem CollectOwnSoulOpt; - private static OptionItem CallMeetingIfDeath; private static OptionItem GetPassiveSouls; private static readonly Dictionary SoulCollectorTarget = []; @@ -29,8 +28,7 @@ public static void SetupCustomOption() SoulCollectorPointsOpt = IntegerOptionItem.Create(Id + 10, "SoulCollectorPointsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]) .SetValueFormat(OptionFormat.Times); CollectOwnSoulOpt = BooleanOptionItem.Create(Id + 11, "SoulCollector_CollectOwnSoulOpt", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); - CallMeetingIfDeath = BooleanOptionItem.Create(Id + 12, "CallMeetingIfDeath", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); - /*GetPassiveSouls = BooleanOptionItem.Create(Id + 13, "GetPassiveSouls", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]);*/ + GetPassiveSouls = BooleanOptionItem.Create(Id + 12, "GetPassiveSouls", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); } public override void Init() { @@ -109,6 +107,11 @@ public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) { SoulCollectorTarget[playerId] = byte.MaxValue; DidVote[playerId] = false; + if (GetPassiveSouls.GetBool()) + { + SoulCollectorPoints[playerId]++; + Utils.SendMessage(GetString("PassiveSoulGained"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + } } } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) @@ -160,7 +163,6 @@ public override void OnFixedUpdate(PlayerControl player) player.RpcSetCustomRole(CustomRoles.Death); player.Notify(GetString("SoulCollectorToDeath")); player.RpcGuardAndKill(player); - if (CallMeetingIfDeath.GetBool()) PlayerControl.LocalPlayer.NoCheckStartMeeting(null); KillIfNotEjected(player); } } \ No newline at end of file From f7bd6b46b1b16f85bf29e587f397091e54622db8 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 14 Apr 2024 19:05:29 -0400 Subject: [PATCH 004/778] sheriff cant kill transformed NA's --- Roles/Crewmate/Sheriff.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index d642cb3894..aa99267c8c 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -99,7 +99,7 @@ public override void Remove(byte playerId) } public static void SetUpNeutralOptions(int Id) { - foreach (var neutral in CustomRolesHelper.AllRoles.Where(x => x.IsNeutral() && x is not CustomRoles.Pestilence && x is not CustomRoles.Glitch).ToArray()) + foreach (var neutral in CustomRolesHelper.AllRoles.Where(x => x.IsNeutral() && !x.IsTNA() && x is not CustomRoles.Glitch).ToArray()) { SetUpKillTargetOption(neutral, Id, true, CanKillNeutralsMode); Id++; From 3c30ff2a9e8abe86c9bd51ae53d87e9938f1fd9e Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 14 Apr 2024 20:04:35 -0400 Subject: [PATCH 005/778] Update OptionHolder.cs --- Modules/OptionHolder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 6016c43f12..fdbcd45a6c 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -598,7 +598,7 @@ public static float GetRoleChance(CustomRoles role) public static void Load() { //####################################### - // 28100 lasted id for roles/add-ons (Next use 28200) + // 28200 lasted id for roles/add-ons (Next use 28300) // Limit id for roles/add-ons --- "59999" //####################################### From b1eb0d887140ccba60332858f6e84ccaeb799ac0 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:04:05 -0400 Subject: [PATCH 006/778] target NA on judge and lawyer --- Resources/Lang/en_US.json | 1 + Roles/Crewmate/Judge.cs | 5 ++++- Roles/Neutral/Lawyer.cs | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index f1ba372498..a511a41b59 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1657,6 +1657,7 @@ "JudgeCanTrialNeutralK": "Can trial Neutral Killing", "JudgeCanTrialNeutralE": "Can trial Neutral Evil", "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", + "JudgeCanTrialNeutralA": "Can trial Neutral Apocalypse", "JudgeCanTrialSidekick": "Can trial Sidekick", "JudgeCanTrialInfected": "Can trial Infected", "JudgeCanTrialContagious": "Can trial Contagious", diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index f556f66103..401bcd486b 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -31,6 +31,7 @@ internal class Judge : RoleBase private static OptionItem CanTrialNeutralK; private static OptionItem CanTrialNeutralE; private static OptionItem CanTrialNeutralC; + private static OptionItem CanTrialNeutralA; private static readonly Dictionary TrialLimit = []; @@ -49,6 +50,7 @@ public static void SetupCustomOption() CanTrialNeutralE = BooleanOptionItem.Create(Id + 17, "JudgeCanTrialNeutralE", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralC = BooleanOptionItem.Create(Id + 18, "JudgeCanTrialNeutralC", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralK = BooleanOptionItem.Create(Id + 15, "JudgeCanTrialNeutralK", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialNeutralA = BooleanOptionItem.Create(Id + 22, "JudgeCanTrialNeutralA", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); TryHideMsg = BooleanOptionItem.Create(Id + 11, "JudgeTryHideMsg", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) .SetColor(Color.green); } @@ -162,7 +164,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) else if (pc.Is(CustomRoles.Infected)) judgeSuicide = false; else if (pc.Is(CustomRoles.Contagious)) judgeSuicide = false; else if (target.Is(CustomRoles.Rascal)) judgeSuicide = false; - else if (target.Is(CustomRoles.Pestilence)) judgeSuicide = true; + else if (target.IsTransformedNeutralApocalypse()) judgeSuicide = true; else if (target.Is(CustomRoles.Trickster)) judgeSuicide = true; else if (target.Is(CustomRoles.Madmate) && CanTrialMadmate.GetBool()) judgeSuicide = false; else if (target.Is(CustomRoles.Charmed) && CanTrialCharmed.GetBool()) judgeSuicide = false; @@ -171,6 +173,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) else if (target.GetCustomRole().IsNB() && CanTrialNeutralB.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNE() && CanTrialNeutralE.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNC() && CanTrialNeutralC.GetBool()) judgeSuicide = false; + else if (target.GetCustomRole().IsNA() && CanTrialNeutralA.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsImpostor() && !target.Is(CustomRoles.Trickster)) judgeSuicide = false; else if (target.GetCustomRole().IsMadmate() && CanTrialMadmate.GetBool()) judgeSuicide = false; else judgeSuicide = true; diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index bb152eb9af..af4febd45a 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -17,6 +17,7 @@ internal class Lawyer : RoleBase private static OptionItem CanTargetImpostor; private static OptionItem CanTargetNeutralKiller; + private static OptionItem CanTargetNeutralApoc; private static OptionItem CanTargetCrewmate; private static OptionItem CanTargetJester; private static OptionItem ShouldChangeRoleAfterTargetDeath; @@ -53,6 +54,7 @@ public static void SetupCustomOption() SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Lawyer); CanTargetImpostor = BooleanOptionItem.Create(Id + 10, "LawyerCanTargetImpostor", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetNeutralKiller = BooleanOptionItem.Create(Id + 11, "LawyerCanTargetNeutralKiller", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + CanTargetNeutralApoc = BooleanOptionItem.Create(Id + 18, "ExecutionerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetCrewmate = BooleanOptionItem.Create(Id + 12, "LawyerCanTargetCrewmate", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetJester = BooleanOptionItem.Create(Id + 13, "LawyerCanTargetJester", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); KnowTargetRole = BooleanOptionItem.Create(Id + 14, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); @@ -81,6 +83,7 @@ public override void Add(byte playerId) if (playerId == target.PlayerId) continue; else if (!CanTargetImpostor.GetBool() && target.Is(CustomRoleTypes.Impostor)) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; + else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; else if (!CanTargetCrewmate.GetBool() && target.Is(CustomRoleTypes.Crewmate)) continue; else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; else if (target.Is(CustomRoleTypes.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; From 5b3850f9d0d9c26bab78e932e3625e48b9da738c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:01:59 -0400 Subject: [PATCH 007/778] change baker id apparently its in use lol --- Roles/Neutral/Baker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index e9959e66a0..718a245acd 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -12,7 +12,7 @@ namespace TOHE.Roles.Neutral; internal class Baker : RoleBase { //===========================SETUP================================\\ - private static readonly int Id = 28200; + private static readonly int Id = 28400; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; From 6671846fd7178ae605e92b4c4bb5b96e8838da67 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 4 May 2024 12:43:45 -0400 Subject: [PATCH 008/778] fix soul collector issue --- Modules/CustomRolesHelper.cs | 3 +-- Patches/MeetingHudPatch.cs | 32 ++++++++++++++++++++++++++- Roles/Neutral/SoulCollector.cs | 40 +++++++++++++++++++--------------- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 5601837242..2de3966999 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1407,8 +1407,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Shroud => CountTypes.Shroud, CustomRoles.Werewolf => CountTypes.Werewolf, CustomRoles.Wraith => CountTypes.Wraith, - CustomRoles.Pestilence or CustomRoles.PlagueBearer or CustomRoles.SoulCollector or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.Berserker or CustomRoles.War - => CountTypes.Apocalypse, + var r when r.IsNA() => CountTypes.Apocalypse, CustomRoles.Agitater => CountTypes.Agitater, CustomRoles.Parasite => CountTypes.Impostor, CustomRoles.SerialKiller => CountTypes.SerialKiller, diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index c7b32e0d11..b8ae66e73c 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -545,6 +545,7 @@ private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, pa Witch.OnCheckForEndVoting(deathReason, playerIds); HexMaster.OnCheckForEndVoting(deathReason, playerIds); Virus.OnCheckForEndVoting(deathReason, playerIds); + SoulCollector.OnCheckForEndVoting(deathReason, playerIds); foreach (var playerId in playerIds) { @@ -810,7 +811,36 @@ public static void NotifyRoleSkillOnMeetingStart() string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); } - + // Apocalypse Notify + if (CustomRoles.Death.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("SoulCollectorTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Death), GetString("ApocalypseIsNigh"))); + }, 3f, "Death Apocalypse Notify"); + } + if (CustomRoles.Famine.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("BakerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Famine), GetString("ApocalypseIsNigh"))); + }, 3f, "Famine Apocalypse Notify"); + } + if (CustomRoles.War.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("BerserkerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.War), GetString("ApocalypseIsNigh"))); + }, 3f, "War Apocalypse Notify"); + } + if (CustomRoles.Pestilence.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("PestilenceTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Pestilence), GetString("ApocalypseIsNigh"))); + }, 3f, "Pestilence Apocalypse Notify"); + } + string MimicMsg = ""; foreach (var pc in Main.AllPlayerControls) { diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 95f4e1c333..01d67b65a7 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -110,7 +110,10 @@ public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) if (GetPassiveSouls.GetBool()) { SoulCollectorPoints[playerId]++; - Utils.SendMessage(GetString("PassiveSoulGained"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + _ = new LateTask(() => + { + Utils.SendMessage(GetString("PassiveSoulGained"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + }, 3f, "Set Chat Visible for Everyone"); } } } @@ -133,36 +136,37 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } } } - - public static void KillIfNotEjected(PlayerControl player) + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { + if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; + if (!CustomRoles.Death.RoleExist()) return; + if (exileIds.Contains(playerIdList.First())) return; var deathList = new List(); - if (Main.AfterMeetingDeathPlayers.ContainsKey(player.PlayerId)) return; + PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); foreach (var pc in Main.AllAlivePlayerControls) { if (pc.IsNeutralApocalypse()) continue; - if (player != null && player.IsAlive()) + if (sc != null && sc.IsAlive()) { if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) { - pc.SetRealKiller(player); + pc.SetRealKiller(sc); deathList.Add(pc.PlayerId); } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } } - else return; + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [..deathList]); + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [.. deathList]); } - public override void OnFixedUpdate(PlayerControl player) + public override void AfterMeetingTasks() { - if (SoulCollectorPoints[player.PlayerId] < SoulCollectorPointsOpt.GetInt() || player.GetCustomRole() is CustomRoles.Death) return; - player.RpcSetCustomRole(CustomRoles.Death); - player.Notify(GetString("SoulCollectorToDeath")); - player.RpcGuardAndKill(player); - KillIfNotEjected(player); + PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); + if (SoulCollectorPoints[sc.PlayerId] < SoulCollectorPointsOpt.GetInt()) return; + sc.RpcSetCustomRole(CustomRoles.Death); + sc.Notify(GetString("SoulCollectorToDeath")); + sc.RpcGuardAndKill(sc); } } \ No newline at end of file From 14c6b9aa442c0b0f90397eb3cc342785116d98e1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 4 May 2024 17:33:39 -0400 Subject: [PATCH 009/778] uh --- Modules/CustomRolesHelper.cs | 474 +++++----------- Modules/OptionHolder.cs | 852 ++--------------------------- Roles/AddOns/Common/Susceptible.cs | 338 +----------- Roles/Crewmate/Jailer.cs | 7 +- Roles/Crewmate/Snitch.cs | 13 +- Roles/Neutral/Lawyer.cs | 14 +- 6 files changed, 198 insertions(+), 1500 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 2de3966999..99bf2ef266 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -6,20 +6,24 @@ using TOHE.Roles.Neutral; using static TOHE.Roles.Core.CustomRoleManager; using TOHE.Roles.AddOns.Impostor; +using TOHE.Roles.Core; +using System; +using static Il2CppSystem.Linq.Expressions.DebugViewWriter; namespace TOHE; public static class CustomRolesHelper { public static readonly CustomRoles[] AllRoles = EnumHelper.GetAllValues(); - public static readonly CustomRoleTypes[] AllRoleTypes = EnumHelper.GetAllValues(); + public static Dictionary DuplicatedRoles; + public static readonly Custom_Team[] AllRoleTypes = EnumHelper.GetAllValues(); public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor, Shapeshifter, Crewmate, Engineer, Scientist { // Vanilla roles if (role.IsVanilla()) return role; // Role base - if (role.GetStaticRoleClass() is not VanillaRole) return role.GetStaticRoleClass().ThisRoleBase; + if (role.GetStaticRoleClass() is not DefaultSetup) return role.GetStaticRoleClass().ThisRoleBase; //Default return role switch @@ -32,8 +36,8 @@ public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor } public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill button (Non-Impostor) - => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor) && !role.IsImpostor() - ? RoleTypes.Impostor + => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor) && !role.IsImpostor() || role is CustomRoles.Killer // FFA + ? RoleTypes.Impostor : RoleTypes.GuardianAngel; @@ -54,90 +58,30 @@ public static bool HasImpKillButton(this PlayerControl player, bool considerVani //This is a overall check for vanilla clients to see if they are imp basis public static bool IsGhostRole(this CustomRoles role) { + if (role.GetStaticRoleClass().ThisRoleType is + Custom_RoleType.CrewmateGhosts or + Custom_RoleType.CrewmateVanillaGhosts or + Custom_RoleType.ImpostorGhosts) + return true; + return role is - CustomRoles.GuardianAngelTOHE or - CustomRoles.EvilSpirit or - CustomRoles.Warden or - CustomRoles.Hawk or - CustomRoles.Bloodmoon or - CustomRoles.Minion; + CustomRoles.EvilSpirit; } - public static bool IsAdditionRole(this CustomRoles role) + + /* + public static bool IsExperimental(this CustomRoles role) { return role is - CustomRoles.Lovers or - CustomRoles.LastImpostor or - CustomRoles.Ntr or - CustomRoles.Cyber or - CustomRoles.Madmate or - CustomRoles.Watcher or - CustomRoles.Admired or - CustomRoles.Flash or - CustomRoles.Torch or - CustomRoles.Seer or - CustomRoles.Bait or - CustomRoles.Burst or - CustomRoles.Diseased or - CustomRoles.Antidote or - CustomRoles.Fragile or - CustomRoles.VoidBallot or - CustomRoles.Aware or - CustomRoles.Swift or - CustomRoles.Cleansed or - CustomRoles.Gravestone or - CustomRoles.Trapper or - CustomRoles.Mare or - CustomRoles.Tiebreaker or - CustomRoles.Oblivious or - CustomRoles.Bewilder or - CustomRoles.Knighted or - CustomRoles.Workhorse or - CustomRoles.Fool or - CustomRoles.Autopsy or - CustomRoles.Necroview or - CustomRoles.Avanger or - CustomRoles.Sleuth or - CustomRoles.Clumsy or - CustomRoles.Nimble or - CustomRoles.Circumvent or - CustomRoles.Youtuber or - CustomRoles.Soulless or - CustomRoles.Loyal or - CustomRoles.Egoist or - CustomRoles.Recruit or - CustomRoles.TicketsStealer or - CustomRoles.Tricky or - CustomRoles.Schizophrenic or - CustomRoles.Mimic or - CustomRoles.Reach or - CustomRoles.Charmed or - CustomRoles.Infected or - CustomRoles.Onbound or - CustomRoles.Rebound or - CustomRoles.Mundane or - CustomRoles.Lazy or - CustomRoles.Rascal or - CustomRoles.Contagious or - CustomRoles.Guesser or - CustomRoles.Unreportable or - CustomRoles.Lucky or - CustomRoles.Unlucky or - CustomRoles.DoubleShot or - CustomRoles.Ghoul or - CustomRoles.Bloodlust or - CustomRoles.Overclocked or - CustomRoles.Stubborn or - CustomRoles.EvilSpirit or - CustomRoles.Hurried or - CustomRoles.Oiiai or - CustomRoles.Influenced or - CustomRoles.Silent or - CustomRoles.Rainbow or - CustomRoles.Susceptible or - CustomRoles.Statue or - CustomRoles.Tired; + CustomRoles.Disperser or + CustomRoles.Doppelganger or + CustomRoles.God or + CustomRoles.Quizmaster; } + */ + + // Add-ons + public static bool IsAdditionRole(this CustomRoles role) => role > CustomRoles.NotAssigned; public static bool IsAmneMaverick(this CustomRoles role) // ROLE ASSIGNING, NOT NEUTRAL TYPE { return role is @@ -245,15 +189,6 @@ CustomRoles.Juggernaut or CustomRoles.BloodKnight or CustomRoles.Cultist; } - public static bool IsCrewVenter(this PlayerControl target) - { - return target.Is(CustomRoles.EngineerTOHE) - || target.Is(CustomRoles.Mechanic) - || target.Is(CustomRoles.CopyCat) - || target.Is(CustomRoles.Telecommunication) && Telecommunication.CanUseVent() - || Knight.CheckCanUseVent(target) - || target.Is(CustomRoles.Nimble); - } public static bool IsTasklessCrewmate(this CustomRoles role) { // Based on Imp but counted as crewmate @@ -275,22 +210,18 @@ CustomRoles.Retributionist or CustomRoles.Benefactor or CustomRoles.Alchemist; } - public static bool IsCK(this CustomRoles role) + public static bool IsCrewKiller(this CustomRoles role) { - return role is - CustomRoles.Knight or - CustomRoles.Veteran or - CustomRoles.Judge or - CustomRoles.Bodyguard or - CustomRoles.Bastion or - CustomRoles.Reverie or - CustomRoles.Crusader or - CustomRoles.NiceGuesser or - CustomRoles.Deceiver or - CustomRoles.Retributionist or - CustomRoles.Sheriff or - CustomRoles.Vigilante or - CustomRoles.Jailer; + return role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.CrewmateKilling; + } + public static bool IsCrewVenter(this PlayerControl target) + { + return target.Is(CustomRoles.EngineerTOHE) + || target.Is(CustomRoles.Mechanic) + || target.Is(CustomRoles.CopyCat) + || target.Is(CustomRoles.Telecommunication) && Telecommunication.CanUseVent() + || Knight.CheckCanUseVent(target) + || target.Is(CustomRoles.Nimble); } public static bool IsNeutral(this CustomRoles role) { @@ -302,95 +233,17 @@ public static bool IsNeutral(this CustomRoles role) } public static bool IsNK(this CustomRoles role) { - if (role == CustomRoles.Arsonist && Arsonist.CanIgniteAnytime()) return true; - else if (role == CustomRoles.Quizmaster && Quizmaster.CanKillAfterMark) return true; - - return role is - CustomRoles.Jackal or - CustomRoles.Doppelganger or - CustomRoles.Bandit or - CustomRoles.Glitch or - CustomRoles.Sidekick or - CustomRoles.Huntsman or - CustomRoles.Infectious or - CustomRoles.Medusa or - CustomRoles.Pelican or - CustomRoles.Stalker or - CustomRoles.Juggernaut or - CustomRoles.Jinx or - CustomRoles.Poisoner or - CustomRoles.Wraith or - CustomRoles.HexMaster or - CustomRoles.Refugee or - CustomRoles.Parasite or - CustomRoles.PlagueDoctor or - CustomRoles.SerialKiller or - CustomRoles.Pyromaniac or - CustomRoles.Werewolf or - CustomRoles.PotionMaster or - CustomRoles.Demon or - CustomRoles.Pickpocket or - CustomRoles.Necromancer or - CustomRoles.Traitor or - CustomRoles.Shroud or - CustomRoles.Virus or - CustomRoles.BloodKnight or - CustomRoles.Spiritcaller or - CustomRoles.Agitater or - CustomRoles.RuthlessRomantic; + return role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.NeutralKilling; } public static bool IsNonNK(this CustomRoles role) // ROLE ASSIGNING, NOT NEUTRAL TYPE { - if (role == CustomRoles.Arsonist && !Arsonist.CanIgniteAnytime()) return true; - else if (role == CustomRoles.Quizmaster && !Quizmaster.CanKillAfterMark) return true; - - return role is - CustomRoles.Amnesiac or - CustomRoles.Follower or - CustomRoles.Hater or - CustomRoles.Lawyer or - CustomRoles.Imitator or - CustomRoles.Maverick or - CustomRoles.Opportunist or - CustomRoles.Pursuer or - CustomRoles.Shaman or - CustomRoles.CursedSoul or - CustomRoles.Doomsayer or - CustomRoles.Executioner or - CustomRoles.Innocent or - CustomRoles.Jester or - CustomRoles.Sunnyboy or - CustomRoles.Masochist or - CustomRoles.Seeker or - CustomRoles.Pixie or - CustomRoles.Collector or - CustomRoles.Cultist or - CustomRoles.Phantom or - CustomRoles.Pirate or - CustomRoles.Terrorist or - CustomRoles.Vulture or - CustomRoles.Taskinator or - CustomRoles.Workaholic or - CustomRoles.Solsticer or - CustomRoles.God or - CustomRoles.Vector or - CustomRoles.Revolutionist or - CustomRoles.Romantic or - CustomRoles.VengefulRomantic or - CustomRoles.SchrodingersCat or - CustomRoles.Provocateur; + return role.IsNB() || role.IsNE() || role.IsNC(); } public static bool IsNA(this CustomRoles role) { - return role is - CustomRoles.PlagueBearer or - CustomRoles.Pestilence or - CustomRoles.Berserker or - CustomRoles.War or - CustomRoles.SoulCollector or - CustomRoles.Death or - CustomRoles.Baker or - CustomRoles.Famine; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralApocalypse + || role.IsTNA(); } public static bool IsTNA(this CustomRoles role) { @@ -402,134 +255,37 @@ CustomRoles.Death or } public static bool IsNB(this CustomRoles role) { - return role is - CustomRoles.Amnesiac or - CustomRoles.Follower or - CustomRoles.Hater or - CustomRoles.Imitator or - CustomRoles.Lawyer or - CustomRoles.Maverick or - CustomRoles.Opportunist or - CustomRoles.Pursuer or - CustomRoles.Shaman or - CustomRoles.Taskinator or - CustomRoles.God or - CustomRoles.Romantic or - CustomRoles.VengefulRomantic or - CustomRoles.Pixie or - CustomRoles.SchrodingersCat or - CustomRoles.Sunnyboy; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralBenign; } public static bool IsNE(this CustomRoles role) { - return role is - CustomRoles.CursedSoul or - CustomRoles.Doomsayer or - CustomRoles.Executioner or - CustomRoles.Innocent or - CustomRoles.Jester or - CustomRoles.Masochist or - CustomRoles.Seeker; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralEvil; } public static bool IsNC(this CustomRoles role) { - return role is - CustomRoles.Collector or - CustomRoles.Cultist or - CustomRoles.Phantom or - CustomRoles.Vector or - CustomRoles.SoulCollector or - CustomRoles.Pirate or - CustomRoles.Terrorist or - CustomRoles.Vulture or - CustomRoles.Workaholic or - CustomRoles.Solsticer or - CustomRoles.Revolutionist or - CustomRoles.Provocateur; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralChaos; } public static bool IsImpostor(this CustomRoles role) // IsImp { + if (role.GetStaticRoleClass().ThisRoleType is + Custom_RoleType.ImpostorVanilla or + Custom_RoleType.ImpostorKilling or + Custom_RoleType.ImpostorSupport or + Custom_RoleType.ImpostorConcealing or + Custom_RoleType.ImpostorHindering or + Custom_RoleType.ImpostorGhosts) return true; return role is CustomRoles.Impostor or - CustomRoles.Shapeshifter or - CustomRoles.ShapeshifterTOHE or - CustomRoles.ImpostorTOHE or - CustomRoles.Consigliere or - CustomRoles.Wildling or - CustomRoles.Morphling or - CustomRoles.BountyHunter or - CustomRoles.Vampire or - CustomRoles.Vampiress or - CustomRoles.Witch or - CustomRoles.Vindicator or - CustomRoles.ShapeMaster or - CustomRoles.Zombie or - CustomRoles.Warlock or - CustomRoles.Undertaker or - CustomRoles.RiftMaker or - CustomRoles.Ninja or - CustomRoles.Bloodmoon or - CustomRoles.Anonymous or - CustomRoles.Visionary or - CustomRoles.Miner or - CustomRoles.Escapist or - CustomRoles.Mercenary or - CustomRoles.Underdog or - CustomRoles.Inhibitor or - CustomRoles.Councillor or - CustomRoles.Saboteur or - CustomRoles.Puppeteer or - CustomRoles.TimeThief or - CustomRoles.Trickster or - CustomRoles.Nemesis or - CustomRoles.Mastermind or - CustomRoles.Chronomancer or - CustomRoles.Stealth or - CustomRoles.Penguin or - CustomRoles.KillingMachine or - CustomRoles.Fireworker or - CustomRoles.Sniper or - CustomRoles.EvilTracker or - CustomRoles.EvilGuesser or - CustomRoles.AntiAdminer or - CustomRoles.Arrogance or - CustomRoles.Bomber or - CustomRoles.Nuker or - CustomRoles.Kamikaze or - CustomRoles.Scavenger or - CustomRoles.Trapster or - CustomRoles.Gangster or - CustomRoles.Cleaner or - CustomRoles.Lightning or - CustomRoles.Greedy or - CustomRoles.Ludopath or - CustomRoles.Godfather or - CustomRoles.CursedWolf or - CustomRoles.SoulCatcher or - CustomRoles.QuickShooter or - CustomRoles.Eraser or - CustomRoles.Butcher or - CustomRoles.Hangman or - CustomRoles.Bard or - CustomRoles.Swooper or - CustomRoles.Disperser or - CustomRoles.Dazzler or - CustomRoles.Deathpact or - CustomRoles.Devourer or - CustomRoles.Camouflager or - CustomRoles.Twister or - CustomRoles.Lurker or - CustomRoles.EvilMini or - CustomRoles.Blackmailer or - CustomRoles.Pitfall or - CustomRoles.Instigator or - CustomRoles.Minion; + CustomRoles.Shapeshifter; } - public static bool IsAbleToBeSidekicked(this CustomRoles role) + public static bool IsAbleToBeSidekicked(this CustomRoles role) => role.GetDYRole() == RoleTypes.Impostor && !role.IsImpostor() && !role.IsRecruitingRole(); - public static bool IsRecruitingRole(this CustomRoles role) + public static bool IsRecruitingRole(this CustomRoles role) => role is CustomRoles.Jackal or CustomRoles.Cultist or @@ -539,10 +295,10 @@ CustomRoles.Virus or public static bool IsMadmate(this CustomRoles role) { + if (role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.Madmate) return true; + return role is - CustomRoles.Crewpostor or - CustomRoles.Refugee or - CustomRoles.Parasite; + CustomRoles.Refugee; } /// /// Role Changes the Crewmates Team, Including changing to Impostor. @@ -697,7 +453,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Mundane: - if (pc.CanUseKillButton() || pc.GetCustomRole().IsTasklessCrewmate() || pc.Is(CustomRoleTypes.Impostor)) + if (pc.CanUseKillButton() || pc.GetCustomRole().IsTasklessCrewmate() || pc.Is(Custom_Team.Impostor)) return false; if ((pc.GetCustomRole().IsCrewmate() && !Mundane.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Mundane.CanBeOnNeutral.GetBool())) return false; @@ -759,7 +515,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.DoubleShot: if (!Options.GuesserMode.GetBool() && !pc.Is(CustomRoles.EvilGuesser) && !pc.Is(CustomRoles.NiceGuesser) && !pc.Is(CustomRoles.Doomsayer) && !pc.Is(CustomRoles.Guesser)) return false; - if (pc.Is(CustomRoles.CopyCat) + if (pc.Is(CustomRoles.CopyCat) || pc.Is(CustomRoles.Workaholic) && !Workaholic.WorkaholicCanGuess.GetBool() || (pc.Is(CustomRoles.Terrorist) && (!Terrorist.TerroristCanGuess.GetBool() || Terrorist.CanTerroristSuicideWin.GetBool()) || (pc.Is(CustomRoles.Phantom) && !Phantom.PhantomCanGuess.GetBool())) @@ -768,14 +524,14 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; if (Options.GuesserMode.GetBool()) { - if (DoubleShot.ImpCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && (pc.Is(CustomRoleTypes.Impostor) && !Options.ImpostorsCanGuess.GetBool())) + if (DoubleShot.ImpCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && (pc.Is(Custom_Team.Impostor) && !Options.ImpostorsCanGuess.GetBool())) return false; - if (DoubleShot.CrewCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && (pc.Is(CustomRoleTypes.Crewmate) && !Options.CrewmatesCanGuess.GetBool())) + if (DoubleShot.CrewCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && (pc.Is(Custom_Team.Crewmate) && !Options.CrewmatesCanGuess.GetBool())) return false; if (DoubleShot.NeutralCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Doomsayer) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.GetCustomRole().IsNK() && !Options.NeutralKillersCanGuess.GetBool()))) return false; } - if ((pc.Is(CustomRoleTypes.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(CustomRoleTypes.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(CustomRoleTypes.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool())) + if ((pc.Is(Custom_Team.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool())) return false; break; @@ -843,11 +599,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Fragile: if (pc.Is(CustomRoles.Lucky) - // || pc.Is(CustomRoles.Luckey) + || pc.Is(CustomRoles.Veteran) || pc.Is(CustomRoles.Guardian) || pc.Is(CustomRoles.Medic) || pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.Jinx) || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.CursedWolf) @@ -872,6 +627,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; + case CustomRoles.Glow: + if ((pc.GetCustomRole().IsCrewmate() && !Glow.CrewCanBeGlow.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Glow.NeutralCanBeGlow.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Glow.ImpCanBeGlow.GetBool())) + return false; + break; case CustomRoles.Antidote: if (pc.Is(CustomRoles.Diseased) || pc.Is(CustomRoles.Solsticer)) return false; @@ -933,7 +692,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Lucky: if (pc.Is(CustomRoles.Guardian) - // || pc.Is(CustomRoles.Luckey) + || pc.Is(CustomRoles.Veteran) || pc.Is(CustomRoles.Unlucky) || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.Fragile)) @@ -943,8 +702,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Unlucky: - if (//pc.Is(CustomRoles.Luckey) - pc.Is(CustomRoles.Vector) + if (pc.Is(CustomRoles.Vector) || pc.Is(CustomRoles.Lucky) || pc.Is(CustomRoles.Lucky) || pc.Is(CustomRoles.Vector) @@ -955,16 +713,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; - case CustomRoles.Ntr: - if (pc.Is(CustomRoles.Lovers) - || pc.Is(CustomRoles.Hater) - || pc.Is(CustomRoles.GuardianAngelTOHE) - || pc.Is(CustomRoles.RuthlessRomantic) - || pc.Is(CustomRoles.Romantic) - || pc.Is(CustomRoles.VengefulRomantic)) - return false; - break; - case CustomRoles.Madmate: if (pc.Is(CustomRoles.Sidekick) || pc.Is(CustomRoles.SuperStar) @@ -1066,11 +814,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Ludopath) || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Vampire) - || pc.Is(CustomRoles.Vampiress) || pc.Is(CustomRoles.Arrogance) || pc.Is(CustomRoles.LastImpostor) || pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.Trapster) || pc.Is(CustomRoles.Onbound) || pc.Is(CustomRoles.Rebound) @@ -1082,17 +828,16 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Swift: if (pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.Trapster) || pc.Is(CustomRoles.Kamikaze) || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Vampire) - || pc.Is(CustomRoles.Vampiress) || pc.Is(CustomRoles.Scavenger) || pc.Is(CustomRoles.Puppeteer) || pc.Is(CustomRoles.Mastermind) || pc.Is(CustomRoles.Warlock) || pc.Is(CustomRoles.Witch) + || pc.Is(CustomRoles.Penguin) || pc.Is(CustomRoles.Nemesis) || pc.Is(CustomRoles.Mare) || pc.Is(CustomRoles.Clumsy) @@ -1114,7 +859,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Circumvent: - if (pc.GetCustomRole() is CustomRoles.Vampire or CustomRoles.Vampiress && !Vampire.CheckCanUseVent() + if (pc.GetCustomRole() is CustomRoles.Vampire && !Vampire.CheckCanUseVent() || pc.Is(CustomRoles.Witch) && Witch.ModeSwitchActionOpt.GetValue() == 1 || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Wildling) @@ -1128,7 +873,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Clumsy: if (pc.Is(CustomRoles.Swift) || pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.KillingMachine)) return false; if (!pc.GetCustomRole().IsImpostor()) @@ -1204,7 +948,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Flash: - if (pc.Is(CustomRoles.Swooper) + if (pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.Tired) || pc.Is(CustomRoles.Statue) @@ -1256,10 +1000,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if ((pc.GetCustomRole().IsCrewmate() && !Rainbow.CrewCanBeRainbow.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Rainbow.NeutralCanBeRainbow.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Rainbow.ImpCanBeRainbow.GetBool())) return false; break; - + case CustomRoles.Susceptible: if ((pc.GetCustomRole().IsCrewmate() && !Susceptible.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Susceptible.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Susceptible.CanBeOnImp.GetBool())) - return false; + return false; break; case CustomRoles.Tired: @@ -1269,11 +1013,11 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Lighter) || pc.Is(CustomRoles.Flash) - || pc.Is(CustomRoles.Mare)) - return false; - if ((pc.GetCustomRole().IsCrewmate() && !Tired.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Tired.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Tired.CanBeOnImp.GetBool())) - return false; - break; + || pc.Is(CustomRoles.Mare)) + return false; + if ((pc.GetCustomRole().IsCrewmate() && !Tired.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Tired.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Tired.CanBeOnImp.GetBool())) + return false; + break; case CustomRoles.Statue: if (pc.Is(CustomRoles.Alchemist) @@ -1333,13 +1077,17 @@ CustomRoles.GuardianAngel or CustomRoles.Impostor or CustomRoles.Shapeshifter; } - public static CustomRoleTypes GetCustomRoleTypes(this CustomRoles role) + public static Custom_Team GetCustomRoleTeam(this CustomRoles role) + { + Custom_Team team = Custom_Team.Crewmate; + if (role.IsImpostor()) team = Custom_Team.Impostor; + if (role.IsNeutral()) team = Custom_Team.Neutral; + if (role.IsAdditionRole()) team = Custom_Team.Addon; + return team; + } + public static Custom_RoleType GetCustomRoleType(this CustomRoles role) { - CustomRoleTypes type = CustomRoleTypes.Crewmate; - if (role.IsImpostor()) type = CustomRoleTypes.Impostor; - if (role.IsNeutral()) type = CustomRoleTypes.Neutral; - if (role.IsAdditionRole()) type = CustomRoleTypes.Addon; - return type; + return role.GetStaticRoleClass().ThisRoleType; } public static bool RoleExist(this CustomRoles role, bool countDead = false) => Main.AllPlayerControls.Any(x => x.Is(role) && (x.IsAlive() || countDead)); public static int GetCount(this CustomRoles role) @@ -1407,7 +1155,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Shroud => CountTypes.Shroud, CustomRoles.Werewolf => CountTypes.Werewolf, CustomRoles.Wraith => CountTypes.Wraith, - var r when r.IsNA() => CountTypes.Apocalypse, + var r when r.IsNA() => CountTypes.Apocalypse, CustomRoles.Agitater => CountTypes.Agitater, CustomRoles.Parasite => CountTypes.Impostor, CustomRoles.SerialKiller => CountTypes.SerialKiller, @@ -1425,7 +1173,7 @@ var r when r.IsNA() => CountTypes.Apocalypse, CustomRoles.Medusa => CountTypes.Medusa, CustomRoles.Refugee => CountTypes.Impostor, CustomRoles.Huntsman => CountTypes.Huntsman, - CustomRoles.Glitch => CountTypes.Glitch, + CustomRoles.Glitch => CountTypes.Glitch, CustomRoles.Spiritcaller => CountTypes.Spiritcaller, CustomRoles.RuthlessRomantic => CountTypes.RuthlessRomantic, CustomRoles.SchrodingersCat => CountTypes.None, @@ -1536,13 +1284,43 @@ var r when r.IsNA() => CountTypes.Apocalypse, }; public static bool HasSubRole(this PlayerControl pc) => Main.PlayerStates[pc.PlayerId].SubRoles.Any(); } -public enum CustomRoleTypes +public enum Custom_Team { Crewmate, Impostor, Neutral, Addon, } +public enum Custom_RoleType +{ + // Impostors + ImpostorVanilla, + ImpostorKilling, + ImpostorSupport, + ImpostorConcealing, + ImpostorHindering, + ImpostorGhosts, + + Madmate, + + // Crewmate + CrewmateVanilla, + CrewmateVanillaGhosts, + CrewmateBasic, + CrewmateSupport, + CrewmateKilling, + CrewmatePower, + CrewmateGhosts, + + // Neutral + NeutralBenign, + NeutralEvil, + NeutralChaos, + NeutralKilling, + NeutralApocalypse, + + None +} public enum CountTypes { OutOfGame, diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index fdbcd45a6c..7898d13738 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -10,7 +10,9 @@ using TOHE.Roles.Double; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using TOHE.Roles.Vanilla; using UnityEngine; +using TOHE.Roles.Core; namespace TOHE; @@ -129,7 +131,7 @@ private enum RatesZeroOne "CamouflageMode.TommyXL" ]; - // 各役職の詳細設定 + //public static OptionItem EnableGM; public static float DefaultKillCooldown = Main.NormalOptions?.KillCooldown ?? 20; public static OptionItem GhostsDoTasks; @@ -488,14 +490,6 @@ private enum RatesZeroOne public static OptionItem AddBracketsToAddons; public static OptionItem NoLimitAddonsNumMax; - // Impostors role settings - public static OptionItem ShapeshiftCD; - public static OptionItem ShapeshiftDur; - - // Crewmates role settings - public static OptionItem ScientistCD; - public static OptionItem ScientistDur; - // Add-Ons settings public static OptionItem LoverSpawnChances; public static OptionItem LoverKnowRoles; @@ -525,7 +519,7 @@ private enum RatesZeroOne { "GuesserMode.All", "GuesserMode.Harmful", "GuesserMode.Random" }; */ - + public static readonly string[] suffixModes = [ "SuffixMode.None", @@ -536,7 +530,7 @@ private enum RatesZeroOne "SuffixMode.OriginalName", "SuffixMode.DoNotKillMe", "SuffixMode.NoAndroidPlz", - "SuffixMode.AutoHost" + "SuffixMode.AutoHost" ]; public static readonly string[] roleAssigningAlgorithms = [ @@ -598,7 +592,7 @@ public static float GetRoleChance(CustomRoles role) public static void Load() { //####################################### - // 28200 lasted id for roles/add-ons (Next use 28300) + // 28400 lasted id for roles/add-ons (Next use 28500) // Limit id for roles/add-ons --- "59999" //####################################### @@ -640,8 +634,6 @@ public static void Load() Madmate.SetupMenuOptions(); - Refugee.SetupCustomOption(); - //MadmateCanFixSabotage = BooleanOptionItem.Create(50010, "MadmateCanFixSabotage", false, TabGroup.ImpostorRoles, false) //.SetGameMode(CustomGameMode.Standard); @@ -700,154 +692,14 @@ public static void Load() .SetHeader(true) .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - SetupRoleOptions(300, TabGroup.ImpostorRoles, CustomRoles.ImpostorTOHE); - - SetupRoleOptions(400, TabGroup.ImpostorRoles, CustomRoles.ShapeshifterTOHE); - ShapeshiftCD = FloatOptionItem.Create(402, "ShapeshiftCooldown", new(1f, 180f, 1f), 15f, TabGroup.ImpostorRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterTOHE]) - .SetValueFormat(OptionFormat.Seconds); - ShapeshiftDur = FloatOptionItem.Create(403, "ShapeshiftDuration", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterTOHE]) - .SetValueFormat(OptionFormat.Seconds); - + CustomRoleManager.GetNormalOptions(Custom_RoleType.ImpostorVanilla).ForEach(r => r.SetupCustomOption()); TextOptionItem.Create(10000001, "RoleType.ImpKilling", TabGroup.ImpostorRoles) // KILLING .SetGameMode(CustomGameMode.Standard) .SetHeader(true) .SetColor(new Color32(255, 25, 25, byte.MaxValue));// KILLING - /* - * Arrogance - */ - Arrogance.SetupCustomOption(); - - /* - * Bomber - */ - Bomber.SetupCustomOption(); - - /* - * Bounty Hunter - */ - BountyHunter.SetupCustomOption(); - - /* - * Butcher - */ - Butcher.SetupCustomOption(); - - /* - * Chronomancer - */ - Chronomancer.SetupCustomOption(); - - /* - * Councillor - */ - Councillor.SetupCustomOption(); - - /* - * Cursed Wolf (From: TOH_Y) - */ - CursedWolf.SetupCustomOption(); - - - /* - * Deathpact - */ - Deathpact.SetupCustomOption(); - - /* - * Evil Guesser - */ - EvilGuesser.SetupCustomOption(); - - /* - * Evil Tracker - */ - EvilTracker.SetupCustomOption(); - - /* - * Greedy - */ - Greedy.SetupCustomOption(); - - /* - * Hangman - */ - Hangman.SetupCustomOption(); - - /* - * Inhibitor - */ - Inhibitor.SetupCustomOption(); - - /* - * Instigator - */ - Instigator.SetupCustomOption(); - - /* - * Killing Machine - */ - KillingMachine.SetupCustomOption(); - - /* - * Ludopath - */ - Ludopath.SetupCustomOption(); - - /* - * Lurker - */ - Lurker.SetupCustomOption(); - - // Mare.SetupCustomOption(); - - /* - * Mercenary - */ - Mercenary.SetupCustomOption(); - - /* - * Ninja - */ - Ninja.SetupCustomOption(); - - /* - * Quick Shooter - */ - QuickShooter.SetupCustomOption(); - - /* - * Saboteur - */ - Saboteur.SetupCustomOption(); - - /* - * Sniper - */ - Sniper.SetupCustomOption(); - - /* - * Spellcaster - */ - Witch.SetupCustomOption(); - - /* - * Trapster - */ - Trapster.SetupCustomOption(); - - /* - * Underdog - */ - Underdog.SetupCustomOption(); - - /* - * Zombie - */ - Zombie.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.ImpostorKilling).ForEach(r => r.SetupCustomOption()); /* * SUPPORT ROLES @@ -856,74 +708,7 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - /* - * Anti Adminer - */ - AntiAdminer.SetupCustomOption(); - - /* - * Blackmailer - */ - Blackmailer.SetupCustomOption(); - - /* - * Camouflager - */ - Camouflager.SetupCustomOption(); - - /* - * Cleaner - */ - Cleaner.SetupCustomOption(); - - /* - * Consigliere - */ - Consigliere.SetupCustomOption(); - - /* - * Fireworker - */ - Fireworker.SetupCustomOption(); - - /* - * Gangster - */ - Gangster.SetupCustomOption(); - - /* - * Godfather - */ - Godfather.SetupCustomOption(); - - /* - * Kamikaze - */ - Kamikaze.SetupCustomOption(); - - /* - * Morphling - */ - Morphling.SetupCustomOption(); - - /* - * Nemesis - */ - Nemesis.SetupCustomOptions(); - /* - * Time Thief - */ - TimeThief.SetupCustomOption(); - - /* - * Vindicator - */ - Vindicator.SetupCustomOption(); - - /* - * Visionary - */ - Visionary.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.ImpostorSupport).ForEach(r => r.SetupCustomOption()); /* * CONCEALING ROLES @@ -932,80 +717,7 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - /* - * Escapist - */ - Escapist.SetupCustomOption(); - - /* - * Lightning - */ - Lightning.SetupCustomOption(); - - /* - * Mastermind - */ - Mastermind.SetupCustomOption(); - - /* - * Miner - */ - Miner.SetupCustomOption(); - - /* - * Puppeteer - */ - Puppeteer.SetupCustomOption(); - - /* - * Rift Maker - */ - RiftMaker.SetupCustomOption(); - - /* - * Scavenger - */ - Scavenger.SetupCustomOption(); - - /* - * ShapeMaster - */ - ShapeMaster.SetupCustomOption(); - - /* - * Soul Catcher - */ - SoulCatcher.SetupCustomOption(); - - /* - * Swooper - */ - Swooper.SetupCustomOption(); - - /* - * Trickster - */ - Trickster.SetupCustomOption(); - - /* - * Undertaker - */ - Undertaker.SetupCustomOption(); - - /* - * Vampire - */ - Vampire.SetupCustomOption(); - - /* - * Warlock - */ - Warlock.SetupCustomOption(); - - /* - * Wildling - */ - Wildling.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.ImpostorConcealing).ForEach(r => r.SetupCustomOption()); /* * HINDERING ROLES @@ -1014,45 +726,7 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - /* - * Anonymous - */ - Anonymous.SetupCustomOption(); - - /* - * Dazzler - */ - Dazzler.SetupCustomOption(); - - /* - * Stealth - */ - Stealth.SetupCustomOption(); - - /* - * Devourer - */ - Devourer.SetupCustomOption(); - - /* - * Eraser - */ - Eraser.SetupCustomOption(); - - /* - * Pitfall - */ - Pitfall.SetupCustomOption(); - - /* - * Penguin - */ - Penguin.SetupCustomOption(); - - /* - * Twister - */ - Twister.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.ImpostorHindering).ForEach(r => r.SetupCustomOption()); /* * MADMATE ROLES @@ -1061,15 +735,7 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - /* - * Crewpostor - */ - Crewpostor.SetupCustomOption(); - - /* - * Parasite - */ - Parasite.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.Madmate).ForEach(r => r.SetupCustomOption()); /* * Impostor Ghost Roles @@ -1078,9 +744,7 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - Minion.SetupCustomOption(); - - Bloodmoon.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.ImpostorGhosts).ForEach(r => r.SetupCustomOption()); #endregion @@ -1091,32 +755,9 @@ public static void Load() TextOptionItem.Create(10000006, "RoleType.VanillaRoles", TabGroup.CrewmateRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - - /* - * Crewmate - */ - SetupRoleOptions(6000, TabGroup.CrewmateRoles, CustomRoles.CrewmateTOHE); - - /* - * Engineer - */ - SetupRoleOptions(6100, TabGroup.CrewmateRoles, CustomRoles.EngineerTOHE); - - /* - * Scientist - */ - SetupRoleOptions(6200, TabGroup.CrewmateRoles, CustomRoles.ScientistTOHE); - ScientistCD = FloatOptionItem.Create(6202, "VitalsCooldown", new(1f, 250f, 1f), 3f, TabGroup.CrewmateRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.ScientistTOHE]) - .SetValueFormat(OptionFormat.Seconds); - ScientistDur = FloatOptionItem.Create(6203, "VitalsDuration", new(1f, 250f, 1f), 15f, TabGroup.CrewmateRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.ScientistTOHE]) - .SetValueFormat(OptionFormat.Seconds); - /* - * Guardian Angel - */ - GuardianAngelTOHE.SetupCustomOptions(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmateVanilla).ForEach(r => r.SetupCustomOption()); + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmateVanillaGhosts).ForEach(r => r.SetupCustomOption()); /* * BASIC ROLES @@ -1125,84 +766,13 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - /* - * Addict - */ - Addict.SetupCustomOption(); - - /* - * Alchemist - */ - Alchemist.SetupCustomOption(); - - /* - * Celebrity - */ - Celebrity.SetupCustomOptions(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmateBasic).ForEach(r => r.SetupCustomOption()); /* - * Cleanser + * MINI */ - Cleanser.SetupCustomOption(); + CustomRoles.Mini.GetStaticRoleClass().SetupCustomOption(); - /* - * Doctor - */ - Doctor.SetupCustomOptions(); - - /* - * Guess Master - */ - GuessMaster.SetupCustomOption(); - - /* - * Lazy Guy - */ - LazyGuy.SetupCustomOptions(); - - /* - * Luckey - */ - //SetupRoleOptions(6900, TabGroup.CrewmateRoles, CustomRoles.Luckey); - //LuckeyProbability = IntegerOptionItem.Create(6902, "LuckeyProbability", new(0, 100, 5), 50, TabGroup.CrewmateRoles, false) - // .SetParent(CustomRoleSpawnChances[CustomRoles.Luckey]) - // .SetValueFormat(OptionFormat.Percent); - - /* - * Mini - */ - Mini.SetupCustomOption(); - - /* - * Mole - */ - Mole.SetupCustomOption(); - - /* - * Superstar - */ - SuperStar.SetupCustomOptions(); - - /* - * Task Manager - */ - TaskManager.SetupCustomOptions(); - - /* - * Tracefinder - */ - Tracefinder.SetupCustomOption(); - - /* - * Transporter - */ - Transporter.SetupCustomOptions(); - - /* - * Randomizer - */ - Randomizer.SetupCustomOptions(); - /* * SUPPORT ROLES */ @@ -1210,213 +780,25 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - /* - * Benefactor - */ - Benefactor.SetupCustomOption(); - - /* - * Chameleon - */ - Chameleon.SetupCustomOption(); - - /* - * Coroner - */ - Coroner.SetupCustomOption(); - - /* - * Deputy - */ - Deputy.SetupCustomOption(); - - /* - * Detective - */ - Detective.SetupCustomOptions(); - - /* - * Fortune Teller - */ - FortuneTeller.SetupCustomOption(); - - /* - * Enigma - */ - Enigma.SetupCustomOption(); - - /* - * Grenadier - */ - Grenadier.SetupCustomOptions(); - - /* - * Inspector - */ - Inspector.SetupCustomOption(); - - /* - * Investigator - */ - Investigator.SetupCustomOption(); - - /* - * Keeper - */ - Keeper.SetupCustomOption(); - - /* - * Lighter - */ - Lighter.SetupCustomOptions(); - - /* - * Mechanic - */ - Mechanic.SetupCustomOption(); - - /* - * Medic - */ - Medic.SetupCustomOption(); - - /* - * Medium - */ - Medium.SetupCustomOption(); - - /* - * Merchant - */ - Merchant.SetupCustomOption(); - - /* - * Mortician - */ - Mortician.SetupCustomOption(); - - /* - * Observer - */ - Observer.SetupCustomOption(); - - /* - * Oracle - */ - Oracle.SetupCustomOption(); - - /* - * Pacifist - */ - Pacifist.SetupCustomOptions(); - - /*SetupRoleOptions(9300, TabGroup.CrewmateRoles, CustomRoles.Paranoia); - ParanoiaNumOfUseButton = IntegerOptionItem.Create(9302, "ParanoiaNumOfUseButton", new(1, 20, 1), 3, TabGroup.CrewmateRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]) - .SetValueFormat(OptionFormat.Times); - ParanoiaVentCooldown = FloatOptionItem.Create(9303, "ParanoiaVentCooldown", new(0, 180, 1), 10, TabGroup.CrewmateRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]) - .SetValueFormat(OptionFormat.Seconds); */ - - /* - * Psychic - */ - Psychic.SetupCustomOption(); - - /* - * Snitch - */ - Snitch.SetupCustomOption(); - - /* - * Spiritualist - */ - Spiritualist.SetupCustomOption(); - - /* - * Spy - */ - Spy.SetupCustomOption(); - - /* - * Time Manager - */ - TimeManager.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmateSupport).ForEach(r => r.SetupCustomOption()); /* - * Time Master + * KILLING ROLES */ - TimeMaster.SetupCustomOptions(); - - /* - * Tracker - */ - Tracker.SetupCustomOption(); - - /* - * Witness - */ - Witness.SetupCustomOptions(); - - TextOptionItem.Create(10000009, "RoleType.CrewKilling", TabGroup.CrewmateRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - Bastion.SetupCustomOptions(); - - Bodyguard.SetupCustomOptions(); - - Crusader.SetupCustomOption(); - - Deceiver.SetupCustomOption(); - - Jailer.SetupCustomOption(); - - Judge.SetupCustomOption(); - - Knight.SetupCustomOption(); - - Retributionist.SetupCustomOptions(); - - Reverie.SetupCustomOption(); - - Sheriff.SetupCustomOption(); - - Veteran.SetupCustomOptions(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmateKilling).ForEach(r => r.SetupCustomOption()); + /* + * POWER ROLES + */ TextOptionItem.Create(10000010, "RoleType.CrewPower", TabGroup.CrewmateRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - Admirer.SetupCustomOption(); - - Captain.SetupCustomOption(); - - CopyCat.SetupCustomOption(); - - Dictator.SetupCustomOptions(); - - Guardian.SetupCustomOptions(); - - Lookout.SetupCustomOptions(); - - Marshall.SetupCustomOption(); - - Mayor.SetupCustomOptions(); - - Monarch.SetupCustomOption(); - - Overseer.SetupCustomOption(); - - President.SetupCustomOption(); - - Swapper.SetupCustomOption(); - - Telecommunication.SetupCustomOption(); - - //ChiefOfPolice.SetupCustomOption(); - + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmatePower).ForEach(r => r.SetupCustomOption()); /* * Crewmate Ghost Roles @@ -1425,9 +807,7 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - Warden.SetupCustomOptions(); - - Hawk.SetupCustomOptions(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.CrewmateGhosts).ForEach(r => r.SetupCustomOption()); #endregion @@ -1437,156 +817,32 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - Amnesiac.SetupCustomOption(); - - Follower.SetupCustomOption(); - - Hater.SetupCustomOption(); - - Imitator.SetupCustomOption(); - - Lawyer.SetupCustomOption(); - - Maverick.SetupCustomOption(); - - Opportunist.SetupCustomOptions(); - - Pixie.SetupCustomOption(); - - Pursuer.SetupCustomOption(); - - Romantic.SetupCustomOption(); - - SchrodingersCat.SetupCustomOption(); - - Shaman.SetupCustomOptions(); - - Taskinator.SetupCustomOption(); - + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralBenign).ForEach(r => r.SetupCustomOption()); TextOptionItem.Create(10000012, "RoleType.NeutralEvil", TabGroup.NeutralRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - CursedSoul.SetupCustomOption(); - - Doomsayer.SetupCustomOption(); - - Executioner.SetupCustomOption(); - - Innocent.SetupCustomOptions(); - - Jester.SetupCustomOptions(); - - Seeker.SetupCustomOption(); - - Masochist.SetupCustomOptions(); - + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralEvil).ForEach(r => r.SetupCustomOption()); TextOptionItem.Create(10000013, "RoleType.NeutralChaos", TabGroup.NeutralRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - - Collector.SetupCustomOption(); - - Cultist.SetupCustomOption(); - - Phantom.SetupCustomOptions(); - - Pirate.SetupCustomOption(); - Provocateur.SetupCustomOptions(); - - Revolutionist.SetupCustomOptions(); - - - /* - * Solsticer - */ - Solsticer.SetupCustomOption(); - - Terrorist.SetupCustomOptions(); - - Vector.SetupCustomOptions(); - - Vulture.SetupCustomOption(); - - Workaholic.SetupCustomOptions(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralChaos).ForEach(r => r.SetupCustomOption()); TextOptionItem.Create(10000014, "RoleType.NeutralKilling", TabGroup.NeutralRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - - Agitater.SetupCustomOption(); - - Arsonist.SetupCustomOptions(); - - Bandit.SetupCustomOption(); - - BloodKnight.SetupCustomOption(); - - Demon.SetupCustomOption(); - - Glitch.SetupCustomOption(); - - HexMaster.SetupCustomOption(); - - Huntsman.SetupCustomOption(); - - Infectious.SetupCustomOption(); - - Jackal.SetupCustomOption(); - - Jinx.SetupCustomOption(); - - Juggernaut.SetupCustomOption(); - - Medusa.SetupCustomOption(); - - Necromancer.SetupCustomOption(); - - Spiritcaller.SetupCustomOption(); - - Pelican.SetupCustomOption(); - Pickpocket.SetupCustomOption(); - - Poisoner.SetupCustomOption(); - - PlagueDoctor.SetupCustomOption(); - - PotionMaster.SetupCustomOption(); - - Pyromaniac.SetupCustomOption(); - - if (!Quizmaster.InExperimental) - Quizmaster.SetupCustomOption(); - - SerialKiller.SetupCustomOption(); - - Shroud.SetupCustomOption(); - - Stalker.SetupCustomOption(); // Stalker (TOHY) - - Traitor.SetupCustomOption(); - - Virus.SetupCustomOption(); - - Werewolf.SetupCustomOption(); - - Wraith.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralKilling).ForEach(r => r.SetupCustomOption()); TextOptionItem.Create(10000015, "RoleType.NeutralApocalypse", TabGroup.NeutralRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - Baker.SetupCustomOption(); - - Berserker.SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralApocalypse).ForEach(r => r.SetupCustomOption()); - PlagueBearer.SetupCustomOption(); - - SoulCollector.SetupCustomOption(); #endregion #region Add-Ons Settings @@ -1599,7 +855,7 @@ public static void Load() Autopsy.SetupCustomOptions(); Bait.SetupCustomOptions(); - + /* * Beartrap */ @@ -1634,7 +890,7 @@ public static void Load() Sleuth.SetupCustomOptions(); Tiebreaker.SetupCustomOptions(); - + Torch.SetupCustomOptions(); Watcher.SetupCustomOptions(); @@ -1662,7 +918,7 @@ public static void Load() Rascal.SetupCustomOptions(); Unlucky.SetupCustomOptions(); - + Tired.SetupCustomOptions(); VoidBallot.SetupCustomOptions(); @@ -1760,7 +1016,7 @@ public static void Load() Reach.SetupCustomOptions(); Rainbow.SetupCustomOptions(); - + Workhorse.SetupCustomOption(); #endregion @@ -1770,40 +1026,30 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(247, 70, 49, byte.MaxValue)); - Disperser.SetupCustomOption(); + CustomRoleManager.GetExperimentalOptions(Custom_Team.Impostor).ForEach(r => r.SetupCustomOption()); + + //Disperser.SetupCustomOption(); - // 船员 + // Crewmate roles TextOptionItem.Create(10000021, "OtherRoles.CrewmateRoles", TabGroup.OtherRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(140, 255, 255, byte.MaxValue)); - /*SetupRoleOptions(24700, TabGroup.OtherRoles, CustomRoles.SpeedBooster); - SpeedBoosterUpSpeed = FloatOptionItem.Create(24703, "SpeedBoosterUpSpeed", new(0.1f, 1.0f, 0.1f), 0.2f, TabGroup.OtherRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.SpeedBooster]) - .SetValueFormat(OptionFormat.Multiplier); - SpeedBoosterTimes = IntegerOptionItem.Create(24704, "SpeedBoosterTimes", new(1, 99, 1), 5, TabGroup.OtherRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.SpeedBooster]) - .SetValueFormat(OptionFormat.Times); */ - - // 中立 + CustomRoleManager.GetExperimentalOptions(Custom_Team.Crewmate).ForEach(r => r.SetupCustomOption()); + TextOptionItem.Create(10000022, "OtherRoles.NeutralRoles", TabGroup.OtherRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - - Doppelganger.SetupCustomOption(); - God.SetupCustomOption(); + CustomRoleManager.GetExperimentalOptions(Custom_Team.Neutral).ForEach(r => r.SetupCustomOption()); - if (Quizmaster.InExperimental) - Quizmaster.SetupCustomOption(); - - // 副职 + // addons TextOptionItem.Create(10000023, "OtherRoles.Addons", TabGroup.OtherRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 154, 206, byte.MaxValue)); - //SetupAdtRoleOptions(25300, CustomRoles.Ntr, tab: TabGroup.OtherRoles); + Glow.SetupCustomOptions; Youtuber.SetupCustomOptions(); @@ -1818,7 +1064,7 @@ public static void Load() SeeEjectedRolesInMeeting = BooleanOptionItem.Create(60041, "SeeEjectedRolesInMeeting", true, TabGroup.SystemSettings, false) .SetColor(Color.green) .HideInHnS(); - + KickLowLevelPlayer = IntegerOptionItem.Create(60050, "KickLowLevelPlayer", new(0, 100, 1), 0, TabGroup.SystemSettings, false) .SetValueFormat(OptionFormat.Level) .SetHeader(true); @@ -1904,7 +1150,7 @@ public static void Load() .HideInHnS(); AutoDisplayLastResult = BooleanOptionItem.Create(60290, "AutoDisplayLastResult", true, TabGroup.SystemSettings, false) .HideInHnS(); - + SuffixMode = StringOptionItem.Create(60300, "SuffixMode", suffixModes, 0, TabGroup.SystemSettings, true) .SetHeader(true); HideHostText = BooleanOptionItem.Create(60311, "HideHostText", false, TabGroup.SystemSettings, false); @@ -2025,7 +1271,7 @@ public static void Load() // Random Spawn RandomSpawn.SetupCustomOption(); - + MapModification = BooleanOptionItem.Create(60480, "MapModification", false, TabGroup.GameSettings, false) .SetColor(new Color32(19, 188, 233, byte.MaxValue)); // Airship Variable Electrical @@ -2436,7 +1682,7 @@ public static void Load() .SetParent(DisableLongTasks); DisableHoistSupplies = BooleanOptionItem.Create(60639, "DisableHoistSupplies", false, TabGroup.TaskSettings, false) .SetParent(DisableLongTasks); - + //Disable Divert Power, Weather Nodes and etc. situational Tasks @@ -2627,7 +1873,7 @@ public static void SetupRoleOptions(int id, TabGroup tab, CustomRoles role, Cust var spawnOption = StringOptionItem.Create(id, role.ToString(), zeroOne ? EnumHelper.GetAllNames() : EnumHelper.GetAllNames(), 0, tab, false).SetColor(Utils.GetRoleColor(role)) .SetHeader(true) .SetGameMode(customGameMode) as StringOptionItem; - + var countOption = IntegerOptionItem.Create(id + 1, "Maximum", new(1, 15, 1), 1, tab, false) .SetParent(spawnOption) .SetValueFormat(OptionFormat.Players) diff --git a/Roles/AddOns/Common/Susceptible.cs b/Roles/AddOns/Common/Susceptible.cs index 5418aa0e10..13b1d7d626 100644 --- a/Roles/AddOns/Common/Susceptible.cs +++ b/Roles/AddOns/Common/Susceptible.cs @@ -40,346 +40,12 @@ public static void CallEnabledAndChange(PlayerControl victim) if (EnabledDeathReasons.GetBool()) { Logger.Info($"{victim.GetNameWithRole().RemoveHtmlTags()} had the death reason {randomReason}", "Susceptible"); - switch (randomReason) - { - case PlayerState.DeathReason.Eaten: - if (!Pelican.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; + Main.PlayerStates[victim.PlayerId].deathReason = randomReason.DeathReasonIsEnable() ? randomReason : Main.PlayerStates[victim.PlayerId].deathReason; - case PlayerState.DeathReason.Spell: - if (!Witch.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Hex: - if (CustomRoles.HexMaster.IsEnable()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Curse: - if (!CustomRoles.CursedWolf.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Jinx: - if (!Jinx.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - - case PlayerState.DeathReason.Shattered: - if (!CustomRoles.Lovers.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - - case PlayerState.DeathReason.Bite: - if (!Vampire.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Poison: - if (!Poisoner.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Bombed: - if (!Bomber.HasEnabled && !Burst.IsEnable && !Trapster.HasEnabled && !Fireworker.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Misfire: - if (!ChiefOfPolice.IsEnable) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Torched: - if (!CustomRoles.Arsonist.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Sniped: - if (!Sniper.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Revenge: - if (!CustomRoles.Avanger.RoleExist() && !CustomRoles.Retributionist.RoleExist() && !CustomRoles.Nemesis.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Gambled: - if (!CustomRoles.EvilGuesser.RoleExist() && !CustomRoles.NiceGuesser.RoleExist() && !Options.GuesserMode.GetBool()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Quantization: - if (!Lightning.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Overtired: - if (!CustomRoles.Workaholic.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Ashamed: - if (!CustomRoles.Workaholic.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.PissedOff: - if (!CustomRoles.Pestilence.RoleExist() && !CustomRoles.Provocateur.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Dismembered: - if (!CustomRoles.Butcher.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.LossOfHead: - if (!Hangman.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Trialed: - if (!Judge.HasEnabled && !Councillor.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Infected: - if (!Infectious.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Hack: - if (!Glitch.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Pirate: - if (!Pirate.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Shrouded: - if (!Shroud.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Mauled: - if (!Werewolf.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.Slice: - if (!Hawk.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.BloodLet: - if (!Bloodmoon.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.Armageddon: - if (!CustomRoles.SoulCollector.IsEnable()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.Starved: - if (!CustomRoles.Baker.IsEnable()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - default: - while (Main.PlayerStates[victim.PlayerId].deathReason != randomReason) - Main.PlayerStates[victim.PlayerId].deathReason = randomReason; - break; - } } else { - while (Main.PlayerStates[victim.PlayerId].deathReason != randomReason) - Main.PlayerStates[victim.PlayerId].deathReason = randomReason; + Main.PlayerStates[victim.PlayerId].deathReason = randomReason.DeathReasonIsEnable(true) ? randomReason : Main.PlayerStates[victim.PlayerId].deathReason; } } } diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 8a22ff09e5..9f372b8803 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -14,6 +14,7 @@ internal class Jailer : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ private static OptionItem JailCooldown; @@ -31,7 +32,7 @@ internal class Jailer : RoleBase private static readonly Dictionary JailerHasExe = []; private static readonly Dictionary JailerDidVote = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Jailer); JailCooldown = FloatOptionItem.Create(Id + 10, "JailerJailCooldown", new(0f, 999f, 1f), 15f, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]) @@ -74,7 +75,7 @@ public override void Remove(byte playerId) JailerHasExe.Remove(playerId); JailerDidVote.Remove(playerId); } - + public override bool CanUseKillButton(PlayerControl pc) => true; public static bool IsTarget(byte playerId) => JailerTarget.ContainsValue(playerId); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).IsAlive() ? JailCooldown.GetFloat() : 300f; public override string GetProgressText(byte playerId, bool cooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer).ShadeColor(0.25f), JailerExeLimit.TryGetValue(playerId, out var exeLimit) ? $"({exeLimit})" : "Invalid"); @@ -181,7 +182,7 @@ private static bool CanBeExecuted(CustomRoles role) (role.IsNE() && NECanBeExe.GetBool()) || (role.IsNK() && NKCanBeExe.GetBool()) || (role.IsNA() && NACanBeExe.GetBool()) || - (role.IsCK() && CKCanBeExe.GetBool()) || + (role.IsCrewKiller() && CKCanBeExe.GetBool()) || (role.IsImpostorTeamV3())); } diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index 4b1f601424..2223975bf5 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -13,6 +13,7 @@ internal class Snitch : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ private static readonly Color RoleColor = Utils.GetRoleColor(CustomRoles.Snitch); @@ -37,7 +38,7 @@ internal class Snitch : RoleBase private static readonly HashSet TargetList = []; private static readonly Dictionary TargetColorlist = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Snitch); OptionEnableTargetArrow = BooleanOptionItem.Create(Id + 10, "SnitchEnableTargetArrow", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); @@ -85,7 +86,7 @@ public override void Remove(byte playerId) } private static bool IsThisRole(byte playerId) => playerIdList.Contains(playerId); - + private static bool GetExpose(PlayerControl pc) { if (!IsThisRole(pc.PlayerId) || !pc.IsAlive() || pc.Is(CustomRoles.Madmate)) return false; @@ -93,10 +94,10 @@ private static bool GetExpose(PlayerControl pc) var snitchId = pc.PlayerId; return IsExposed[snitchId]; } - + private static bool IsSnitchTarget(PlayerControl target) - => HasEnabled && (target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)); - + => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)); + private static void CheckTask(PlayerControl snitch) { if (!snitch.IsAlive() || snitch.Is(CustomRoles.Madmate)) return; @@ -145,7 +146,7 @@ private static void CheckTask(PlayerControl snitch) public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { if (!IsThisRole(player.PlayerId) || player.Is(CustomRoles.Madmate)) return true; - + CheckTask(player); return true; } diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index af4febd45a..26746c0553 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -13,6 +13,7 @@ internal class Lawyer : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ private static OptionItem CanTargetImpostor; @@ -49,7 +50,7 @@ private enum ChangeRolesSelect CustomRoles.Doctor, ]; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Lawyer); CanTargetImpostor = BooleanOptionItem.Create(Id + 10, "LawyerCanTargetImpostor", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); @@ -81,12 +82,12 @@ public override void Add(byte playerId) foreach (var target in Main.AllPlayerControls) { if (playerId == target.PlayerId) continue; - else if (!CanTargetImpostor.GetBool() && target.Is(CustomRoleTypes.Impostor)) continue; + else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; - else if (!CanTargetCrewmate.GetBool() && target.Is(CustomRoleTypes.Crewmate)) continue; + else if (!CanTargetCrewmate.GetBool() && target.Is(Custom_Team.Crewmate)) continue; else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; - else if (target.Is(CustomRoleTypes.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; + else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; @@ -177,6 +178,8 @@ public override bool KnowRoleTarget(PlayerControl player, PlayerControl target) } private static string LawyerMark(PlayerControl seer, PlayerControl target, bool IsForMeeting = false) { + if (seer == null || target == null) return ""; + if (!seer.Is(CustomRoles.Lawyer)) { if (!TargetKnowsLawyer.GetBool()) return ""; @@ -216,6 +219,8 @@ private static void ChangeRole(PlayerControl lawyer) text = string.Format(text, Utils.ColorString(Utils.GetRoleColor(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]), GetString(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()].ToString()))); lawyer.Notify(text); } + /* + // Lonnie moment, makes the lawyer loose after death public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { if (Target.ContainsKey(target.PlayerId)) @@ -224,6 +229,7 @@ public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl SendRPC(target.PlayerId, SetTarget: false); } } + */ /*public static bool CheckExileTarget(GameData.PlayerInfo exiled, bool DecidedWinner, bool Check = false) { if (!HasEnabled) return false; From 6418b1498cb0fa3429d2a3b07ed8f14b1c8ad4c6 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 4 May 2024 17:56:44 -0400 Subject: [PATCH 010/778] uh --- Modules/CustomRolesHelper.cs | 449 ++++++++--------------------- Modules/ExtendedPlayerControl.cs | 14 +- Patches/MeetingHudPatch.cs | 32 +- Roles/AddOns/Common/Susceptible.cs | 338 +--------------------- Roles/Core/RoleBase.cs | 25 +- Roles/Crewmate/Jailer.cs | 7 +- Roles/Crewmate/Judge.cs | 3 +- Roles/Crewmate/Sheriff.cs | 9 +- Roles/Crewmate/Snitch.cs | 5 +- Roles/Neutral/Baker.cs | 5 +- Roles/Neutral/Berserker.cs | 3 +- Roles/Neutral/Lawyer.cs | 14 +- Roles/Neutral/PlagueBearer.cs | 3 +- Roles/Neutral/SoulCollector.cs | 51 ++-- 14 files changed, 233 insertions(+), 725 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 5601837242..4b4195df5c 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -6,20 +6,24 @@ using TOHE.Roles.Neutral; using static TOHE.Roles.Core.CustomRoleManager; using TOHE.Roles.AddOns.Impostor; +using TOHE.Roles.Core; +using System; +using static Il2CppSystem.Linq.Expressions.DebugViewWriter; namespace TOHE; public static class CustomRolesHelper { public static readonly CustomRoles[] AllRoles = EnumHelper.GetAllValues(); - public static readonly CustomRoleTypes[] AllRoleTypes = EnumHelper.GetAllValues(); + public static Dictionary DuplicatedRoles; + public static readonly Custom_Team[] AllRoleTypes = EnumHelper.GetAllValues(); public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor, Shapeshifter, Crewmate, Engineer, Scientist { // Vanilla roles if (role.IsVanilla()) return role; // Role base - if (role.GetStaticRoleClass() is not VanillaRole) return role.GetStaticRoleClass().ThisRoleBase; + if (role.GetStaticRoleClass() is not DefaultSetup) return role.GetStaticRoleClass().ThisRoleBase; //Default return role switch @@ -32,7 +36,7 @@ public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor } public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill button (Non-Impostor) - => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor) && !role.IsImpostor() + => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor) && !role.IsImpostor() || role is CustomRoles.Killer // FFA ? RoleTypes.Impostor : RoleTypes.GuardianAngel; @@ -54,90 +58,30 @@ public static bool HasImpKillButton(this PlayerControl player, bool considerVani //This is a overall check for vanilla clients to see if they are imp basis public static bool IsGhostRole(this CustomRoles role) { + if (role.GetStaticRoleClass().ThisRoleType is + Custom_RoleType.CrewmateGhosts or + Custom_RoleType.CrewmateVanillaGhosts or + Custom_RoleType.ImpostorGhosts) + return true; + return role is - CustomRoles.GuardianAngelTOHE or - CustomRoles.EvilSpirit or - CustomRoles.Warden or - CustomRoles.Hawk or - CustomRoles.Bloodmoon or - CustomRoles.Minion; + CustomRoles.EvilSpirit; } - public static bool IsAdditionRole(this CustomRoles role) + + /* + public static bool IsExperimental(this CustomRoles role) { return role is - CustomRoles.Lovers or - CustomRoles.LastImpostor or - CustomRoles.Ntr or - CustomRoles.Cyber or - CustomRoles.Madmate or - CustomRoles.Watcher or - CustomRoles.Admired or - CustomRoles.Flash or - CustomRoles.Torch or - CustomRoles.Seer or - CustomRoles.Bait or - CustomRoles.Burst or - CustomRoles.Diseased or - CustomRoles.Antidote or - CustomRoles.Fragile or - CustomRoles.VoidBallot or - CustomRoles.Aware or - CustomRoles.Swift or - CustomRoles.Cleansed or - CustomRoles.Gravestone or - CustomRoles.Trapper or - CustomRoles.Mare or - CustomRoles.Tiebreaker or - CustomRoles.Oblivious or - CustomRoles.Bewilder or - CustomRoles.Knighted or - CustomRoles.Workhorse or - CustomRoles.Fool or - CustomRoles.Autopsy or - CustomRoles.Necroview or - CustomRoles.Avanger or - CustomRoles.Sleuth or - CustomRoles.Clumsy or - CustomRoles.Nimble or - CustomRoles.Circumvent or - CustomRoles.Youtuber or - CustomRoles.Soulless or - CustomRoles.Loyal or - CustomRoles.Egoist or - CustomRoles.Recruit or - CustomRoles.TicketsStealer or - CustomRoles.Tricky or - CustomRoles.Schizophrenic or - CustomRoles.Mimic or - CustomRoles.Reach or - CustomRoles.Charmed or - CustomRoles.Infected or - CustomRoles.Onbound or - CustomRoles.Rebound or - CustomRoles.Mundane or - CustomRoles.Lazy or - CustomRoles.Rascal or - CustomRoles.Contagious or - CustomRoles.Guesser or - CustomRoles.Unreportable or - CustomRoles.Lucky or - CustomRoles.Unlucky or - CustomRoles.DoubleShot or - CustomRoles.Ghoul or - CustomRoles.Bloodlust or - CustomRoles.Overclocked or - CustomRoles.Stubborn or - CustomRoles.EvilSpirit or - CustomRoles.Hurried or - CustomRoles.Oiiai or - CustomRoles.Influenced or - CustomRoles.Silent or - CustomRoles.Rainbow or - CustomRoles.Susceptible or - CustomRoles.Statue or - CustomRoles.Tired; + CustomRoles.Disperser or + CustomRoles.Doppelganger or + CustomRoles.God or + CustomRoles.Quizmaster; } + */ + + // Add-ons + public static bool IsAdditionRole(this CustomRoles role) => role > CustomRoles.NotAssigned; public static bool IsAmneMaverick(this CustomRoles role) // ROLE ASSIGNING, NOT NEUTRAL TYPE { return role is @@ -245,15 +189,6 @@ CustomRoles.Juggernaut or CustomRoles.BloodKnight or CustomRoles.Cultist; } - public static bool IsCrewVenter(this PlayerControl target) - { - return target.Is(CustomRoles.EngineerTOHE) - || target.Is(CustomRoles.Mechanic) - || target.Is(CustomRoles.CopyCat) - || target.Is(CustomRoles.Telecommunication) && Telecommunication.CanUseVent() - || Knight.CheckCanUseVent(target) - || target.Is(CustomRoles.Nimble); - } public static bool IsTasklessCrewmate(this CustomRoles role) { // Based on Imp but counted as crewmate @@ -275,22 +210,18 @@ CustomRoles.Retributionist or CustomRoles.Benefactor or CustomRoles.Alchemist; } - public static bool IsCK(this CustomRoles role) + public static bool IsCrewKiller(this CustomRoles role) { - return role is - CustomRoles.Knight or - CustomRoles.Veteran or - CustomRoles.Judge or - CustomRoles.Bodyguard or - CustomRoles.Bastion or - CustomRoles.Reverie or - CustomRoles.Crusader or - CustomRoles.NiceGuesser or - CustomRoles.Deceiver or - CustomRoles.Retributionist or - CustomRoles.Sheriff or - CustomRoles.Vigilante or - CustomRoles.Jailer; + return role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.CrewmateKilling; + } + public static bool IsCrewVenter(this PlayerControl target) + { + return target.Is(CustomRoles.EngineerTOHE) + || target.Is(CustomRoles.Mechanic) + || target.Is(CustomRoles.CopyCat) + || target.Is(CustomRoles.Telecommunication) && Telecommunication.CanUseVent() + || Knight.CheckCanUseVent(target) + || target.Is(CustomRoles.Nimble); } public static bool IsNeutral(this CustomRoles role) { @@ -302,95 +233,17 @@ public static bool IsNeutral(this CustomRoles role) } public static bool IsNK(this CustomRoles role) { - if (role == CustomRoles.Arsonist && Arsonist.CanIgniteAnytime()) return true; - else if (role == CustomRoles.Quizmaster && Quizmaster.CanKillAfterMark) return true; - - return role is - CustomRoles.Jackal or - CustomRoles.Doppelganger or - CustomRoles.Bandit or - CustomRoles.Glitch or - CustomRoles.Sidekick or - CustomRoles.Huntsman or - CustomRoles.Infectious or - CustomRoles.Medusa or - CustomRoles.Pelican or - CustomRoles.Stalker or - CustomRoles.Juggernaut or - CustomRoles.Jinx or - CustomRoles.Poisoner or - CustomRoles.Wraith or - CustomRoles.HexMaster or - CustomRoles.Refugee or - CustomRoles.Parasite or - CustomRoles.PlagueDoctor or - CustomRoles.SerialKiller or - CustomRoles.Pyromaniac or - CustomRoles.Werewolf or - CustomRoles.PotionMaster or - CustomRoles.Demon or - CustomRoles.Pickpocket or - CustomRoles.Necromancer or - CustomRoles.Traitor or - CustomRoles.Shroud or - CustomRoles.Virus or - CustomRoles.BloodKnight or - CustomRoles.Spiritcaller or - CustomRoles.Agitater or - CustomRoles.RuthlessRomantic; + return role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.NeutralKilling; } public static bool IsNonNK(this CustomRoles role) // ROLE ASSIGNING, NOT NEUTRAL TYPE { - if (role == CustomRoles.Arsonist && !Arsonist.CanIgniteAnytime()) return true; - else if (role == CustomRoles.Quizmaster && !Quizmaster.CanKillAfterMark) return true; - - return role is - CustomRoles.Amnesiac or - CustomRoles.Follower or - CustomRoles.Hater or - CustomRoles.Lawyer or - CustomRoles.Imitator or - CustomRoles.Maverick or - CustomRoles.Opportunist or - CustomRoles.Pursuer or - CustomRoles.Shaman or - CustomRoles.CursedSoul or - CustomRoles.Doomsayer or - CustomRoles.Executioner or - CustomRoles.Innocent or - CustomRoles.Jester or - CustomRoles.Sunnyboy or - CustomRoles.Masochist or - CustomRoles.Seeker or - CustomRoles.Pixie or - CustomRoles.Collector or - CustomRoles.Cultist or - CustomRoles.Phantom or - CustomRoles.Pirate or - CustomRoles.Terrorist or - CustomRoles.Vulture or - CustomRoles.Taskinator or - CustomRoles.Workaholic or - CustomRoles.Solsticer or - CustomRoles.God or - CustomRoles.Vector or - CustomRoles.Revolutionist or - CustomRoles.Romantic or - CustomRoles.VengefulRomantic or - CustomRoles.SchrodingersCat or - CustomRoles.Provocateur; + return role.IsNB() || role.IsNE() || role.IsNC(); } public static bool IsNA(this CustomRoles role) { - return role is - CustomRoles.PlagueBearer or - CustomRoles.Pestilence or - CustomRoles.Berserker or - CustomRoles.War or - CustomRoles.SoulCollector or - CustomRoles.Death or - CustomRoles.Baker or - CustomRoles.Famine; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralApocalypse + || role.IsTNA(); } public static bool IsTNA(this CustomRoles role) { @@ -402,128 +255,31 @@ CustomRoles.Death or } public static bool IsNB(this CustomRoles role) { - return role is - CustomRoles.Amnesiac or - CustomRoles.Follower or - CustomRoles.Hater or - CustomRoles.Imitator or - CustomRoles.Lawyer or - CustomRoles.Maverick or - CustomRoles.Opportunist or - CustomRoles.Pursuer or - CustomRoles.Shaman or - CustomRoles.Taskinator or - CustomRoles.God or - CustomRoles.Romantic or - CustomRoles.VengefulRomantic or - CustomRoles.Pixie or - CustomRoles.SchrodingersCat or - CustomRoles.Sunnyboy; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralBenign; } public static bool IsNE(this CustomRoles role) { - return role is - CustomRoles.CursedSoul or - CustomRoles.Doomsayer or - CustomRoles.Executioner or - CustomRoles.Innocent or - CustomRoles.Jester or - CustomRoles.Masochist or - CustomRoles.Seeker; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralEvil; } public static bool IsNC(this CustomRoles role) { - return role is - CustomRoles.Collector or - CustomRoles.Cultist or - CustomRoles.Phantom or - CustomRoles.Vector or - CustomRoles.SoulCollector or - CustomRoles.Pirate or - CustomRoles.Terrorist or - CustomRoles.Vulture or - CustomRoles.Workaholic or - CustomRoles.Solsticer or - CustomRoles.Revolutionist or - CustomRoles.Provocateur; + return role.GetStaticRoleClass().ThisRoleType + is Custom_RoleType.NeutralChaos; } public static bool IsImpostor(this CustomRoles role) // IsImp { + if (role.GetStaticRoleClass().ThisRoleType is + Custom_RoleType.ImpostorVanilla or + Custom_RoleType.ImpostorKilling or + Custom_RoleType.ImpostorSupport or + Custom_RoleType.ImpostorConcealing or + Custom_RoleType.ImpostorHindering or + Custom_RoleType.ImpostorGhosts) return true; return role is CustomRoles.Impostor or - CustomRoles.Shapeshifter or - CustomRoles.ShapeshifterTOHE or - CustomRoles.ImpostorTOHE or - CustomRoles.Consigliere or - CustomRoles.Wildling or - CustomRoles.Morphling or - CustomRoles.BountyHunter or - CustomRoles.Vampire or - CustomRoles.Vampiress or - CustomRoles.Witch or - CustomRoles.Vindicator or - CustomRoles.ShapeMaster or - CustomRoles.Zombie or - CustomRoles.Warlock or - CustomRoles.Undertaker or - CustomRoles.RiftMaker or - CustomRoles.Ninja or - CustomRoles.Bloodmoon or - CustomRoles.Anonymous or - CustomRoles.Visionary or - CustomRoles.Miner or - CustomRoles.Escapist or - CustomRoles.Mercenary or - CustomRoles.Underdog or - CustomRoles.Inhibitor or - CustomRoles.Councillor or - CustomRoles.Saboteur or - CustomRoles.Puppeteer or - CustomRoles.TimeThief or - CustomRoles.Trickster or - CustomRoles.Nemesis or - CustomRoles.Mastermind or - CustomRoles.Chronomancer or - CustomRoles.Stealth or - CustomRoles.Penguin or - CustomRoles.KillingMachine or - CustomRoles.Fireworker or - CustomRoles.Sniper or - CustomRoles.EvilTracker or - CustomRoles.EvilGuesser or - CustomRoles.AntiAdminer or - CustomRoles.Arrogance or - CustomRoles.Bomber or - CustomRoles.Nuker or - CustomRoles.Kamikaze or - CustomRoles.Scavenger or - CustomRoles.Trapster or - CustomRoles.Gangster or - CustomRoles.Cleaner or - CustomRoles.Lightning or - CustomRoles.Greedy or - CustomRoles.Ludopath or - CustomRoles.Godfather or - CustomRoles.CursedWolf or - CustomRoles.SoulCatcher or - CustomRoles.QuickShooter or - CustomRoles.Eraser or - CustomRoles.Butcher or - CustomRoles.Hangman or - CustomRoles.Bard or - CustomRoles.Swooper or - CustomRoles.Disperser or - CustomRoles.Dazzler or - CustomRoles.Deathpact or - CustomRoles.Devourer or - CustomRoles.Camouflager or - CustomRoles.Twister or - CustomRoles.Lurker or - CustomRoles.EvilMini or - CustomRoles.Blackmailer or - CustomRoles.Pitfall or - CustomRoles.Instigator or - CustomRoles.Minion; + CustomRoles.Shapeshifter; } public static bool IsAbleToBeSidekicked(this CustomRoles role) @@ -539,10 +295,10 @@ CustomRoles.Virus or public static bool IsMadmate(this CustomRoles role) { + if (role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.Madmate) return true; + return role is - CustomRoles.Crewpostor or - CustomRoles.Refugee or - CustomRoles.Parasite; + CustomRoles.Refugee; } /// /// Role Changes the Crewmates Team, Including changing to Impostor. @@ -697,7 +453,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Mundane: - if (pc.CanUseKillButton() || pc.GetCustomRole().IsTasklessCrewmate() || pc.Is(CustomRoleTypes.Impostor)) + if (pc.CanUseKillButton() || pc.GetCustomRole().IsTasklessCrewmate() || pc.Is(Custom_Team.Impostor)) return false; if ((pc.GetCustomRole().IsCrewmate() && !Mundane.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Mundane.CanBeOnNeutral.GetBool())) return false; @@ -768,14 +524,14 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; if (Options.GuesserMode.GetBool()) { - if (DoubleShot.ImpCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && (pc.Is(CustomRoleTypes.Impostor) && !Options.ImpostorsCanGuess.GetBool())) + if (DoubleShot.ImpCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && (pc.Is(Custom_Team.Impostor) && !Options.ImpostorsCanGuess.GetBool())) return false; - if (DoubleShot.CrewCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && (pc.Is(CustomRoleTypes.Crewmate) && !Options.CrewmatesCanGuess.GetBool())) + if (DoubleShot.CrewCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && (pc.Is(Custom_Team.Crewmate) && !Options.CrewmatesCanGuess.GetBool())) return false; if (DoubleShot.NeutralCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Doomsayer) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.GetCustomRole().IsNK() && !Options.NeutralKillersCanGuess.GetBool()))) return false; } - if ((pc.Is(CustomRoleTypes.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(CustomRoleTypes.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(CustomRoleTypes.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool())) + if ((pc.Is(Custom_Team.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool())) return false; break; @@ -843,11 +599,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Fragile: if (pc.Is(CustomRoles.Lucky) - // || pc.Is(CustomRoles.Luckey) + || pc.Is(CustomRoles.Veteran) || pc.Is(CustomRoles.Guardian) || pc.Is(CustomRoles.Medic) || pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.Jinx) || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.CursedWolf) @@ -872,6 +627,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; + case CustomRoles.Glow: + if ((pc.GetCustomRole().IsCrewmate() && !Glow.CrewCanBeGlow.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Glow.NeutralCanBeGlow.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Glow.ImpCanBeGlow.GetBool())) + return false; + break; case CustomRoles.Antidote: if (pc.Is(CustomRoles.Diseased) || pc.Is(CustomRoles.Solsticer)) return false; @@ -933,7 +692,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Lucky: if (pc.Is(CustomRoles.Guardian) - // || pc.Is(CustomRoles.Luckey) + || pc.Is(CustomRoles.Veteran) || pc.Is(CustomRoles.Unlucky) || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.Fragile)) @@ -943,8 +702,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Unlucky: - if (//pc.Is(CustomRoles.Luckey) - pc.Is(CustomRoles.Vector) + if (pc.Is(CustomRoles.Vector) || pc.Is(CustomRoles.Lucky) || pc.Is(CustomRoles.Lucky) || pc.Is(CustomRoles.Vector) @@ -955,16 +713,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; - case CustomRoles.Ntr: - if (pc.Is(CustomRoles.Lovers) - || pc.Is(CustomRoles.Hater) - || pc.Is(CustomRoles.GuardianAngelTOHE) - || pc.Is(CustomRoles.RuthlessRomantic) - || pc.Is(CustomRoles.Romantic) - || pc.Is(CustomRoles.VengefulRomantic)) - return false; - break; - case CustomRoles.Madmate: if (pc.Is(CustomRoles.Sidekick) || pc.Is(CustomRoles.SuperStar) @@ -1066,11 +814,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Ludopath) || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Vampire) - || pc.Is(CustomRoles.Vampiress) || pc.Is(CustomRoles.Arrogance) || pc.Is(CustomRoles.LastImpostor) || pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.Trapster) || pc.Is(CustomRoles.Onbound) || pc.Is(CustomRoles.Rebound) @@ -1082,17 +828,16 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Swift: if (pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.Trapster) || pc.Is(CustomRoles.Kamikaze) || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Vampire) - || pc.Is(CustomRoles.Vampiress) || pc.Is(CustomRoles.Scavenger) || pc.Is(CustomRoles.Puppeteer) || pc.Is(CustomRoles.Mastermind) || pc.Is(CustomRoles.Warlock) || pc.Is(CustomRoles.Witch) + || pc.Is(CustomRoles.Penguin) || pc.Is(CustomRoles.Nemesis) || pc.Is(CustomRoles.Mare) || pc.Is(CustomRoles.Clumsy) @@ -1114,7 +859,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Circumvent: - if (pc.GetCustomRole() is CustomRoles.Vampire or CustomRoles.Vampiress && !Vampire.CheckCanUseVent() + if (pc.GetCustomRole() is CustomRoles.Vampire && !Vampire.CheckCanUseVent() || pc.Is(CustomRoles.Witch) && Witch.ModeSwitchActionOpt.GetValue() == 1 || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Wildling) @@ -1128,7 +873,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Clumsy: if (pc.Is(CustomRoles.Swift) || pc.Is(CustomRoles.Bomber) - || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.KillingMachine)) return false; if (!pc.GetCustomRole().IsImpostor()) @@ -1333,13 +1077,17 @@ CustomRoles.GuardianAngel or CustomRoles.Impostor or CustomRoles.Shapeshifter; } - public static CustomRoleTypes GetCustomRoleTypes(this CustomRoles role) + public static Custom_Team GetCustomRoleTeam(this CustomRoles role) + { + Custom_Team team = Custom_Team.Crewmate; + if (role.IsImpostor()) team = Custom_Team.Impostor; + if (role.IsNeutral()) team = Custom_Team.Neutral; + if (role.IsAdditionRole()) team = Custom_Team.Addon; + return team; + } + public static Custom_RoleType GetCustomRoleType(this CustomRoles role) { - CustomRoleTypes type = CustomRoleTypes.Crewmate; - if (role.IsImpostor()) type = CustomRoleTypes.Impostor; - if (role.IsNeutral()) type = CustomRoleTypes.Neutral; - if (role.IsAdditionRole()) type = CustomRoleTypes.Addon; - return type; + return role.GetStaticRoleClass().ThisRoleType; } public static bool RoleExist(this CustomRoles role, bool countDead = false) => Main.AllPlayerControls.Any(x => x.Is(role) && (x.IsAlive() || countDead)); public static int GetCount(this CustomRoles role) @@ -1407,8 +1155,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Shroud => CountTypes.Shroud, CustomRoles.Werewolf => CountTypes.Werewolf, CustomRoles.Wraith => CountTypes.Wraith, - CustomRoles.Pestilence or CustomRoles.PlagueBearer or CustomRoles.SoulCollector or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.Berserker or CustomRoles.War - => CountTypes.Apocalypse, + var r when r.IsNA() => CountTypes.Apocalypse, CustomRoles.Agitater => CountTypes.Agitater, CustomRoles.Parasite => CountTypes.Impostor, CustomRoles.SerialKiller => CountTypes.SerialKiller, @@ -1537,13 +1284,43 @@ CustomRoles.Pestilence or CustomRoles.PlagueBearer or CustomRoles.SoulCollector }; public static bool HasSubRole(this PlayerControl pc) => Main.PlayerStates[pc.PlayerId].SubRoles.Any(); } -public enum CustomRoleTypes +public enum Custom_Team { Crewmate, Impostor, Neutral, Addon, } +public enum Custom_RoleType +{ + // Impostors + ImpostorVanilla, + ImpostorKilling, + ImpostorSupport, + ImpostorConcealing, + ImpostorHindering, + ImpostorGhosts, + + Madmate, + + // Crewmate + CrewmateVanilla, + CrewmateVanillaGhosts, + CrewmateBasic, + CrewmateSupport, + CrewmateKilling, + CrewmatePower, + CrewmateGhosts, + + // Neutral + NeutralBenign, + NeutralEvil, + NeutralChaos, + NeutralKilling, + NeutralApocalypse, + + None +} public enum CountTypes { OutOfGame, diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 79aea582f8..0058dff264 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -130,8 +130,8 @@ public static void RpcSetNameEx(this PlayerControl player, string name) public static void RpcSetNamePrivate(this PlayerControl player, string name, bool DontShowOnModdedClient = false, PlayerControl seer = null, bool force = false) { - //player: 名前の変更対象 - //seer: 上の変更を確認することができるプレイヤー + //player: player whose name needs to be changed + //seer: player who can see name changes if (player == null || name == null || !AmongUsClient.Instance.AmHost) return; if (seer == null) seer = player; @@ -145,10 +145,12 @@ public static void RpcSetNamePrivate(this PlayerControl player, string name, boo Logger.Info($"Set:{player?.Data?.PlayerName}:{name} for {seer.GetNameWithRole().RemoveHtmlTags()}", "RpcSetNamePrivate"); var clientId = seer.GetClientId(); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, clientId); - writer.Write(name); - writer.Write(DontShowOnModdedClient); - AmongUsClient.Instance.FinishRpcImmediately(writer); + var sender = CustomRpcSender.Create(name: $"SetNamePrivate"); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName, clientId) + .Write(name) + .Write(DontShowOnModdedClient) + .EndRpc(); + sender.SendMessage(); } public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, int clientId) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index c7b32e0d11..b8ae66e73c 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -545,6 +545,7 @@ private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, pa Witch.OnCheckForEndVoting(deathReason, playerIds); HexMaster.OnCheckForEndVoting(deathReason, playerIds); Virus.OnCheckForEndVoting(deathReason, playerIds); + SoulCollector.OnCheckForEndVoting(deathReason, playerIds); foreach (var playerId in playerIds) { @@ -810,7 +811,36 @@ public static void NotifyRoleSkillOnMeetingStart() string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); } - + // Apocalypse Notify + if (CustomRoles.Death.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("SoulCollectorTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Death), GetString("ApocalypseIsNigh"))); + }, 3f, "Death Apocalypse Notify"); + } + if (CustomRoles.Famine.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("BakerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Famine), GetString("ApocalypseIsNigh"))); + }, 3f, "Famine Apocalypse Notify"); + } + if (CustomRoles.War.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("BerserkerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.War), GetString("ApocalypseIsNigh"))); + }, 3f, "War Apocalypse Notify"); + } + if (CustomRoles.Pestilence.RoleExist()) + { + _ = new LateTask(() => + { + AddMsg(string.Format(GetString("PestilenceTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Pestilence), GetString("ApocalypseIsNigh"))); + }, 3f, "Pestilence Apocalypse Notify"); + } + string MimicMsg = ""; foreach (var pc in Main.AllPlayerControls) { diff --git a/Roles/AddOns/Common/Susceptible.cs b/Roles/AddOns/Common/Susceptible.cs index 5418aa0e10..13b1d7d626 100644 --- a/Roles/AddOns/Common/Susceptible.cs +++ b/Roles/AddOns/Common/Susceptible.cs @@ -40,346 +40,12 @@ public static void CallEnabledAndChange(PlayerControl victim) if (EnabledDeathReasons.GetBool()) { Logger.Info($"{victim.GetNameWithRole().RemoveHtmlTags()} had the death reason {randomReason}", "Susceptible"); - switch (randomReason) - { - case PlayerState.DeathReason.Eaten: - if (!Pelican.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; + Main.PlayerStates[victim.PlayerId].deathReason = randomReason.DeathReasonIsEnable() ? randomReason : Main.PlayerStates[victim.PlayerId].deathReason; - case PlayerState.DeathReason.Spell: - if (!Witch.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Hex: - if (CustomRoles.HexMaster.IsEnable()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Curse: - if (!CustomRoles.CursedWolf.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Jinx: - if (!Jinx.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - - case PlayerState.DeathReason.Shattered: - if (!CustomRoles.Lovers.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - - case PlayerState.DeathReason.Bite: - if (!Vampire.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Poison: - if (!Poisoner.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Bombed: - if (!Bomber.HasEnabled && !Burst.IsEnable && !Trapster.HasEnabled && !Fireworker.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Misfire: - if (!ChiefOfPolice.IsEnable) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Torched: - if (!CustomRoles.Arsonist.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Sniped: - if (!Sniper.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Revenge: - if (!CustomRoles.Avanger.RoleExist() && !CustomRoles.Retributionist.RoleExist() && !CustomRoles.Nemesis.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Gambled: - if (!CustomRoles.EvilGuesser.RoleExist() && !CustomRoles.NiceGuesser.RoleExist() && !Options.GuesserMode.GetBool()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Quantization: - if (!Lightning.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Overtired: - if (!CustomRoles.Workaholic.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Ashamed: - if (!CustomRoles.Workaholic.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.PissedOff: - if (!CustomRoles.Pestilence.RoleExist() && !CustomRoles.Provocateur.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Dismembered: - if (!CustomRoles.Butcher.RoleExist()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.LossOfHead: - if (!Hangman.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Trialed: - if (!Judge.HasEnabled && !Councillor.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Infected: - if (!Infectious.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Hack: - if (!Glitch.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Pirate: - if (!Pirate.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Shrouded: - if (!Shroud.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - case PlayerState.DeathReason.Mauled: - if (!Werewolf.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.Slice: - if (!Hawk.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.BloodLet: - if (!Bloodmoon.HasEnabled) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.Armageddon: - if (!CustomRoles.SoulCollector.IsEnable()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - case PlayerState.DeathReason.Starved: - if (!CustomRoles.Baker.IsEnable()) - { - Main.PlayerStates[victim.PlayerId].deathReason = PlayerState.DeathReason.Kill; - } - else - { - goto default; - } - break; - - default: - while (Main.PlayerStates[victim.PlayerId].deathReason != randomReason) - Main.PlayerStates[victim.PlayerId].deathReason = randomReason; - break; - } } else { - while (Main.PlayerStates[victim.PlayerId].deathReason != randomReason) - Main.PlayerStates[victim.PlayerId].deathReason = randomReason; + Main.PlayerStates[victim.PlayerId].deathReason = randomReason.DeathReasonIsEnable(true) ? randomReason : Main.PlayerStates[victim.PlayerId].deathReason; } } } diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 1aeaaf2e8c..d36d924b41 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -33,27 +33,34 @@ public virtual void Remove(byte playerId) /// public abstract CustomRoles ThisRoleBase { get; } + /// + /// Defines the role type + /// + public abstract Custom_RoleType ThisRoleType { get; } + /// /// A generic method to set if a impostor/SS base may use kill button. /// - public virtual bool CanUseKillButton(PlayerControl pc) => pc.Is(CustomRoleTypes.Impostor) && pc.IsAlive(); + public virtual bool CanUseKillButton(PlayerControl pc) => pc.Is(Custom_Team.Impostor) && pc.IsAlive(); /// /// A generic method to set if a impostor/SS base may vent. /// - public virtual bool CanUseImpostorVentButton(PlayerControl pc) => pc.Is(CustomRoleTypes.Impostor) && pc.IsAlive(); + public virtual bool CanUseImpostorVentButton(PlayerControl pc) => pc.Is(Custom_Team.Impostor) && pc.IsAlive(); /// /// A generic method to set if the role can use sabotage. /// - public virtual bool CanUseSabotage(PlayerControl pc) => pc.Is(CustomRoleTypes.Impostor); + public virtual bool CanUseSabotage(PlayerControl pc) => pc.Is(Custom_Team.Impostor); /// /// When the player presses the sabotage button /// public virtual bool OnSabotage(PlayerControl pc) => pc != null; - //public virtual void SetupCustomOption() - //{ } + public virtual void SetupCustomOption() + { } + + public virtual bool IsExperimental => false; /// /// A generic method to send a CustomRole's Gameoptions. @@ -314,8 +321,14 @@ public virtual void SetAbilityButtonText(HudManager hud, byte playerId) /// Set PlayerName text for the role /// public virtual string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) => string.Empty; - + // + // // Add Mark/LowerText/Suffix for player + // When using this code remember the seer can also see the target, therefore.. + // + // return string.empty if "seer != seen" if only seer should have it + // otherwise make some list or byte or smt of sorts to only get the target. + // not needed if both should have it. public virtual string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => string.Empty; public virtual string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) => string.Empty; public virtual string GetSuffix(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => string.Empty; diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 8a22ff09e5..9f372b8803 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -14,6 +14,7 @@ internal class Jailer : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ private static OptionItem JailCooldown; @@ -31,7 +32,7 @@ internal class Jailer : RoleBase private static readonly Dictionary JailerHasExe = []; private static readonly Dictionary JailerDidVote = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Jailer); JailCooldown = FloatOptionItem.Create(Id + 10, "JailerJailCooldown", new(0f, 999f, 1f), 15f, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]) @@ -74,7 +75,7 @@ public override void Remove(byte playerId) JailerHasExe.Remove(playerId); JailerDidVote.Remove(playerId); } - + public override bool CanUseKillButton(PlayerControl pc) => true; public static bool IsTarget(byte playerId) => JailerTarget.ContainsValue(playerId); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).IsAlive() ? JailCooldown.GetFloat() : 300f; public override string GetProgressText(byte playerId, bool cooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer).ShadeColor(0.25f), JailerExeLimit.TryGetValue(playerId, out var exeLimit) ? $"({exeLimit})" : "Invalid"); @@ -181,7 +182,7 @@ private static bool CanBeExecuted(CustomRoles role) (role.IsNE() && NECanBeExe.GetBool()) || (role.IsNK() && NKCanBeExe.GetBool()) || (role.IsNA() && NACanBeExe.GetBool()) || - (role.IsCK() && CKCanBeExe.GetBool()) || + (role.IsCrewKiller() && CKCanBeExe.GetBool()) || (role.IsImpostorTeamV3())); } diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 401bcd486b..c0e9151012 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -17,6 +17,7 @@ internal class Judge : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ public static OptionItem TrialLimitPerMeeting; @@ -168,7 +169,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) else if (target.Is(CustomRoles.Trickster)) judgeSuicide = true; else if (target.Is(CustomRoles.Madmate) && CanTrialMadmate.GetBool()) judgeSuicide = false; else if (target.Is(CustomRoles.Charmed) && CanTrialCharmed.GetBool()) judgeSuicide = false; - else if (target.GetCustomRole().IsCK() && CanTrialCrewKilling.GetBool()) judgeSuicide = false; + else if (target.GetCustomRole().IsCrewKiller() && CanTrialCrewKilling.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNK() && CanTrialNeutralK.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNB() && CanTrialNeutralB.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNE() && CanTrialNeutralE.GetBool()) judgeSuicide = false; diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index aa99267c8c..42f098284b 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -13,6 +13,7 @@ internal class Sheriff : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ private static OptionItem KillCooldown; @@ -46,7 +47,7 @@ private enum KillOption SheriffCanKillSeparately } - public static void SetupCustomOption() + public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Sheriff); KillCooldown = FloatOptionItem.Create(Id + 10, "KillCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]) @@ -191,10 +192,10 @@ public static bool CanBeKilledBySheriff(PlayerControl player) { CustomRoles.Trickster => false, CustomRoles.Pestilence => true, - _ => cRole.GetCustomRoleTypes() switch + _ => cRole.GetCustomRoleTeam() switch { - CustomRoleTypes.Impostor => true, - CustomRoleTypes.Neutral => CanKillNeutrals.GetBool() && (CanKillNeutralsMode.GetValue() == 0 || (!KillTargetOptions.TryGetValue(cRole, out var option) || option.GetBool())), + Custom_Team.Impostor => true, + Custom_Team.Neutral => CanKillNeutrals.GetBool() && (CanKillNeutralsMode.GetValue() == 0 || (!KillTargetOptions.TryGetValue(cRole, out var option) || option.GetBool())), _ => CanKill, } }; diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index 4b1f601424..fe44c9861f 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -13,6 +13,7 @@ internal class Snitch : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ private static readonly Color RoleColor = Utils.GetRoleColor(CustomRoles.Snitch); @@ -37,7 +38,7 @@ internal class Snitch : RoleBase private static readonly HashSet TargetList = []; private static readonly Dictionary TargetColorlist = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Snitch); OptionEnableTargetArrow = BooleanOptionItem.Create(Id + 10, "SnitchEnableTargetArrow", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); @@ -95,7 +96,7 @@ private static bool GetExpose(PlayerControl pc) } private static bool IsSnitchTarget(PlayerControl target) - => HasEnabled && (target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)); + => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)); private static void CheckTask(PlayerControl snitch) { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index e9959e66a0..8f5daced2b 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -12,11 +12,12 @@ namespace TOHE.Roles.Neutral; internal class Baker : RoleBase { //===========================SETUP================================\\ - private static readonly int Id = 28200; + private static readonly int Id = 28400; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ private static OptionItem BreadNeededToTransform; @@ -24,7 +25,7 @@ internal class Baker : RoleBase private static readonly Dictionary> BreadList = []; private static bool CanUseAbility; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Baker, 1, zeroOne: false); BreadNeededToTransform = IntegerOptionItem.Create(Id + 10, "BakerBreadNeededToTransform", new(1, 5, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]) diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 10fc01bebb..af1369b108 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -16,6 +16,7 @@ internal class Berserker : RoleBase public static bool HasEnabled => PlayerIds.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ private static OptionItem BerserkerKillCooldown; @@ -39,7 +40,7 @@ internal class Berserker : RoleBase private static readonly Dictionary BerserkerKillMax = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Berserker, 1, zeroOne: false); BerserkerKillCooldown = FloatOptionItem.Create(Id + 2, "BerserkerKillCooldown", new(25f, 250f, 2.5f), 35f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Berserker]) diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index af4febd45a..26746c0553 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -13,6 +13,7 @@ internal class Lawyer : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ private static OptionItem CanTargetImpostor; @@ -49,7 +50,7 @@ private enum ChangeRolesSelect CustomRoles.Doctor, ]; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Lawyer); CanTargetImpostor = BooleanOptionItem.Create(Id + 10, "LawyerCanTargetImpostor", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); @@ -81,12 +82,12 @@ public override void Add(byte playerId) foreach (var target in Main.AllPlayerControls) { if (playerId == target.PlayerId) continue; - else if (!CanTargetImpostor.GetBool() && target.Is(CustomRoleTypes.Impostor)) continue; + else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; - else if (!CanTargetCrewmate.GetBool() && target.Is(CustomRoleTypes.Crewmate)) continue; + else if (!CanTargetCrewmate.GetBool() && target.Is(Custom_Team.Crewmate)) continue; else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; - else if (target.Is(CustomRoleTypes.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; + else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; @@ -177,6 +178,8 @@ public override bool KnowRoleTarget(PlayerControl player, PlayerControl target) } private static string LawyerMark(PlayerControl seer, PlayerControl target, bool IsForMeeting = false) { + if (seer == null || target == null) return ""; + if (!seer.Is(CustomRoles.Lawyer)) { if (!TargetKnowsLawyer.GetBool()) return ""; @@ -216,6 +219,8 @@ private static void ChangeRole(PlayerControl lawyer) text = string.Format(text, Utils.ColorString(Utils.GetRoleColor(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]), GetString(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()].ToString()))); lawyer.Notify(text); } + /* + // Lonnie moment, makes the lawyer loose after death public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { if (Target.ContainsKey(target.PlayerId)) @@ -224,6 +229,7 @@ public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl SendRPC(target.PlayerId, SetTarget: false); } } + */ /*public static bool CheckExileTarget(GameData.PlayerInfo exiled, bool DecidedWinner, bool Check = false) { if (!HasEnabled) return false; diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 5c4fc498d5..2e01af7167 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -15,6 +15,7 @@ internal class PlagueBearer : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ private static OptionItem PlagueBearerCDOpt; @@ -27,7 +28,7 @@ internal class PlagueBearer : RoleBase //private static readonly Dictionary PestilenceCD = []; private static readonly HashSet PestilenceList = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.PlagueBearer, 1, zeroOne: false); PlagueBearerCDOpt = FloatOptionItem.Create(Id + 10, "PlagueBearerCD", new(0f, 180f, 2.5f), 22.5f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueBearer]) diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 95f4e1c333..2da3bc8370 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -12,6 +12,7 @@ internal class SoulCollector : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ private static OptionItem SoulCollectorPointsOpt; @@ -22,7 +23,7 @@ internal class SoulCollector : RoleBase private static readonly Dictionary SoulCollectorPoints = []; private static readonly Dictionary DidVote = []; - public static void SetupCustomOption() + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulCollector, 1, zeroOne: false); SoulCollectorPointsOpt = IntegerOptionItem.Create(Id + 10, "SoulCollectorPointsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]) @@ -53,7 +54,7 @@ public override void Add(byte playerId) private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WritePacked((int)CustomRoles.SoulCollector); //SetSoulCollectorLimit + writer.WritePacked((int)CustomRoles.Collector); //SetSoulCollectorLimit writer.Write(playerId); writer.Write(SoulCollectorPoints[playerId]); writer.Write(SoulCollectorTarget[playerId]); @@ -109,8 +110,12 @@ public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) DidVote[playerId] = false; if (GetPassiveSouls.GetBool()) { - SoulCollectorPoints[playerId]++; - Utils.SendMessage(GetString("PassiveSoulGained"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + SoulCollectorPoints[playerId]++; + _ = new LateTask(() => + { + Utils.SendMessage(GetString("PassiveSoulGained"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + + }, 3f, "Set Chat Visible for Everyone"); } } } @@ -134,35 +139,37 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } } - public static void KillIfNotEjected(PlayerControl player) + public override void AfterMeetingTasks() { + PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); + if (SoulCollectorPoints[sc.PlayerId] < SoulCollectorPointsOpt.GetInt()) return; + sc.RpcSetCustomRole(CustomRoles.Death); + sc.Notify(GetString("SoulCollectorToDeath")); + sc.RpcGuardAndKill(sc); + } + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + { + if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; + if (!CustomRoles.Death.RoleExist()) return; + if (exileIds.Contains(playerIdList.First())) return; var deathList = new List(); - if (Main.AfterMeetingDeathPlayers.ContainsKey(player.PlayerId)) return; + PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); foreach (var pc in Main.AllAlivePlayerControls) { if (pc.IsNeutralApocalypse()) continue; - if (player != null && player.IsAlive()) + if (sc != null && sc.IsAlive()) { if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) { - pc.SetRealKiller(player); + pc.SetRealKiller(sc); deathList.Add(pc.PlayerId); } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } } - else return; + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [..deathList]); - } - public override void OnFixedUpdate(PlayerControl player) - { - if (SoulCollectorPoints[player.PlayerId] < SoulCollectorPointsOpt.GetInt() || player.GetCustomRole() is CustomRoles.Death) return; - player.RpcSetCustomRole(CustomRoles.Death); - player.Notify(GetString("SoulCollectorToDeath")); - player.RpcGuardAndKill(player); - KillIfNotEjected(player); + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [.. deathList]); } } \ No newline at end of file From e5d1d9bc6bee29a31947b9b3eb94976951d9c8d1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 4 May 2024 21:41:02 -0400 Subject: [PATCH 011/778] make pb show up in options --- Modules/OptionHolder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index d64195ba69..4cc166e984 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -842,6 +842,8 @@ public static void Load() CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralApocalypse).ForEach(r => r.SetupCustomOption()); + CustomRoles.PlagueBearer.GetStaticRoleClass().SetupCustomOption(); + #endregion #region Add-Ons Settings From cfd780d4f61be29110a3abf1bac69fb05ac643c0 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 5 May 2024 13:44:59 -0400 Subject: [PATCH 012/778] NA to AntiBlackout --- Modules/AntiBlackout.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 5128cdaae5..d76a49579a 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -33,6 +33,7 @@ public static bool CheckBlackOut() else if (pc.GetCustomRole().IsNK()) NeutralKillers.Add(pc.PlayerId); // Neutral Killers else if (pc.Is(CustomRoles.Cultist)) NeutralKillers.Add(pc.PlayerId); + else if (pc.GetCustomRole().IsNA()) NeutralKillers.Add(pc.PlayerId); // Neutral Apocalypse else Crewmates.Add(pc.PlayerId); } From ea29b6c7cc3e8b241206b9c8e64868b9b3f9ae26 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 5 May 2024 13:51:15 -0400 Subject: [PATCH 013/778] NA to Fortune Teller Results --- Roles/Crewmate/FortuneTeller.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 685076c2dc..a045823a6a 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -211,6 +211,7 @@ public override void OnVote(PlayerControl player, PlayerControl target) CustomRoles.Greedy, CustomRoles.Merchant, CustomRoles.SoulCollector, + CustomRoles.Baker, CustomRoles.Trickster], [CustomRoles.Pestilence, @@ -227,6 +228,8 @@ public override void OnVote(PlayerControl player, PlayerControl target) CustomRoles.Arrogance, CustomRoles.KillingMachine, CustomRoles.Berserker, + CustomRoles.War, + CustomRoles.Death, CustomRoles.Butcher], [CustomRoles.Coroner, @@ -235,7 +238,8 @@ public override void OnVote(PlayerControl player, PlayerControl target) CustomRoles.Tracefinder, CustomRoles.Seeker, CustomRoles.Tracker, - CustomRoles.Romantic, + CustomRoles.Romantic, + CustomRoles.Famine, CustomRoles.SchrodingersCat], [CustomRoles.Bodyguard, From de1bf0966190d6a717b49c74e74c631fb8287503 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 5 May 2024 13:56:01 -0400 Subject: [PATCH 014/778] unautomate options plaguebearer doesn't show up when doing CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralApocalypse).ForEach(r => r.SetupCustomOption()); so i went back to the previous option style for NA's so that they can be alphabetized --- Modules/OptionHolder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 93e448ae59..bd0ba7bd92 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -845,10 +845,14 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralApocalypse).ForEach(r => r.SetupCustomOption()); + CustomRoles.Baker.GetStaticRoleClass().SetupCustomOption(); + + CustomRoles.Berserker.GetStaticRoleClass().SetupCustomOption(); CustomRoles.PlagueBearer.GetStaticRoleClass().SetupCustomOption(); + CustomRoles.SoulCollector.GetStaticRoleClass().SetupCustomOption(); + #endregion #region Add-Ons Settings From 530298d37fa6006f19143a2c885bd7acf8024edd Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 7 May 2024 18:57:11 -0400 Subject: [PATCH 015/778] make famine do more needs to update FamineInfoLong and find out why OnCheckForEndVoting doesn't work for ANY role anymore --- Modules/CustomRolesHelper.cs | 7 ++- Patches/MeetingHudPatch.cs | 1 + Resources/Lang/en_US.json | 5 ++ Roles/Neutral/Baker.cs | 113 ++++++++++++++++++++++++++++------- main.cs | 3 + 5 files changed, 106 insertions(+), 23 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 69b6af1518..ecce8998ad 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -122,6 +122,11 @@ CustomRoles.Phantom or CustomRoles.Stalker or CustomRoles.Doomsayer or CustomRoles.SoulCollector or + CustomRoles.Death or + CustomRoles.Berserker or + CustomRoles.War or + CustomRoles.Baker or + CustomRoles.Famine or CustomRoles.Pirate or CustomRoles.Seeker or CustomRoles.Pixie or @@ -245,7 +250,7 @@ public static bool IsNA(this CustomRoles role) is Custom_RoleType.NeutralApocalypse || role.IsTNA(); } - public static bool IsTNA(this CustomRoles role) + public static bool IsTNA(this CustomRoles role) // Transformed Neutral Apocalypse { return role is CustomRoles.Pestilence or diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 5bdf66b2b6..8fc80ac52c 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -545,6 +545,7 @@ private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, pa Witch.OnCheckForEndVoting(deathReason, playerIds); HexMaster.OnCheckForEndVoting(deathReason, playerIds); Virus.OnCheckForEndVoting(deathReason, playerIds); + Baker.OnCheckForEndVoting(deathReason, playerIds); SoulCollector.OnCheckForEndVoting(deathReason, playerIds); foreach (var playerId in playerIds) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 1cd707b0a7..c463a31d37 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2574,6 +2574,11 @@ "BakerBreadNeededToTransform": "Required number of bread to become Famine", "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", "BakerKillButtonText": "Bread", + "FamineKillButtonText": "Starve", + "FamineStarveCooldown": "Famine starve cooldown", + "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", + "FamineAlreadyStarved": "That player has already been starved!", + "FamineStarved": "Player starved", "ChronomancerKillCooldown": "Time to fully charge the kill button", diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 8f5daced2b..eacd795ea5 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using Hazel; +using System.Linq; using TOHE.Roles.Core; using TOHE.Roles.Impostor; using static TOHE.Options; @@ -21,27 +22,36 @@ internal class Baker : RoleBase //==================================================================\\ private static OptionItem BreadNeededToTransform; + private static OptionItem FamineStarveCooldown; private static readonly Dictionary> BreadList = []; + private static readonly Dictionary> FamineList = []; private static bool CanUseAbility; + private static bool StarvedNonBreaded; public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Baker, 1, zeroOne: false); BreadNeededToTransform = IntegerOptionItem.Create(Id + 10, "BakerBreadNeededToTransform", new(1, 5, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]) .SetValueFormat(OptionFormat.Times); + FamineStarveCooldown = FloatOptionItem.Create(Id + 11, "FamineStarveCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Baker]) + .SetValueFormat(OptionFormat.Seconds); } public override void Init() { playerIdList.Clear(); BreadList.Clear(); + FamineList.Clear(); CanUseAbility = false; + StarvedNonBreaded = false; } public override void Add(byte playerId) { playerIdList.Add(playerId); BreadList[playerId] = []; + FamineList[playerId] = []; CanUseAbility = true; + StarvedNonBreaded = false; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } @@ -76,8 +86,21 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => BreadList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; + { + if (seer.GetCustomRole() is CustomRoles.Baker || !StarvedNonBreaded) + return BreadList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; + else + return FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; + } public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); + public override void SetKillCooldown(byte id) + { + PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); + if (baker.GetCustomRole() is CustomRoles.Famine) + Main.AllPlayerKillCooldown[id] = FamineStarveCooldown.GetFloat(); + else + Main.AllPlayerKillCooldown[id] = Main.AllPlayerKillCooldown[id]; + } public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.KillButton.OverrideText(GetString("BakerKillButtonText")); @@ -97,6 +120,25 @@ private static bool AllHasBread(PlayerControl player) public override void OnReportDeadBody(PlayerControl marg, PlayerControl iscute) { CanUseAbility = true; + PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); + if (baker.GetCustomRole() is CustomRoles.Famine) + { + foreach (var pc in FamineList) + { + foreach (var tar in pc.Value) + { + var target = Utils.GetPlayerById(tar); + var killer = Utils.GetPlayerById(pc.Key); + if (killer == null || target == null) continue; + target.RpcExileV2(); + target.SetRealKiller(killer); + Main.PlayerStates[tar].deathReason = PlayerState.DeathReason.Starved; + Main.PlayerStates[tar].SetDead(); + MurderPlayerPatch.AfterPlayerDeathTasks(killer, target, true); + Logger.Info($"{killer.GetRealName()} has starved {target.GetRealName()}", "Famine"); + } + } + } } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { @@ -107,6 +149,13 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i BreadList[playerId].Remove(playerId); } } + foreach (var playerId in FamineList.Keys.ToArray()) + { + if (deadPlayer.PlayerId == playerId) + { + FamineList[playerId].Remove(playerId); + } + } } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { @@ -117,7 +166,10 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } if (target.IsNeutralApocalypse()) { - killer.Notify(GetString("BakerCantBreadApoc")); + if (killer.GetCustomRole() is CustomRoles.Baker) + killer.Notify(GetString("BakerCantBreadApoc")); + else + killer.Notify(GetString("FamineCantStarveApoc")); return false; } if (HasBread(killer.PlayerId, target.PlayerId)) @@ -125,12 +177,27 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr killer.Notify(GetString("BakerAlreadyBreaded")); return false; } - BreadList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); - killer.Notify(GetString("BakerBreaded")); - Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); - CanUseAbility = false; + if (FamineList[killer.PlayerId].Contains(target.PlayerId)) + { + killer.Notify(GetString("FamineAlreadyStarved")); + return false; + } + if (killer.GetCustomRole() is CustomRoles.Baker) + { + BreadList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("BakerBreaded")); + Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); + CanUseAbility = false; + } + else if (killer.GetCustomRole() is CustomRoles.Famine) { + FamineList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("FamineStarved")); + Logger.Info(target.GetRealName() + $" has been starved", "Baker"); + } return false; } public override void OnFixedUpdate(PlayerControl player) @@ -140,32 +207,34 @@ public override void OnFixedUpdate(PlayerControl player) player.RpcSetCustomRole(CustomRoles.Famine); player.Notify(GetString("BakerToFamine")); player.RpcGuardAndKill(player); - KillIfNotEjected(player); - } - public static void KillIfNotEjected(PlayerControl player) + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { + if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; + if (!CustomRoles.Famine.RoleExist()) return; + if (exileIds.Contains(playerIdList.First())) return; + if (StarvedNonBreaded) return; var deathList = new List(); - var baker = player.PlayerId; - if (Main.AfterMeetingDeathPlayers.ContainsKey(baker)) return; + PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); foreach (var pc in Main.AllAlivePlayerControls) { - var notBaker = pc.PlayerId; - if (pc.IsNeutralApocalypse() || HasBread(baker, notBaker)) continue; - if (player != null && player.IsAlive()) + if (pc.IsNeutralApocalypse() || HasBread(baker.PlayerId, pc.PlayerId)) continue; + if (baker != null && baker.IsAlive()) { if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) { - pc.SetRealKiller(player); + pc.SetRealKiller(baker); deathList.Add(pc.PlayerId); } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } } - else return; + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } } CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Starved, [.. deathList]); + BreadList.Clear(); + StarvedNonBreaded = true; + CanUseAbility = true; } } diff --git a/main.cs b/main.cs index 6179408763..2b584a2bea 100644 --- a/main.cs +++ b/main.cs @@ -336,6 +336,9 @@ public static void LoadRoleClasses() CustomRolesHelper.DuplicatedRoles = new Dictionary { { CustomRoles.Pestilence, typeof(PlagueBearer) }, + { CustomRoles.Death, typeof(SoulCollector) }, + { CustomRoles.War, typeof(Berserker) }, + { CustomRoles.Famine, typeof(Baker) }, { CustomRoles.NiceMini, typeof(Mini) }, { CustomRoles.EvilMini, typeof(Mini) } }; From 17d15888d3264e76bd8df1295096a62a194131b1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 8 May 2024 19:00:59 -0400 Subject: [PATCH 016/778] more famine stuff + death doesnt show soul counter --- Roles/Neutral/Baker.cs | 24 +++++++++++++++--------- Roles/Neutral/SoulCollector.cs | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index eacd795ea5..efa6cae306 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -103,7 +103,10 @@ public override void SetKillCooldown(byte id) } public override void SetAbilityButtonText(HudManager hud, byte playerId) { - hud.KillButton.OverrideText(GetString("BakerKillButtonText")); + if (CustomRoles.Baker.RoleExist()) + hud.KillButton.OverrideText(GetString("BakerKillButtonText")); + else if (CustomRoles.Famine.RoleExist()) + hud.KillButton.OverrideText(GetString("FamineKillButtonText")); } private static bool HasBread(byte pc, byte target) { @@ -182,7 +185,16 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr killer.Notify(GetString("FamineAlreadyStarved")); return false; } - if (killer.GetCustomRole() is CustomRoles.Baker) + if (killer.GetCustomRole() is CustomRoles.Famine) { + FamineList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("FamineStarved")); + Logger.Info(target.GetRealName() + $" has been starved", "Famine"); + CanUseAbility = true; + return false; + } + else if (killer.GetCustomRole() is CustomRoles.Baker) { BreadList[killer.PlayerId].Add(target.PlayerId); SendRPC(killer, target); @@ -190,13 +202,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr killer.Notify(GetString("BakerBreaded")); Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); CanUseAbility = false; - } - else if (killer.GetCustomRole() is CustomRoles.Famine) { - FamineList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); - killer.Notify(GetString("FamineStarved")); - Logger.Info(target.GetRealName() + $" has been starved", "Baker"); + return false; } return false; } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 2da3bc8370..cae38f559f 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -49,7 +49,7 @@ public override void Add(byte playerId) CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } - public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid"); + public override string GetProgressText(byte playerId, bool cvooms) => CustomRoles.SoulCollector.RoleExist() ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid") : ""; private static void SendRPC(byte playerId) { From 8c6bfc28f4153970756b235097551e79d3ff6e03 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 9 May 2024 16:41:07 -0400 Subject: [PATCH 017/778] more role interactions + starve fix --- Modules/GuessManager.cs | 8 ++++++- Modules/OptionHolder.cs | 4 ++++ Patches/IntroPatch.cs | 2 +- Resources/Lang/en_US.json | 1 + Roles/(Ghosts)/Crewmate/Hawk.cs | 2 +- Roles/AddOns/Common/Burst.cs | 2 +- Roles/AddOns/Crewmate/Ghoul.cs | 2 +- Roles/Crewmate/Alchemist.cs | 2 +- Roles/Crewmate/Crusader.cs | 2 +- Roles/Crewmate/Deceiver.cs | 2 +- Roles/Crewmate/Randomizer.cs | 2 +- Roles/Crewmate/Retributionist.cs | 6 ++--- Roles/Crewmate/Sheriff.cs | 2 +- Roles/Crewmate/Veteran.cs | 2 +- Roles/Impostor/Bomber.cs | 2 +- Roles/Impostor/Councillor.cs | 6 +++++ Roles/Impostor/Crewpostor.cs | 9 ++++++-- Roles/Impostor/CursedWolf.cs | 2 +- Roles/Impostor/Hangman.cs | 2 +- Roles/Impostor/Nemesis.cs | 6 ++--- Roles/Impostor/Trapster.cs | 4 ++-- Roles/Neutral/Baker.cs | 38 +++++++++++++++----------------- Roles/Neutral/Demon.cs | 2 +- Roles/Neutral/HexMaster.cs | 2 +- Roles/Neutral/Infectious.cs | 2 +- Roles/Neutral/Jinx.cs | 2 +- Roles/Neutral/Pelican.cs | 2 +- Roles/Neutral/Poisoner.cs | 2 +- Roles/Neutral/Shroud.cs | 2 +- Roles/Neutral/Werewolf.cs | 2 +- 30 files changed, 72 insertions(+), 52 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 21588c8a1e..0db2d31387 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -221,7 +221,13 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) else pc.ShowPopUp(GetString("GuessShielded")); return true; } - + + if (role.IsTNA() && !Options.TransformedNeutralApocalypseCanBeGuessed.GetBool()) + { + if (!isUI) Utils.SendMessage(GetString("GuessImmune"), pc.PlayerId); + else pc.ShowPopUp(GetString("GuessImmune")); + return true; + } if (pc.Is(CustomRoles.Phantom) && !Phantom.PhantomCanGuess.GetBool()) { if (!isUI) Utils.SendMessage(GetString("GuessDisabled"), pc.PlayerId); diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 3a92f2fffc..2b744a89b0 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -480,6 +480,7 @@ private enum RatesZeroOne public static OptionItem NeutralKillingRolesMaxPlayer; public static OptionItem NeutralApocalypseRolesMinPlayer; public static OptionItem NeutralApocalypseRolesMaxPlayer; + public static OptionItem TransformedNeutralApocalypseCanBeGuessed; public static OptionItem NeutralRoleWinTogether; public static OptionItem NeutralWinTogether; @@ -663,6 +664,9 @@ public static void Load() NeutralApocalypseRolesMaxPlayer = IntegerOptionItem.Create(60023, "NeutralApocalypseRolesMaxPlayer", new(0, 4, 1), 0, TabGroup.NeutralRoles, false) .SetGameMode(CustomGameMode.Standard) .SetValueFormat(OptionFormat.Players); + TransformedNeutralApocalypseCanBeGuessed = BooleanOptionItem.Create(60024, "TNACanBeGuessed", false, TabGroup.NeutralRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); NeutralRoleWinTogether = BooleanOptionItem.Create(60017, "NeutralRoleWinTogether", false, TabGroup.NeutralRoles, false) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 42b1a9ac8b..20d6b9c4dd 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -495,7 +495,7 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections __instance.overlayHandle.color = Palette.CrewmateBlue; return false; } - else if (role is CustomRoles.Romantic or CustomRoles.Doppelganger or CustomRoles.Pyromaniac or CustomRoles.Huntsman or CustomRoles.RuthlessRomantic or CustomRoles.VengefulRomantic or CustomRoles.SerialKiller or CustomRoles.Jackal or CustomRoles.Seeker or CustomRoles.Pixie or CustomRoles.Agitater or CustomRoles.CursedSoul or CustomRoles.Pirate or CustomRoles.Amnesiac or CustomRoles.Arsonist or CustomRoles.Sidekick or CustomRoles.Innocent or CustomRoles.Pelican or CustomRoles.Pursuer or CustomRoles.Revolutionist or CustomRoles.Hater or CustomRoles.Demon or CustomRoles.Glitch or CustomRoles.Juggernaut or CustomRoles.Stalker or CustomRoles.Provocateur or CustomRoles.BloodKnight or CustomRoles.SerialKiller or CustomRoles.Werewolf or CustomRoles.Maverick or CustomRoles.Shroud or CustomRoles.Follower or CustomRoles.Cultist or CustomRoles.Pelican or CustomRoles.Infectious or CustomRoles.Virus or CustomRoles.Pickpocket or CustomRoles.Traitor or CustomRoles.PlagueBearer or CustomRoles.Pestilence or CustomRoles.Spiritcaller or CustomRoles.Necromancer or CustomRoles.Medusa or CustomRoles.HexMaster or CustomRoles.Wraith or CustomRoles.Jinx or CustomRoles.Poisoner or CustomRoles.PotionMaster) //or CustomRoles.Occultist + else if (role is CustomRoles.Romantic or CustomRoles.Doppelganger or CustomRoles.Pyromaniac or CustomRoles.Huntsman or CustomRoles.RuthlessRomantic or CustomRoles.VengefulRomantic or CustomRoles.SerialKiller or CustomRoles.Jackal or CustomRoles.Seeker or CustomRoles.Pixie or CustomRoles.Agitater or CustomRoles.CursedSoul or CustomRoles.Pirate or CustomRoles.Amnesiac or CustomRoles.Arsonist or CustomRoles.Sidekick or CustomRoles.Innocent or CustomRoles.Pelican or CustomRoles.Pursuer or CustomRoles.Revolutionist or CustomRoles.Hater or CustomRoles.Demon or CustomRoles.Glitch or CustomRoles.Juggernaut or CustomRoles.Stalker or CustomRoles.Provocateur or CustomRoles.BloodKnight or CustomRoles.SerialKiller or CustomRoles.Werewolf or CustomRoles.Maverick or CustomRoles.Shroud or CustomRoles.Follower or CustomRoles.Cultist or CustomRoles.Pelican or CustomRoles.Infectious or CustomRoles.Virus or CustomRoles.Pickpocket or CustomRoles.Traitor or CustomRoles.PlagueBearer or CustomRoles.Pestilence or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.Berserker or CustomRoles.War or CustomRoles.Spiritcaller or CustomRoles.Necromancer or CustomRoles.Medusa or CustomRoles.HexMaster or CustomRoles.Wraith or CustomRoles.Jinx or CustomRoles.Poisoner or CustomRoles.PotionMaster) //or CustomRoles.Occultist { yourTeam = new Il2CppSystem.Collections.Generic.List(); yourTeam.Add(PlayerControl.LocalPlayer); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index f3418d958a..c87d0931e3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2573,6 +2573,7 @@ "PassiveSoulGained": "You have gained a passive soul from the underworld.", "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", + "ApocalypseImmune": "This player is immune because they are invincible!", "BakerToFamine": "You have become Famine!!!", "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", "BakerAlreadyBreaded": "That player already has bread!", diff --git a/Roles/(Ghosts)/Crewmate/Hawk.cs b/Roles/(Ghosts)/Crewmate/Hawk.cs index 5f78f9d1c8..812d0fcc40 100644 --- a/Roles/(Ghosts)/Crewmate/Hawk.cs +++ b/Roles/(Ghosts)/Crewmate/Hawk.cs @@ -132,7 +132,7 @@ private static bool CheckRetriConflicts(PlayerControl killer, PlayerControl targ return target != null && Main.AllAlivePlayerControls.Length >= MinimumPlayersAliveToKill.GetInt() && KillCount[killer.PlayerId] > 0 && rnd.Next(100) >= KillerChanceMiss[target.PlayerId] - && !target.Is(CustomRoles.Pestilence) + && !target.IsTransformedNeutralApocalypse() && (!target.Is(CustomRoles.NiceMini) || Mini.Age > 18); } public static bool CanKill(byte id) => KillCount.TryGetValue(id, out var x) && x > 0; diff --git a/Roles/AddOns/Common/Burst.cs b/Roles/AddOns/Common/Burst.cs index 9d77c2a6ce..1c2f33e491 100644 --- a/Roles/AddOns/Common/Burst.cs +++ b/Roles/AddOns/Common/Burst.cs @@ -43,7 +43,7 @@ public static void AfterBurstDeadTasks(PlayerControl killer, PlayerControl targe { target.SetRealKiller(killer); BurstBodies.Add(target.PlayerId); - if (killer.PlayerId != target.PlayerId && !killer.Is(CustomRoles.Pestilence)) + if (killer.PlayerId != target.PlayerId && !killer.IsTransformedNeutralApocalypse()) { killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Burst), GetString("BurstNotify"))); _ = new LateTask(() => diff --git a/Roles/AddOns/Crewmate/Ghoul.cs b/Roles/AddOns/Crewmate/Ghoul.cs index e361274b91..babac4812e 100644 --- a/Roles/AddOns/Crewmate/Ghoul.cs +++ b/Roles/AddOns/Crewmate/Ghoul.cs @@ -28,7 +28,7 @@ public static void ApplyGameOptions(PlayerControl player) { if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Ghoul) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == player.PlayerId)) { - if (!player.Is(CustomRoles.Pestilence)) + if (!player.IsTransformedNeutralApocalypse()) KillGhoul.Add(player.PlayerId); } } diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index 15798741ed..0b8d65151a 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -176,7 +176,7 @@ private void OnFixedUpdatesBloodlus(PlayerControl player) float dis; foreach (var target in Main.AllAlivePlayerControls) { - if (target.PlayerId != player.PlayerId && !target.Is(CustomRoles.Pestilence)) + if (target.PlayerId != player.PlayerId && !target.IsTransformedNeutralApocalypse()) { dis = Vector2.Distance(bloodlustPos, target.transform.position); targetDistance.Add(target.PlayerId, dis); diff --git a/Roles/Crewmate/Crusader.cs b/Roles/Crewmate/Crusader.cs index cad8e3d370..8336f59c2b 100644 --- a/Roles/Crewmate/Crusader.cs +++ b/Roles/Crewmate/Crusader.cs @@ -104,7 +104,7 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr foreach (var crusader in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Crusader)).ToArray()) { - if (!killer.Is(CustomRoles.Pestilence) && !killer.Is(CustomRoles.KillingMachine) + if (!killer.IsTransformedNeutralApocalypse() && !killer.Is(CustomRoles.KillingMachine) && killer.CheckForInvalidMurdering(target) && crusader.RpcCheckAndMurder(killer, true)) { crusader.RpcMurderPlayer(killer); diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index f7c80f9caf..ea5e403af4 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -89,7 +89,7 @@ private static bool IsClient(byte playerId) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return true; - if (target.Is(CustomRoles.Pestilence) || target.Is(CustomRoles.SerialKiller)) return true; + if (target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.SerialKiller)) return true; if (!(CanBeClient(target) && CanSeel(killer.PlayerId))) return false; diff --git a/Roles/Crewmate/Randomizer.cs b/Roles/Crewmate/Randomizer.cs index a7ea9c3c80..d2441a17db 100644 --- a/Roles/Crewmate/Randomizer.cs +++ b/Roles/Crewmate/Randomizer.cs @@ -109,7 +109,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t { var pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId).ToList(); var rp = pcList[IRandom.Instance.Next(0, pcList.Count)]; - if (!rp.Is(CustomRoles.Pestilence)) + if (!rp.IsTransformedNeutralApocalypse()) { Main.PlayerStates[rp.PlayerId].deathReason = PlayerState.DeathReason.Revenge; rp.SetRealKiller(target); diff --git a/Roles/Crewmate/Retributionist.cs b/Roles/Crewmate/Retributionist.cs index 812a12d0f5..9be46b2654 100644 --- a/Roles/Crewmate/Retributionist.cs +++ b/Roles/Crewmate/Retributionist.cs @@ -145,10 +145,10 @@ public static bool RetributionistMsgCheck(PlayerControl pc, string msg, bool isU else pc.ShowPopUp(GetString("RetributionistKillDead")); return true; } - else if (target.Is(CustomRoles.Pestilence)) + else if (target.IsTransformedNeutralApocalypse()) { - if (!isUI) SendMessage(GetString("PestilenceImmune"), pc.PlayerId); - else pc.ShowPopUp(GetString("PestilenceImmune")); + if (!isUI) SendMessage(GetString("GuessImmune"), pc.PlayerId); + else pc.ShowPopUp(GetString("GuessImmune")); return true; } else if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index 42f098284b..92de123631 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -191,7 +191,7 @@ public static bool CanBeKilledBySheriff(PlayerControl player) return cRole switch { CustomRoles.Trickster => false, - CustomRoles.Pestilence => true, + var r when cRole.IsTNA() => false, _ => cRole.GetCustomRoleTeam() switch { Custom_Team.Impostor => true, diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs index 5f602a2566..4367f1f232 100644 --- a/Roles/Crewmate/Veteran.cs +++ b/Roles/Crewmate/Veteran.cs @@ -75,7 +75,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t Logger.Info($"{target.GetRealName()} 老兵反弹击杀:{killer.GetRealName()}", "Veteran Kill"); return false; } - if (killer.Is(CustomRoles.Pestilence)) + if (killer.Is(CustomRoles.Pestilence) || killer.Is(CustomRoles.War)) { target.SetRealKiller(killer); killer.RpcMurderPlayer(target); diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index b17d227fa9..2d2646ae9d 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -79,7 +79,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl if (!target.IsModClient()) target.KillFlash(); if (target.PlayerId == shapeshifter.PlayerId) continue; - if (!target.IsAlive() || Medic.ProtectList.Contains(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.Is(CustomRoles.Pestilence) || target.Is(CustomRoles.Solsticer)) continue; + if (!target.IsAlive() || Medic.ProtectList.Contains(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; var pos = shapeshifter.transform.position; var dis = Vector2.Distance(pos, target.transform.position); diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index d229986dcf..f31846c762 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -176,6 +176,12 @@ public static bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) else pc.ShowPopUp(GetString("GuessSolsticer")); return true; } + else if (target.IsTransformedNeutralApocalypse() && !target.Is(CustomRoles.Pestilence)) + { + if (!isUI) Utils.SendMessage(GetString("ApocalypseImmune"), pc.PlayerId); + else pc.ShowPopUp(GetString("ApocalypseImmune")); + return true; + } else if (target.Is(CustomRoles.Madmate) && CanMurderMadmate.GetBool()) CouncillorSuicide = false; else if (target.Is(CustomRoles.Parasite) && CanMurderMadmate.GetBool()) CouncillorSuicide = false; else if (target.Is(CustomRoles.Refugee) && CanMurderMadmate.GetBool()) CouncillorSuicide = false; diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index e93d37f6fa..5729e87021 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -123,7 +123,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount list = [.. list.OrderBy(x => Vector2.Distance(player.transform.position, x.transform.position))]; var target = list[0]; - if (!target.Is(CustomRoles.Pestilence)) + if (!target.IsTransformedNeutralApocalypse()) { if (!LungeKill.GetBool()) { @@ -141,13 +141,18 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } Logger.Info($"Crewpostor completed task to kill:{player.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Crewpostor"); } - else + else if (target.Is(CustomRoles.Pestilence)) { player.SetRealKiller(target); target.RpcMurderPlayer(player); player.RpcGuardAndKill(); Logger.Info($"Crewpostor tried to kill pestilence (reflected back):{target.GetNameWithRole().RemoveHtmlTags()} => {player.GetNameWithRole().RemoveHtmlTags()}", "Pestilence Reflect"); } + else + { + player.RpcGuardAndKill(); + Logger.Info($"Crewpostor tried to kill Apocalypse Member:{target.GetNameWithRole().RemoveHtmlTags()} => {player.GetNameWithRole().RemoveHtmlTags()}", "Apocalypse Immune"); + } } return true; diff --git a/Roles/Impostor/CursedWolf.cs b/Roles/Impostor/CursedWolf.cs index 35154292de..68e52e3ea1 100644 --- a/Roles/Impostor/CursedWolf.cs +++ b/Roles/Impostor/CursedWolf.cs @@ -60,7 +60,7 @@ public static void ReceiveRPC(MessageReader reader) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (killer == target || SpellCount[target.PlayerId] <= 0) return true; - if (killer.Is(CustomRoles.Pestilence)) return true; + if (killer.IsTransformedNeutralApocalypse()) return true; killer.RpcGuardAndKill(target); target.RpcGuardAndKill(target); diff --git a/Roles/Impostor/Hangman.cs b/Roles/Impostor/Hangman.cs index 078a6e1012..7dbcdc2ac6 100644 --- a/Roles/Impostor/Hangman.cs +++ b/Roles/Impostor/Hangman.cs @@ -42,7 +42,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (target.Is(CustomRoles.Pestilence)) + if (target.IsTransformedNeutralApocalypse()) return true; if (target.Is(CustomRoles.Madmate) && !Madmate.ImpCanKillMadmate.GetBool()) diff --git a/Roles/Impostor/Nemesis.cs b/Roles/Impostor/Nemesis.cs index 46c72938a3..1d202a8cc1 100644 --- a/Roles/Impostor/Nemesis.cs +++ b/Roles/Impostor/Nemesis.cs @@ -129,10 +129,10 @@ public static bool NemesisMsgCheck(PlayerControl pc, string msg, bool isUI = fal else pc.ShowPopUp(GetString("NemesisKillDead")); return true; } - else if (target.Is(CustomRoles.Pestilence)) + else if (target.IsTransformedNeutralApocalypse()) { - if (!isUI) Utils.SendMessage(GetString("PestilenceImmune"), pc.PlayerId); - else pc.ShowPopUp(GetString("PestilenceImmune")); + if (!isUI) Utils.SendMessage(GetString("GuessImmune"), pc.PlayerId); + else pc.ShowPopUp(GetString("GuessImmune")); return true; } else if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) diff --git a/Roles/Impostor/Trapster.cs b/Roles/Impostor/Trapster.cs index c532a9f580..fae54c4163 100644 --- a/Roles/Impostor/Trapster.cs +++ b/Roles/Impostor/Trapster.cs @@ -58,7 +58,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, GameData.Play var Trapsters = Playerids.GetPlayerListByIds(); // if trapster dead - if (target.Is(CustomRoles.Trapster) && TrapTrapsterBody.GetBool() && !reporter.Is(CustomRoles.Pestilence)) + if (target.Is(CustomRoles.Trapster) && TrapTrapsterBody.GetBool() && !reporter.IsTransformedNeutralApocalypse()) { var killerId = target.PlayerId; @@ -78,7 +78,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, GameData.Play // if reporter try reported trap body if (BoobyTrapBody.Contains(target.PlayerId) && reporter.IsAlive() - && !reporter.Is(CustomRoles.Pestilence) && Trapsters.All(Trapi => Trapi.RpcCheckAndMurder(reporter, true))) + && !reporter.IsTransformedNeutralApocalypse() && Trapsters.All(Trapi => Trapi.RpcCheckAndMurder(reporter, true))) { var killerId = target.PlayerId; diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index efa6cae306..b8efa53d64 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -162,6 +162,17 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { + if (killer.GetCustomRole() == CustomRoles.Famine && !target.IsNeutralApocalypse()) { + if (StarvedNonBreaded) { + FamineList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("FamineStarved")); + Logger.Info(target.GetRealName() + $" has been starved", "Famine"); + CanUseAbility = true; + return false; + } + } if (!CanUseAbility) { killer.Notify(GetString("BakerBreadUsedAlready")); @@ -169,7 +180,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } if (target.IsNeutralApocalypse()) { - if (killer.GetCustomRole() is CustomRoles.Baker) + if (killer.GetCustomRole() == CustomRoles.Baker) killer.Notify(GetString("BakerCantBreadApoc")); else killer.Notify(GetString("FamineCantStarveApoc")); @@ -185,25 +196,12 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr killer.Notify(GetString("FamineAlreadyStarved")); return false; } - if (killer.GetCustomRole() is CustomRoles.Famine) { - FamineList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); - killer.Notify(GetString("FamineStarved")); - Logger.Info(target.GetRealName() + $" has been starved", "Famine"); - CanUseAbility = true; - return false; - } - else if (killer.GetCustomRole() is CustomRoles.Baker) - { - BreadList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); - killer.Notify(GetString("BakerBreaded")); - Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); - CanUseAbility = false; - return false; - } + BreadList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("BakerBreaded")); + Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); + CanUseAbility = false; return false; } public override void OnFixedUpdate(PlayerControl player) diff --git a/Roles/Neutral/Demon.cs b/Roles/Neutral/Demon.cs index 9f0eb5b69e..f3b8ab4280 100644 --- a/Roles/Neutral/Demon.cs +++ b/Roles/Neutral/Demon.cs @@ -111,7 +111,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { - if (target.Is(CustomRoles.Pestilence)) return true; + if (target.IsTransformedNeutralApocalypse()) return true; if (killer == null || target == null || !target.Is(CustomRoles.Demon) || killer.Is(CustomRoles.Demon)) return true; if (DemonHealth[target.PlayerId] - SelfDamage.GetInt() < 1) diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index 7a2e29685b..ad5c0c8a02 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -165,7 +165,7 @@ public override void AfterMeetingTasks() public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (Medic.ProtectList.Contains(target.PlayerId)) return false; - if (target.Is(CustomRoles.Pestilence)) return false; + if (target.IsTransformedNeutralApocalypse()) return false; if (NowSwitchTrigger == SwitchTrigger.TriggerDouble) { diff --git a/Roles/Neutral/Infectious.cs b/Roles/Neutral/Infectious.cs index 5065fa0e05..bfc73c57f9 100644 --- a/Roles/Neutral/Infectious.cs +++ b/Roles/Neutral/Infectious.cs @@ -113,7 +113,7 @@ private static bool InfectOrMurder(PlayerControl killer, PlayerControl target) } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (target.Is(CustomRoles.Pestilence)) return true; + if (target.IsTransformedNeutralApocalypse()) return true; if (target.Is(CustomRoles.Infectious)) return true; if (target.Is(CustomRoles.SerialKiller)) return true; diff --git a/Roles/Neutral/Jinx.cs b/Roles/Neutral/Jinx.cs index 6cc3172f7e..b59a40f583 100644 --- a/Roles/Neutral/Jinx.cs +++ b/Roles/Neutral/Jinx.cs @@ -70,7 +70,7 @@ public static void ReceiveRPC(MessageReader reader) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (JinxSpellCount[target.PlayerId] <= 0) return true; - if (killer.Is(CustomRoles.Pestilence)) return true; + if (killer.IsTransformedNeutralApocalypse()) return true; if (killer == target) return true; killer.RpcGuardAndKill(target); diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index 6f09a2dc0d..264be98b9c 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -113,7 +113,7 @@ public static bool CanEat(PlayerControl pc, byte id) if (target.Is(CustomRoles.Penguin) || id == Penguin.AbductVictim.PlayerId) return false; - return target != null && target.CanBeTeleported() && !target.Is(CustomRoles.Pestilence) && !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); + return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); } public static Vector2 GetBlackRoomPSForPelican() { diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Neutral/Poisoner.cs index 991f7760ad..dacbf04160 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Neutral/Poisoner.cs @@ -67,7 +67,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (target.Is(CustomRoles.Bait)) return true; if (Guardian.CannotBeKilled(target)) return true; if (target.Is(CustomRoles.Glitch)) return true; - if (target.Is(CustomRoles.Pestilence)) return true; + if (target.IsTransformedNeutralApocalypse()) return true; if (Medic.ProtectList.Contains(target.PlayerId)) return false; killer.SetKillCooldown(); diff --git a/Roles/Neutral/Shroud.cs b/Roles/Neutral/Shroud.cs index e6b554497e..aaec0f0b50 100644 --- a/Roles/Neutral/Shroud.cs +++ b/Roles/Neutral/Shroud.cs @@ -117,7 +117,7 @@ private static void OnFixedUpdateOthers(PlayerControl shroud) float dis; foreach (var target in Main.AllAlivePlayerControls) { - if (target.PlayerId != shroud.PlayerId && !target.Is(CustomRoles.Shroud) && !target.Is(CustomRoles.Pestilence)) + if (target.PlayerId != shroud.PlayerId && !target.Is(CustomRoles.Shroud) && !target.IsTransformedNeutralApocalypse()) { dis = Vector2.Distance(shroudPos, target.transform.position); targetDistance.Add(target.PlayerId, dis); diff --git a/Roles/Neutral/Werewolf.cs b/Roles/Neutral/Werewolf.cs index 5134c59dfb..ae586be769 100644 --- a/Roles/Neutral/Werewolf.cs +++ b/Roles/Neutral/Werewolf.cs @@ -59,7 +59,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (player == killer) continue; if (player == target) continue; - if (player.Is(CustomRoles.Pestilence)) continue; + if (player.IsTransformedNeutralApocalypse()) continue; else if ((player.Is(CustomRoles.NiceMini) || player.Is(CustomRoles.EvilMini)) && Mini.Age < 18) continue; if (Vector2.Distance(killer.transform.position, player.transform.position) <= Werewolf.MaulRadius.GetFloat()) From dfc02bcee0dadcaef292a867454d397a27bae815 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 9 May 2024 18:55:00 -0400 Subject: [PATCH 018/778] did a bunch setting for if TNA's can be guessed (Pestilence still does its regular thing though) Soul Collector now uses Kill button Death now increases meeting time Soul Collector and Baker have the option to vent Made Famine and Death ACTUALLY unkillable --- Modules/GuessManager.cs | 26 ++++++++++- Modules/MeetingTimeManager.cs | 6 ++- Modules/OptionHolder.cs | 6 +-- Patches/IntroPatch.cs | 2 +- Resources/Lang/en_US.json | 15 ++++-- Roles/Neutral/Baker.cs | 9 ++++ Roles/Neutral/SoulCollector.cs | 83 +++++++++++++++++++++------------- 7 files changed, 106 insertions(+), 41 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 0db2d31387..f43c5016ad 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -901,6 +901,30 @@ void ClickEvent() listOfRoles.Add(CustomRoles.Admired); } + if (CustomRoles.PlagueBearer.IsEnable()) + { + if (!listOfRoles.Contains(CustomRoles.Pestilence)) + listOfRoles.Add(CustomRoles.Pestilence); + } + + if (CustomRoles.SoulCollector.IsEnable()) + { + if (!listOfRoles.Contains(CustomRoles.Death)) + listOfRoles.Add(CustomRoles.Death); + } + + if (CustomRoles.Baker.IsEnable()) + { + if (!listOfRoles.Contains(CustomRoles.Famine)) + listOfRoles.Add(CustomRoles.Famine); + } + + if (CustomRoles.Berserker.IsEnable()) + { + if (!listOfRoles.Contains(CustomRoles.War)) + listOfRoles.Add(CustomRoles.War); + } + arrayOfRoles = [.. listOfRoles]; } else @@ -936,7 +960,7 @@ or CustomRoles.Rebound or CustomRoles.LastImpostor or CustomRoles.Mare or CustomRoles.Cyber - ) continue; + || (role.IsTNA() && !Options.TransformedNeutralApocalypseCanBeGuessed.GetBool())) continue; CreateRole(role); } diff --git a/Modules/MeetingTimeManager.cs b/Modules/MeetingTimeManager.cs index 8957c966f9..9b2663c259 100644 --- a/Modules/MeetingTimeManager.cs +++ b/Modules/MeetingTimeManager.cs @@ -2,6 +2,7 @@ using System; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; namespace TOHE.Modules; @@ -58,7 +59,10 @@ public static void OnReportDeadBody() MeetingTimeMax = TimeManager.MeetingTimeLimit.GetInt(); BonusMeetingTime += TimeManager.TotalIncreasedMeetingTime(); } - + if (SoulCollector.HasEnabled) + { + BonusMeetingTime += SoulCollector.GetDeathMeetingTimeIncrease(); + } int TotalMeetingTime = DiscussionTime + VotingTime; if (TimeManager.HasEnabled) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeManager, MeetingTimeMax) - TotalMeetingTime; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 2b744a89b0..819f3e2017 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -664,9 +664,6 @@ public static void Load() NeutralApocalypseRolesMaxPlayer = IntegerOptionItem.Create(60023, "NeutralApocalypseRolesMaxPlayer", new(0, 4, 1), 0, TabGroup.NeutralRoles, false) .SetGameMode(CustomGameMode.Standard) .SetValueFormat(OptionFormat.Players); - TransformedNeutralApocalypseCanBeGuessed = BooleanOptionItem.Create(60024, "TNACanBeGuessed", false, TabGroup.NeutralRoles, false) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true); NeutralRoleWinTogether = BooleanOptionItem.Create(60017, "NeutralRoleWinTogether", false, TabGroup.NeutralRoles, false) @@ -842,6 +839,9 @@ public static void Load() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); + TransformedNeutralApocalypseCanBeGuessed = BooleanOptionItem.Create(60024, "TNACanBeGuessed", false, TabGroup.NeutralRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); CustomRoles.Baker.GetStaticRoleClass().SetupCustomOption(); CustomRoles.Berserker.GetStaticRoleClass().SetupCustomOption(); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 20d6b9c4dd..e06da836ce 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -495,7 +495,7 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections __instance.overlayHandle.color = Palette.CrewmateBlue; return false; } - else if (role is CustomRoles.Romantic or CustomRoles.Doppelganger or CustomRoles.Pyromaniac or CustomRoles.Huntsman or CustomRoles.RuthlessRomantic or CustomRoles.VengefulRomantic or CustomRoles.SerialKiller or CustomRoles.Jackal or CustomRoles.Seeker or CustomRoles.Pixie or CustomRoles.Agitater or CustomRoles.CursedSoul or CustomRoles.Pirate or CustomRoles.Amnesiac or CustomRoles.Arsonist or CustomRoles.Sidekick or CustomRoles.Innocent or CustomRoles.Pelican or CustomRoles.Pursuer or CustomRoles.Revolutionist or CustomRoles.Hater or CustomRoles.Demon or CustomRoles.Glitch or CustomRoles.Juggernaut or CustomRoles.Stalker or CustomRoles.Provocateur or CustomRoles.BloodKnight or CustomRoles.SerialKiller or CustomRoles.Werewolf or CustomRoles.Maverick or CustomRoles.Shroud or CustomRoles.Follower or CustomRoles.Cultist or CustomRoles.Pelican or CustomRoles.Infectious or CustomRoles.Virus or CustomRoles.Pickpocket or CustomRoles.Traitor or CustomRoles.PlagueBearer or CustomRoles.Pestilence or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.Berserker or CustomRoles.War or CustomRoles.Spiritcaller or CustomRoles.Necromancer or CustomRoles.Medusa or CustomRoles.HexMaster or CustomRoles.Wraith or CustomRoles.Jinx or CustomRoles.Poisoner or CustomRoles.PotionMaster) //or CustomRoles.Occultist + else if (role is CustomRoles.Romantic or CustomRoles.Doppelganger or CustomRoles.Pyromaniac or CustomRoles.Huntsman or CustomRoles.RuthlessRomantic or CustomRoles.VengefulRomantic or CustomRoles.SerialKiller or CustomRoles.Jackal or CustomRoles.Seeker or CustomRoles.Pixie or CustomRoles.Agitater or CustomRoles.CursedSoul or CustomRoles.Pirate or CustomRoles.Amnesiac or CustomRoles.Arsonist or CustomRoles.Sidekick or CustomRoles.Innocent or CustomRoles.Pelican or CustomRoles.Pursuer or CustomRoles.Revolutionist or CustomRoles.Hater or CustomRoles.Demon or CustomRoles.Glitch or CustomRoles.Juggernaut or CustomRoles.Stalker or CustomRoles.Provocateur or CustomRoles.BloodKnight or CustomRoles.SerialKiller or CustomRoles.Werewolf or CustomRoles.Maverick or CustomRoles.Shroud or CustomRoles.Follower or CustomRoles.Cultist or CustomRoles.Pelican or CustomRoles.Infectious or CustomRoles.Virus or CustomRoles.Pickpocket or CustomRoles.Traitor or CustomRoles.PlagueBearer or CustomRoles.Pestilence or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.SoulCollector or CustomRoles.Death or CustomRoles.Berserker or CustomRoles.War or CustomRoles.Spiritcaller or CustomRoles.Necromancer or CustomRoles.Medusa or CustomRoles.HexMaster or CustomRoles.Wraith or CustomRoles.Jinx or CustomRoles.Poisoner or CustomRoles.PotionMaster) //or CustomRoles.Occultist { yourTeam = new Il2CppSystem.Collections.Generic.List(); yourTeam.Add(PlayerControl.LocalPlayer); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index c87d0931e3..6805d30453 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -843,13 +843,13 @@ "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill that makes them immortal for a few seconds.", - "ApocalypseInfoLong": "(Apocalypse):\nEvery role of the Apocalypse Team has their own objective to carry out in order to transform.\nTransformed Apocalypse members are immortal, but everyone will be notified that they have transformed.\n\nRoles: Plaguebearer, Soul Collector, Baker, Berserker\nTransformed: Pestilence, Death, Famine, War\n\n(if you got this as a role, you have bugged the game, good job)(if you got this as a role, you have bugged the game, good job)", "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence you will become immortal and gain the ability to kill.\nIn addition to this, after turning into Pestilence you will kill anyone who tries to kill you.\n\nTo win, turn into Pestilence and kill everyone.", "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected back towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone the meeting after you transform.", - "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you vote players to predict their death. If the prediction is correct and the target dies in the next round you collect their soul. \n\nOnce you collect the configurable amount of souls, you become Death.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you can use your kill button on a player to predict their death. If your target dies in the round you select them or in the meeting after, you will gain a soul.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nA configurable amount of more meeting time will be given on the meeting Death transforms in order to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone the meeting after you transform.", "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", - "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players at the next meeting.\n\nYou are invincible and your presence is announced to everyone the meeting after you transform.", "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone the meeting after you transform.", "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", @@ -1349,6 +1349,7 @@ "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", + "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", "ImpKnowWhosMadmate": "Impostors know Madmates", @@ -2571,6 +2572,11 @@ "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", "GetPassiveSouls": "Gain a passive soul every round", "PassiveSoulGained": "You have gained a passive soul from the underworld.", + "SoulCollectorTargetUsed": "You've already targeted someone this round!", + "SoulCollectorSoulGained": "Soul gained", + "SoulCollectorCanVent": "Soul Collector can Vent", + "DeathMeetingTimeIncrease": "Increased Meeting time when Death exists", + "SoulCollectorMeetingDeath": "Your target has died during the meeting. You have gained a soul.", "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", "ApocalypseImmune": "This player is immune because they are invincible!", @@ -2582,6 +2588,7 @@ "BakerBreadNeededToTransform": "Required number of bread to become Famine", "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", "BakerKillButtonText": "Bread", + "BakerCanVent": "Baker can Vent", "FamineKillButtonText": "Starve", "FamineStarveCooldown": "Famine starve cooldown", "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index b8efa53d64..73407c000c 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -23,6 +23,7 @@ internal class Baker : RoleBase private static OptionItem BreadNeededToTransform; private static OptionItem FamineStarveCooldown; + private static OptionItem BakerCanVent; private static readonly Dictionary> BreadList = []; private static readonly Dictionary> FamineList = []; @@ -36,6 +37,7 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Times); FamineStarveCooldown = FloatOptionItem.Create(Id + 11, "FamineStarveCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Baker]) .SetValueFormat(OptionFormat.Seconds); + BakerCanVent = BooleanOptionItem.Create(Id + 13, "BakerCanVent", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]); } public override void Init() { @@ -93,6 +95,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo return FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; } public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); + public override bool CanUseImpostorVentButton(PlayerControl pc) => BakerCanVent.GetBool(); public override void SetKillCooldown(byte id) { PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); @@ -204,6 +207,12 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr CanUseAbility = false; return false; } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (target.Is(CustomRoles.Baker)) return true; + return false; + } public override void OnFixedUpdate(PlayerControl player) { if (!AllHasBread(player) || player.Is(CustomRoles.Famine)) return; diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index cae38f559f..af46c94521 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -1,4 +1,5 @@ using Hazel; +using System.Diagnostics.Metrics; using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; @@ -11,32 +12,33 @@ internal class SoulCollector : RoleBase public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; - public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ private static OptionItem SoulCollectorPointsOpt; - private static OptionItem CollectOwnSoulOpt; private static OptionItem GetPassiveSouls; + private static OptionItem SoulCollectorCanVent; + private static OptionItem DeathMeetingTimeIncrease; private static readonly Dictionary SoulCollectorTarget = []; private static readonly Dictionary SoulCollectorPoints = []; - private static readonly Dictionary DidVote = []; public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulCollector, 1, zeroOne: false); SoulCollectorPointsOpt = IntegerOptionItem.Create(Id + 10, "SoulCollectorPointsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]) .SetValueFormat(OptionFormat.Times); - CollectOwnSoulOpt = BooleanOptionItem.Create(Id + 11, "SoulCollector_CollectOwnSoulOpt", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); GetPassiveSouls = BooleanOptionItem.Create(Id + 12, "GetPassiveSouls", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); + SoulCollectorCanVent = BooleanOptionItem.Create(Id + 13, "SoulCollectorCanVent", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]); + DeathMeetingTimeIncrease = IntegerOptionItem.Create(Id + 14, "DeathMeetingTimeIncrease", new(0, 120, 1), 0, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.SoulCollector]) + .SetValueFormat(OptionFormat.Seconds); } public override void Init() { playerIdList.Clear(); SoulCollectorTarget.Clear(); SoulCollectorPoints.Clear(); - DidVote.Clear(); } public override void Add(byte playerId) @@ -44,7 +46,6 @@ public override void Add(byte playerId) playerIdList.Add(playerId); SoulCollectorTarget.TryAdd(playerId, byte.MaxValue); SoulCollectorPoints.TryAdd(playerId, 0); - DidVote.TryAdd(playerId, false); CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } @@ -80,34 +81,36 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); - public override void OnVote(PlayerControl voter, PlayerControl target) + public override bool CanUseKillButton(PlayerControl pc) => pc.Is(CustomRoles.SoulCollector); + public override bool CanUseImpostorVentButton(PlayerControl pc) => SoulCollectorCanVent.GetBool(); + public static int GetDeathMeetingTimeIncrease() { - if (DidVote.TryGetValue(voter.PlayerId, out var voted) && voted) return; - if (SoulCollectorTarget[voter.PlayerId] != byte.MaxValue) return; - - DidVote[voter.PlayerId] = true; - - if (!CollectOwnSoulOpt.GetBool() && voter.PlayerId == target.PlayerId) + return DeathMeetingTimeIncrease.GetInt(); + } + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (SoulCollectorTarget[killer.PlayerId] != byte.MaxValue) { - Utils.SendMessage(GetString("SoulCollectorSelfVote"), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - Logger.Info($"{voter.GetNameWithRole()} self vote not allowed", "SoulCollector"); - SoulCollectorTarget[voter.PlayerId] = byte.MaxValue; - return; + killer.Notify(GetString("SoulCollectorTargetUsed")); + return false; } - - SoulCollectorTarget.Remove(voter.PlayerId); - SoulCollectorTarget.TryAdd(voter.PlayerId, target.PlayerId); - Logger.Info($"{voter.GetNameWithRole()} predicted the death of {target.GetNameWithRole()}", "SoulCollector"); - Utils.SendMessage(string.Format(GetString("SoulCollectorTarget"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - SendRPC(voter.PlayerId); + SoulCollectorTarget.Remove(killer.PlayerId); + SoulCollectorTarget.TryAdd(killer.PlayerId, target.PlayerId); + Logger.Info($"{killer.GetNameWithRole()} predicted the death of {target.GetNameWithRole()}", "SoulCollector"); + killer.Notify(string.Format(GetString("SoulCollectorTarget"), target.GetRealName())); + return false; + } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (target.Is(CustomRoles.SoulCollector)) return true; + return false; } - public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) { foreach (var playerId in SoulCollectorTarget.Keys) { - SoulCollectorTarget[playerId] = byte.MaxValue; - DidVote[playerId] = false; if (GetPassiveSouls.GetBool()) { SoulCollectorPoints[playerId]++; @@ -129,24 +132,42 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i { SoulCollectorTarget[playerId] = byte.MaxValue; SoulCollectorPoints[playerId]++; + if (GameStates.IsMeeting) _ = new LateTask(() => + { + Utils.SendMessage(GetString("SoulCollectorMeetingDeath"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + + }, 3f, "Set Chat Visible for Everyone"); + Utils.GetPlayerById(playerId).Notify(GetString("SoulCollectorSoulGained")); SendRPC(playerId); Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(playerId), ForceLoop: false); } if (SoulCollectorPoints[playerId] >= SoulCollectorPointsOpt.GetInt()) { SoulCollectorPoints[playerId] = SoulCollectorPointsOpt.GetInt(); + if (!GameStates.IsMeeting) { + PlayerControl sc = Utils.GetPlayerById(playerId); + sc.RpcSetCustomRole(CustomRoles.Death); + sc.Notify(GetString("SoulCollectorToDeath")); + sc.RpcGuardAndKill(sc); + } } } } - public override void AfterMeetingTasks() { + foreach (var playerId in SoulCollectorTarget.Keys) + { + SoulCollectorTarget[playerId] = byte.MaxValue; + } PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); - if (SoulCollectorPoints[sc.PlayerId] < SoulCollectorPointsOpt.GetInt()) return; - sc.RpcSetCustomRole(CustomRoles.Death); - sc.Notify(GetString("SoulCollectorToDeath")); - sc.RpcGuardAndKill(sc); + if (SoulCollectorPoints[sc.PlayerId] >= SoulCollectorPointsOpt.GetInt() && !sc.Is(CustomRoles.Death)) + { + sc.RpcSetCustomRole(CustomRoles.Death); + sc.Notify(GetString("SoulCollectorToDeath")); + sc.RpcGuardAndKill(sc); + } } + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; From 3b19b0f0ef2a80147f11fd03f5701b3a3faa61ec Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 11 May 2024 13:40:08 -0400 Subject: [PATCH 019/778] psychic and snitch --- Roles/Crewmate/Psychic.cs | 5 ++++- Roles/Crewmate/Snitch.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index a2e651af23..593f5aed15 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -22,6 +22,7 @@ internal class Psychic : RoleBase private static OptionItem NBshowEvil; private static OptionItem NEshowEvil; private static OptionItem NCshowEvil; + private static OptionItem NAshowEvil; private static readonly HashSet RedPlayer = []; @@ -35,6 +36,7 @@ public override void SetupCustomOption() NBshowEvil = BooleanOptionItem.Create(Id + 4, "NBareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); NEshowEvil = BooleanOptionItem.Create(Id + 5, "NEareRed", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); NCshowEvil = BooleanOptionItem.Create(Id + 7, "NCareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + NAshowEvil = BooleanOptionItem.Create(Id + 8, "NAareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); } public override void Init() { @@ -82,7 +84,8 @@ public static void GetRedName() (x.GetCustomRole().IsCrewKiller() && CkshowEvil.GetBool()) || (x.GetCustomRole().IsNE() && NEshowEvil.GetBool()) || (x.GetCustomRole().IsNC() && NCshowEvil.GetBool()) || - (x.GetCustomRole().IsNB() && NBshowEvil.GetBool()) + (x.GetCustomRole().IsNB() && NBshowEvil.GetBool()) || + (x.GetCustomRole().IsNA() && NAshowEvil.GetBool()) ).ToList(); List BadList = []; diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index f1f525401c..0f78c07fdb 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -44,7 +44,7 @@ public override void SetupCustomOption() OptionEnableTargetArrow = BooleanOptionItem.Create(Id + 10, "SnitchEnableTargetArrow", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionCanGetColoredArrow = BooleanOptionItem.Create(Id + 11, "SnitchCanGetArrowColor", true, TabGroup.CrewmateRoles, false).SetParent(OptionEnableTargetArrow); OptionCanFindNeutralKiller = BooleanOptionItem.Create(Id + 12, "SnitchCanFindNeutralKiller", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); - OptionCanFindNeutralApocalypse = BooleanOptionItem.Create(Id + 15, "SnitchCanFindNeutralApocalypse", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OptionCanFindNeutralApocalypse = BooleanOptionItem.Create(Id + 15, "SnitchCanFindNeutralApoc", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionCanFindMadmate = BooleanOptionItem.Create(Id + 14, "SnitchCanFindMadmate", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionRemainingTasks = IntegerOptionItem.Create(Id + 13, "SnitchRemainingTaskFound", new(0, 10, 1), 1, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Snitch); From b12be8fa4844f9267c514dcc977b6a0a8efe2c75 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 17 May 2024 21:52:22 -0400 Subject: [PATCH 020/778] why did the last commit do nothing --- Modules/CustomRolesHelper.cs | 4 +- Modules/MeetingTimeManager.cs | 2 +- Patches/IntroPatch.cs | 34 +++++- Patches/MeetingHudPatch.cs | 28 ++--- Resources/Lang/en_US.json | 7 +- Roles/Neutral/Amnesiac.cs | 7 ++ Roles/Neutral/Baker.cs | 206 +++++++++++++++++---------------- Roles/Neutral/Berserker.cs | 98 +++++++++------- Roles/Neutral/Executioner.cs | 2 +- Roles/Neutral/Lawyer.cs | 4 +- Roles/Neutral/PlagueBearer.cs | 6 +- Roles/Neutral/SoulCollector.cs | 56 ++++++--- main.cs | 4 - 13 files changed, 275 insertions(+), 183 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index bf3f2d10f3..32a3171f6b 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -244,9 +244,7 @@ public static bool IsNonNK(this CustomRoles role) // ROLE ASSIGNING, NOT NEUTRAL } public static bool IsNA(this CustomRoles role) { - return role.GetStaticRoleClass().ThisRoleType - is Custom_RoleType.NeutralApocalypse - || role.IsTNA(); + return role.GetStaticRoleClass().ThisRoleType is Custom_RoleType.NeutralApocalypse; } public static bool IsTNA(this CustomRoles role) // Transformed Neutral Apocalypse { diff --git a/Modules/MeetingTimeManager.cs b/Modules/MeetingTimeManager.cs index 9b2663c259..2372ba1bcd 100644 --- a/Modules/MeetingTimeManager.cs +++ b/Modules/MeetingTimeManager.cs @@ -61,7 +61,7 @@ public static void OnReportDeadBody() } if (SoulCollector.HasEnabled) { - BonusMeetingTime += SoulCollector.GetDeathMeetingTimeIncrease(); + BonusMeetingTime += SoulCollector.DeathMeetingTimeIncrease.GetInt(); } int TotalMeetingTime = DiscussionTime + VotingTime; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 93123a9760..1c383464e4 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -266,6 +266,17 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections } teamToDisplay = lawyerTeam; } + if (PlayerControl.LocalPlayer.IsNeutralApocalypse()) + { + var apocTeam = new Il2CppSystem.Collections.Generic.List(); + apocTeam.Add(PlayerControl.LocalPlayer); + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.IsNeutralApocalypse() && pc != PlayerControl.LocalPlayer) + apocTeam.Add(pc); + } + teamToDisplay = apocTeam; + } if (PlayerControl.LocalPlayer.Is(CustomRoles.SerialKiller)) { var serialkillerTeam = new Il2CppSystem.Collections.Generic.List(); @@ -400,6 +411,14 @@ public static void Postfix(IntroCutscene __instance) __instance.ImpostorText.gameObject.SetActive(true); __instance.ImpostorText.text = GetString("SubText.Madmate"); } + if (PlayerControl.LocalPlayer.IsNeutralApocalypse()) + { + __instance.TeamTitle.text = GetString("Apocalypse"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Death); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Apocalypse"); + } if (Options.CurrentGameMode == CustomGameMode.FFA) { __instance.TeamTitle.text = "FREE FOR ALL"; @@ -483,6 +502,19 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections __instance.overlayHandle.color = Palette.ImpostorRed; return true; } + else if (role.IsNA()) + { + yourTeam = new Il2CppSystem.Collections.Generic.List(); + yourTeam.Add(PlayerControl.LocalPlayer); + foreach (var pc in Main.AllPlayerControls.Where(x => !x.AmOwner).ToArray()) + { + if (pc.IsNeutralApocalypse()) + yourTeam.Add(pc); + } + __instance.BeginCrewmate(yourTeam); + __instance.overlayHandle.color = Utils.GetRoleColor(CustomRoles.Death); + return false; + } else if (role is CustomRoles.Vigilante or CustomRoles.Sheriff or CustomRoles.Jailer or CustomRoles.Investigator or CustomRoles.Knight or CustomRoles.Medic or CustomRoles.Deceiver or CustomRoles.Witness or CustomRoles.Monarch or CustomRoles.Overseer or CustomRoles.Reverie or CustomRoles.Admirer or CustomRoles.Deputy or CustomRoles.Crusader or CustomRoles.CopyCat) { yourTeam = new Il2CppSystem.Collections.Generic.List(); @@ -495,7 +527,7 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections __instance.overlayHandle.color = Palette.CrewmateBlue; return false; } - else if (role is CustomRoles.Romantic or CustomRoles.Doppelganger or CustomRoles.Pyromaniac or CustomRoles.Huntsman or CustomRoles.RuthlessRomantic or CustomRoles.VengefulRomantic or CustomRoles.SerialKiller or CustomRoles.Jackal or CustomRoles.Seeker or CustomRoles.Pixie or CustomRoles.Agitater or CustomRoles.CursedSoul or CustomRoles.Pirate or CustomRoles.Amnesiac or CustomRoles.Arsonist or CustomRoles.Sidekick or CustomRoles.Innocent or CustomRoles.Pelican or CustomRoles.Pursuer or CustomRoles.Revolutionist or CustomRoles.Hater or CustomRoles.Demon or CustomRoles.Glitch or CustomRoles.Juggernaut or CustomRoles.Stalker or CustomRoles.Provocateur or CustomRoles.BloodKnight or CustomRoles.SerialKiller or CustomRoles.Werewolf or CustomRoles.Maverick or CustomRoles.Shroud or CustomRoles.Follower or CustomRoles.Cultist or CustomRoles.Pelican or CustomRoles.Infectious or CustomRoles.Virus or CustomRoles.Pickpocket or CustomRoles.Traitor or CustomRoles.PlagueBearer or CustomRoles.Pestilence or CustomRoles.Death or CustomRoles.Baker or CustomRoles.Famine or CustomRoles.SoulCollector or CustomRoles.Death or CustomRoles.Berserker or CustomRoles.War or CustomRoles.Spiritcaller or CustomRoles.Necromancer or CustomRoles.Medusa or CustomRoles.HexMaster or CustomRoles.Wraith or CustomRoles.Jinx or CustomRoles.Poisoner or CustomRoles.PotionMaster) //or CustomRoles.Occultist + else if (role is CustomRoles.Romantic or CustomRoles.Doppelganger or CustomRoles.Pyromaniac or CustomRoles.Huntsman or CustomRoles.RuthlessRomantic or CustomRoles.VengefulRomantic or CustomRoles.SerialKiller or CustomRoles.Jackal or CustomRoles.Seeker or CustomRoles.Pixie or CustomRoles.Agitater or CustomRoles.CursedSoul or CustomRoles.Pirate or CustomRoles.Amnesiac or CustomRoles.Arsonist or CustomRoles.Sidekick or CustomRoles.Innocent or CustomRoles.Pelican or CustomRoles.Pursuer or CustomRoles.Revolutionist or CustomRoles.Hater or CustomRoles.Demon or CustomRoles.Glitch or CustomRoles.Juggernaut or CustomRoles.Stalker or CustomRoles.Provocateur or CustomRoles.BloodKnight or CustomRoles.SerialKiller or CustomRoles.Werewolf or CustomRoles.Maverick or CustomRoles.Shroud or CustomRoles.Follower or CustomRoles.Cultist or CustomRoles.Pelican or CustomRoles.Infectious or CustomRoles.Virus or CustomRoles.Pickpocket or CustomRoles.Traitor or CustomRoles.Spiritcaller or CustomRoles.Necromancer or CustomRoles.Medusa or CustomRoles.HexMaster or CustomRoles.Wraith or CustomRoles.Jinx or CustomRoles.Poisoner or CustomRoles.PotionMaster) //or CustomRoles.Occultist { yourTeam = new Il2CppSystem.Collections.Generic.List(); yourTeam.Add(PlayerControl.LocalPlayer); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index f0df048aed..8dfda07a10 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -543,8 +543,8 @@ private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, pa Witch.OnCheckForEndVoting(deathReason, playerIds); HexMaster.OnCheckForEndVoting(deathReason, playerIds); Virus.OnCheckForEndVoting(deathReason, playerIds); - Baker.OnCheckForEndVoting(deathReason, playerIds); - SoulCollector.OnCheckForEndVoting(deathReason, playerIds); + Famine.OnCheckForEndVoting(deathReason, playerIds); + Death.OnCheckForEndVoting(deathReason, playerIds); foreach (var playerId in playerIds) { @@ -809,33 +809,33 @@ public static void NotifyRoleSkillOnMeetingStart() AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); } // Apocalypse Notify - if (CustomRoles.Death.RoleExist()) + if (CustomRoles.Pestilence.RoleExist()) { _ = new LateTask(() => { - AddMsg(string.Format(GetString("SoulCollectorTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Death), GetString("ApocalypseIsNigh"))); - }, 3f, "Death Apocalypse Notify"); + AddMsg(string.Format(GetString("PestilenceTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Pestilence), GetString("ApocalypseIsNigh"))); + }, 3f, "Pestilence Apocalypse Notify"); } - if (CustomRoles.Famine.RoleExist()) + if (CustomRoles.War.RoleExist()) { _ = new LateTask(() => { - AddMsg(string.Format(GetString("BakerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Famine), GetString("ApocalypseIsNigh"))); - }, 3f, "Famine Apocalypse Notify"); + AddMsg(string.Format(GetString("BerserkerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.War), GetString("ApocalypseIsNigh"))); + }, 3f, "War Apocalypse Notify"); } - if (CustomRoles.War.RoleExist()) + if (CustomRoles.Famine.RoleExist()) { _ = new LateTask(() => { - AddMsg(string.Format(GetString("BerserkerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.War), GetString("ApocalypseIsNigh"))); - }, 3f, "War Apocalypse Notify"); + AddMsg(string.Format(GetString("BakerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Famine), GetString("ApocalypseIsNigh"))); + }, 3f, "Famine Apocalypse Notify"); } - if (CustomRoles.Pestilence.RoleExist()) + if (CustomRoles.Death.RoleExist()) { _ = new LateTask(() => { - AddMsg(string.Format(GetString("PestilenceTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Pestilence), GetString("ApocalypseIsNigh"))); - }, 3f, "Pestilence Apocalypse Notify"); + AddMsg(string.Format(GetString("SoulCollectorTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Death), GetString("ApocalypseIsNigh"))); + }, 3f, "Death Apocalypse Notify"); } string MimicMsg = ""; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 65200c26f8..d4f385ba07 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -16,6 +16,7 @@ "SubText.Crewmate": "Find and exile the Impostors", "SubText.Impostor": "Sabotage and kill everyone", "SubText.Neutral": "Work alone to achieve your victory", + "SubText.Apocalypse": "Become unstoppable with your team", "SubText.Madmate": "Help the Impostors", "TypeImpostor": "Impostors", @@ -244,7 +245,7 @@ "Collector": "Collector", "Provocateur": "Provocateur", "BloodKnight": "Blood Knight", - "Apocalypse": "Apocalypse Team", + "Apocalypse": "Apocalypse", "PlagueBearer": "Plaguebearer", "Pestilence": "Pestilence", "SoulCollector": "Soul Collector", @@ -548,7 +549,7 @@ "PestilenceInfo": "Obliterate everyone!", "SoulCollectorInfo": "Predict deaths to collect souls", "DeathInfo": "Enact Armageddon", - "BakerInfo": "Feed Players Bread in order to become Famine", + "BakerInfo": "Feed Players Bread to become Famine", "FamineInfo": "Starve Everyone", "BerserkerInfo": "Kill to increase your level", "WarInfo": "Destroy everything", @@ -1457,6 +1458,7 @@ "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", "LawyerCanTargetImpostor": "Can Target Impostors", "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", + "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", "LawyerCanTargetCrewmate": "Can Target Crewmates", "LawyerCanTargetJester": "Can Target Jester", "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", @@ -2584,6 +2586,7 @@ "SoulCollectorCanVent": "Soul Collector can Vent", "DeathMeetingTimeIncrease": "Increased Meeting time when Death exists", "SoulCollectorMeetingDeath": "Your target has died during the meeting. You have gained a soul.", + "SoulCollectorKillButtonText": "Predict", "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", "ApocalypseImmune": "This player is immune because they are invincible!", diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 041d245c98..824792c650 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -143,6 +143,13 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, GameData.Pl Main.TasklessCrewmate.Add(__instance.PlayerId); } } + if (tar.GetCustomRole().IsNA()) + { + __instance.RpcSetCustomRole(tar.GetCustomRole()); + __instance.GetRoleClass().Add(__instance.PlayerId); + __instance.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("YouRememberedRole"))); + tar.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedYourRole"))); + } if (tar.GetCustomRole().IsAmneNK()) { __instance.RpcSetCustomRole(tar.GetCustomRole()); diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 73407c000c..6a4144d289 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -4,6 +4,7 @@ using TOHE.Roles.Core; using TOHE.Roles.Impostor; using static TOHE.Options; +using static TOHE.PlayerState; using static TOHE.Translator; using static TOHE.Utils; using static UnityEngine.ParticleSystem.PlaybackState; @@ -22,13 +23,13 @@ internal class Baker : RoleBase //==================================================================\\ private static OptionItem BreadNeededToTransform; - private static OptionItem FamineStarveCooldown; - private static OptionItem BakerCanVent; + public static OptionItem FamineStarveCooldown; + public static OptionItem BakerCanVent; - private static readonly Dictionary> BreadList = []; - private static readonly Dictionary> FamineList = []; + public static readonly Dictionary> BreadList = []; + public static readonly Dictionary> FamineList = []; private static bool CanUseAbility; - private static bool StarvedNonBreaded; + public static bool StarvedNonBreaded; public override void SetupCustomOption() { @@ -83,35 +84,17 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) byte BreadHolderId = reader.ReadByte(); BreadList[BakerId].Add(BreadHolderId); } - public override string GetProgressText(byte playerId, bool comms) => CustomRoles.Baker.RoleExist() ? ColorString(GetRoleColor(CustomRoles.Baker).ShadeColor(0.25f), $"({BreadedPlayerCount(playerId).Item1}/{BreadedPlayerCount(playerId).Item2})") : ""; + public override string GetProgressText(byte playerId, bool comms) => ColorString(GetRoleColor(CustomRoles.Baker).ShadeColor(0.25f), $"({BreadedPlayerCount(playerId).Item1}/{BreadedPlayerCount(playerId).Item2})"); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - { - if (seer.GetCustomRole() is CustomRoles.Baker || !StarvedNonBreaded) - return BreadList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; - else - return FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; - } + => BreadList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); public override bool CanUseImpostorVentButton(PlayerControl pc) => BakerCanVent.GetBool(); - public override void SetKillCooldown(byte id) - { - PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); - if (baker.GetCustomRole() is CustomRoles.Famine) - Main.AllPlayerKillCooldown[id] = FamineStarveCooldown.GetFloat(); - else - Main.AllPlayerKillCooldown[id] = Main.AllPlayerKillCooldown[id]; - } - public override void SetAbilityButtonText(HudManager hud, byte playerId) - { - if (CustomRoles.Baker.RoleExist()) - hud.KillButton.OverrideText(GetString("BakerKillButtonText")); - else if (CustomRoles.Famine.RoleExist()) - hud.KillButton.OverrideText(GetString("FamineKillButtonText")); - } - private static bool HasBread(byte pc, byte target) + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Main.AllPlayerKillCooldown[id]; + public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("BakerKillButtonText")); + public static bool HasBread(byte pc, byte target) { return BreadList[pc].Contains(target); } @@ -126,25 +109,6 @@ private static bool AllHasBread(PlayerControl player) public override void OnReportDeadBody(PlayerControl marg, PlayerControl iscute) { CanUseAbility = true; - PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); - if (baker.GetCustomRole() is CustomRoles.Famine) - { - foreach (var pc in FamineList) - { - foreach (var tar in pc.Value) - { - var target = Utils.GetPlayerById(tar); - var killer = Utils.GetPlayerById(pc.Key); - if (killer == null || target == null) continue; - target.RpcExileV2(); - target.SetRealKiller(killer); - Main.PlayerStates[tar].deathReason = PlayerState.DeathReason.Starved; - Main.PlayerStates[tar].SetDead(); - MurderPlayerPatch.AfterPlayerDeathTasks(killer, target, true); - Logger.Info($"{killer.GetRealName()} has starved {target.GetRealName()}", "Famine"); - } - } - } } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { @@ -155,62 +119,26 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i BreadList[playerId].Remove(playerId); } } - foreach (var playerId in FamineList.Keys.ToArray()) - { - if (deadPlayer.PlayerId == playerId) - { - FamineList[playerId].Remove(playerId); - } - } } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (killer.GetCustomRole() == CustomRoles.Famine && !target.IsNeutralApocalypse()) { - if (StarvedNonBreaded) { - FamineList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); - killer.Notify(GetString("FamineStarved")); - Logger.Info(target.GetRealName() + $" has been starved", "Famine"); - CanUseAbility = true; - return false; - } - } if (!CanUseAbility) - { killer.Notify(GetString("BakerBreadUsedAlready")); - return false; - } - if (target.IsNeutralApocalypse()) - { - if (killer.GetCustomRole() == CustomRoles.Baker) - killer.Notify(GetString("BakerCantBreadApoc")); - else - killer.Notify(GetString("FamineCantStarveApoc")); - return false; - } - if (HasBread(killer.PlayerId, target.PlayerId)) - { + + else if (target.IsNeutralApocalypse()) + killer.Notify(GetString("BakerCantBreadApoc")); + + else if (HasBread(killer.PlayerId, target.PlayerId)) killer.Notify(GetString("BakerAlreadyBreaded")); - return false; - } - if (FamineList[killer.PlayerId].Contains(target.PlayerId)) - { - killer.Notify(GetString("FamineAlreadyStarved")); - return false; + + else { + BreadList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("BakerBreaded")); + Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); + CanUseAbility = false; } - BreadList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); - killer.Notify(GetString("BakerBreaded")); - Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); - CanUseAbility = false; - return false; - } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; - if (target.Is(CustomRoles.Baker)) return true; return false; } public override void OnFixedUpdate(PlayerControl player) @@ -228,7 +156,7 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para if (exileIds.Contains(playerIdList.First())) return; if (StarvedNonBreaded) return; var deathList = new List(); - PlayerControl baker = Utils.GetPlayerById(playerIdList.First()); + PlayerControl baker = GetPlayerById(playerIdList.First()); foreach (var pc in Main.AllAlivePlayerControls) { if (pc.IsNeutralApocalypse() || HasBread(baker.PlayerId, pc.PlayerId)) continue; @@ -248,6 +176,88 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Starved, [.. deathList]); BreadList.Clear(); StarvedNonBreaded = true; - CanUseAbility = true; + } +} +internal class Famine : RoleBase +{ + //===========================SETUP================================\\ + public static readonly HashSet playerIdList = []; + public static bool HasEnabled => playerIdList.Any(); + public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; + //==================================================================\\ + public override void Init() + { + playerIdList.Clear(); + } + public override void Add(byte playerId) + { + playerIdList.Add(playerId); + + if (!AmongUsClient.Instance.AmHost) return; + if (!Main.ResetCamPlayerList.Contains(playerId)) + Main.ResetCamPlayerList.Add(playerId); + CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); + } + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Baker.FamineStarveCooldown.GetFloat(); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(true); + public override bool CanUseKillButton(PlayerControl pc) => true; + public override bool CanUseImpostorVentButton(PlayerControl pc) => Baker.BakerCanVent.GetBool(); + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; + public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("FamineKillButtonText")); + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => Baker.FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"⁂" : ""; + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (target.IsNeutralApocalypse()) killer.Notify(GetString("FamineCantStarveApoc")); + else if (Baker.FamineList[killer.PlayerId].Contains(target.PlayerId)) + killer.Notify(GetString("FamineAlreadyStarved")); + else if (Baker.StarvedNonBreaded) + { + Baker.FamineList[killer.PlayerId].Add(target.PlayerId); + Baker.SendRPC(killer, target); + Utils.NotifyRoles(SpecifySeer: killer); + killer.Notify(GetString("FamineStarved")); + Logger.Info(target.GetRealName() + $" has been starved", "Famine"); + } + return false; + } + private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) + { + foreach (var playerId in Baker.FamineList.Keys.ToArray()) + { + if (deadPlayer.PlayerId == playerId) + { + Baker.FamineList[playerId].Remove(playerId); + } + } + } + public override void OnReportDeadBody(PlayerControl marg, PlayerControl iscute) + { + foreach (var pc in Baker.FamineList) + { + foreach (var tar in pc.Value) + { + var target = Utils.GetPlayerById(tar); + var killer = Utils.GetPlayerById(pc.Key); + if (killer == null || target == null) continue; + target.RpcExileV2(); + target.SetRealKiller(killer); + Main.PlayerStates[tar].deathReason = PlayerState.DeathReason.Starved; + Main.PlayerStates[tar].SetDead(); + MurderPlayerPatch.AfterPlayerDeathTasks(killer, target, true); + Logger.Info($"{killer.GetRealName()} has starved {target.GetRealName()}", "Famine"); + } + } + + } + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + { + Baker.OnCheckForEndVoting(deathReason, exileIds); } } diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index af1369b108..039310ec1d 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -4,6 +4,7 @@ using static TOHE.Options; using static TOHE.Translator; using AmongUs.GameOptions; +using TOHE.Roles.Core; namespace TOHE.Roles.Neutral; @@ -32,11 +33,11 @@ internal class Berserker : RoleBase //public static OptionItem BerserkerSpeed; private static OptionItem BerserkerFourCanNotKill; private static OptionItem BerserkerImmortalLevel; - private static OptionItem WarKillCooldown; + public static OptionItem WarKillCooldown; private static OptionItem BerserkerHasImpostorVision; - private static OptionItem WarHasImpostorVision; + public static OptionItem WarHasImpostorVision; private static OptionItem BerserkerCanVent; - private static OptionItem WarCanVent; + public static OptionItem WarCanVent; private static readonly Dictionary BerserkerKillMax = []; @@ -86,43 +87,20 @@ public override void Remove(byte playerId) BerserkerKillMax.Remove(playerId); } - public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); - public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); - public override void SetKillCooldown(byte id) - { - if (CustomRoles.Berserker.RoleExist()) - Main.AllPlayerKillCooldown[id] = BerserkerKillCooldown.GetFloat(); - else if (CustomRoles.War.RoleExist()) - Main.AllPlayerKillCooldown[id] = WarKillCooldown.GetFloat(); - } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - if (pc.Is(CustomRoles.Berserker) && BerserkerCanVent.GetBool()) return true; - if (pc.Is(CustomRoles.War) && WarCanVent.GetBool()) return true; - return false; - } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (CustomRoles.Berserker.RoleExist()) - opt.SetVision(BerserkerHasImpostorVision.GetBool()); - else if (CustomRoles.War.RoleExist()) - opt.SetVision(WarHasImpostorVision.GetBool()); - } + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) + => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override void SetKillCooldown(byte id) + => Main.AllPlayerKillCooldown[id] = BerserkerKillCooldown.GetFloat(); + public override bool CanUseImpostorVentButton(PlayerControl pc) => BerserkerCanVent.GetBool(); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + => opt.SetVision(BerserkerHasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (BerserkerKillMax[target.PlayerId] >= BerserkerImmortalLevel.GetInt() && BerserkerFourCanNotKill.GetBool()) - { - killer.RpcTeleport(target.GetCustomPosition()); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); - killer.SetKillCooldown(target: target, forceAnime: true); - return false; - } - return true; - } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (target.IsNeutralApocalypse()) return false; + bool noScav = true; if (BerserkerKillMax[killer.PlayerId] < BerserkerMax.GetInt()) { BerserkerKillMax[killer.PlayerId]++; @@ -141,7 +119,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Main.AllPlayerKillCooldown[killer.PlayerId] = BerserkerOneKillCooldown.GetFloat(); } - if (BerserkerKillMax[killer.PlayerId] == BerserkerScavengerLevel.GetInt() && BerserkerTwoCanScavenger.GetBool()) + if (BerserkerKillMax[killer.PlayerId] >= BerserkerScavengerLevel.GetInt() && BerserkerTwoCanScavenger.GetBool()) { killer.RpcTeleport(target.GetCustomPosition()); RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); @@ -153,7 +131,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.SetKillCooldownV2(); target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Berserker), Translator.GetString("KilledByBerserker"))); - return false; + noScav = false; } if (BerserkerKillMax[killer.PlayerId] >= BerserkerBomberLevel.GetInt() && BerserkerThreeCanBomber.GetBool()) @@ -176,13 +154,53 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } } } - if (BerserkerKillMax[killer.PlayerId] >= BerserkerImmortalLevel.GetInt() && BerserkerFourCanNotKill.GetBool()) + if (BerserkerKillMax[killer.PlayerId] >= BerserkerImmortalLevel.GetInt() && BerserkerFourCanNotKill.GetBool()&& !killer.Is(CustomRoles.War)) { killer.RpcSetCustomRole(CustomRoles.War); killer.Notify(GetString("BerserkerToWar")); Main.AllPlayerKillCooldown[killer.PlayerId] = WarKillCooldown.GetFloat(); } - return true; + return noScav; } } +internal class War : RoleBase +{ + //===========================SETUP================================\\ + public static readonly HashSet playerIdList = []; + public static bool HasEnabled => playerIdList.Any(); + public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; + //==================================================================\\ + + public override void Init() + { + playerIdList.Clear(); + } + public override void Add(byte playerId) + { + playerIdList.Add(playerId); + + if (!AmongUsClient.Instance.AmHost) return; + if (!Main.ResetCamPlayerList.Contains(playerId)) + Main.ResetCamPlayerList.Add(playerId); + } + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) + => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Berserker.WarKillCooldown.GetFloat(); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(Berserker.WarHasImpostorVision.GetBool()); + public override bool CanUseKillButton(PlayerControl pc) => true; + public override bool CanUseImpostorVentButton(PlayerControl pc) => Berserker.WarCanVent.GetBool(); + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return false; + } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + return CustomRoles.Berserker.GetStaticRoleClass().OnCheckMurderAsKiller(killer, target); + } +} \ No newline at end of file diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 04d997f275..0873a8bfcd 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -82,11 +82,11 @@ public override void Add(byte playerId) { if (playerId == target.PlayerId) continue; else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; + else if (!CanTargetNeutralApocalypse.GetBool() && target.GetCustomRole().IsNA()) continue; else if (!CanTargetNeutralKiller.GetBool() && target.GetCustomRole().IsNK()) continue; else if (!CanTargetNeutralBenign.GetBool() && target.GetCustomRole().IsNB()) continue; else if (!CanTargetNeutralEvil.GetBool() && target.GetCustomRole().IsNE()) continue; else if (!CanTargetNeutralChaos.GetBool() && target.GetCustomRole().IsNC()) continue; - else if (!CanTargetNeutralApocalypse.GetBool() && target.GetCustomRole().IsNA()) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index d220bde88f..4dd14dcd4e 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -55,7 +55,7 @@ public override void SetupCustomOption() SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Lawyer); CanTargetImpostor = BooleanOptionItem.Create(Id + 10, "LawyerCanTargetImpostor", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetNeutralKiller = BooleanOptionItem.Create(Id + 11, "LawyerCanTargetNeutralKiller", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - CanTargetNeutralApoc = BooleanOptionItem.Create(Id + 18, "ExecutionerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + CanTargetNeutralApoc = BooleanOptionItem.Create(Id + 18, "LawyerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetCrewmate = BooleanOptionItem.Create(Id + 12, "LawyerCanTargetCrewmate", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetJester = BooleanOptionItem.Create(Id + 13, "LawyerCanTargetJester", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); KnowTargetRole = BooleanOptionItem.Create(Id + 14, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); @@ -83,8 +83,8 @@ public override void Add(byte playerId) { if (playerId == target.PlayerId) continue; else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; + else if (!CanTargetNeutralApoc.GetBool() && target.GetCustomRole().IsNA()) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; - else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; else if (!CanTargetCrewmate.GetBool() && target.Is(Custom_Team.Crewmate)) continue; else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index a283deff4b..dfc8a13a1e 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -214,7 +214,7 @@ internal class Pestilence : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ public override void Init() @@ -229,7 +229,9 @@ public override void Add(byte playerId) if (!Main.ResetCamPlayerList.Contains(playerId)) Main.ResetCamPlayerList.Add(playerId); } - + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = PlagueBearer.PestilenceCooldownOpt.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(PlagueBearer.PestilenceHasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index af46c94521..eda90d757b 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -1,3 +1,4 @@ +using AmongUs.GameOptions; using Hazel; using System.Diagnostics.Metrics; using TOHE.Roles.Core; @@ -18,8 +19,8 @@ internal class SoulCollector : RoleBase private static OptionItem SoulCollectorPointsOpt; private static OptionItem GetPassiveSouls; - private static OptionItem SoulCollectorCanVent; - private static OptionItem DeathMeetingTimeIncrease; + public static OptionItem SoulCollectorCanVent; + public static OptionItem DeathMeetingTimeIncrease; private static readonly Dictionary SoulCollectorTarget = []; private static readonly Dictionary SoulCollectorPoints = []; @@ -50,8 +51,8 @@ public override void Add(byte playerId) CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } - public override string GetProgressText(byte playerId, bool cvooms) => CustomRoles.SoulCollector.RoleExist() ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid") : ""; - + public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid"); + public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("SoulCollectorKillButtonText")); private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); @@ -81,12 +82,10 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => SoulCollectorTarget[seer.PlayerId] == seen.PlayerId ? $"♠" : ""; public override bool CanUseKillButton(PlayerControl pc) => pc.Is(CustomRoles.SoulCollector); public override bool CanUseImpostorVentButton(PlayerControl pc) => SoulCollectorCanVent.GetBool(); - public static int GetDeathMeetingTimeIncrease() - { - return DeathMeetingTimeIncrease.GetInt(); - } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; @@ -101,12 +100,6 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr killer.Notify(string.Format(GetString("SoulCollectorTarget"), target.GetRealName())); return false; } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; - if (target.Is(CustomRoles.SoulCollector)) return true; - return false; - } public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) { foreach (var playerId in SoulCollectorTarget.Keys) @@ -167,7 +160,6 @@ public override void AfterMeetingTasks() sc.RpcGuardAndKill(sc); } } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; @@ -193,4 +185,38 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [.. deathList]); } +} +internal class Death : RoleBase +{ + //===========================SETUP================================\\ + public static readonly HashSet playerIdList = []; + public static bool HasEnabled => playerIdList.Any(); + public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; + //==================================================================\\ + + public override void Init() + { + playerIdList.Clear(); + } + public override void Add(byte playerId) + { + playerIdList.Add(playerId); + + if (!AmongUsClient.Instance.AmHost) return; + if (!Main.ResetCamPlayerList.Contains(playerId)) + Main.ResetCamPlayerList.Add(playerId); + } + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) + => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(true); + public override bool CanUseImpostorVentButton(PlayerControl pc) => SoulCollector.SoulCollectorCanVent.GetBool(); + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; + + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + { + SoulCollector.OnCheckForEndVoting(deathReason, exileIds); + } } \ No newline at end of file diff --git a/main.cs b/main.cs index d9e744e7b6..771c110472 100644 --- a/main.cs +++ b/main.cs @@ -333,10 +333,6 @@ public static void LoadRoleClasses() CustomRolesHelper.DuplicatedRoles = new Dictionary { - { CustomRoles.Pestilence, typeof(PlagueBearer) }, - { CustomRoles.Death, typeof(SoulCollector) }, - { CustomRoles.War, typeof(Berserker) }, - { CustomRoles.Famine, typeof(Baker) }, { CustomRoles.NiceMini, typeof(Mini) }, { CustomRoles.EvilMini, typeof(Mini) } }; From a366cf7c914f5a162e8b33008602677e412406b2 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 18 May 2024 12:44:33 -0400 Subject: [PATCH 021/778] baker id --- Modules/OptionHolder.cs | 2 +- Roles/Neutral/Baker.cs | 2 +- Roles/Neutral/Berserker.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 90026503e0..477bb8f938 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -591,7 +591,7 @@ public static float GetRoleChance(CustomRoles role) public static void Load() { //####################################### - // 28400 last id for roles/add-ons (Next use 28500) + // 28500 last id for roles/add-ons (Next use 28600) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 6a4144d289..0f7b238f1d 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -14,7 +14,7 @@ namespace TOHE.Roles.Neutral; internal class Baker : RoleBase { //===========================SETUP================================\\ - private static readonly int Id = 28400; + private const int Id = 28500; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 039310ec1d..730718f1c7 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -149,8 +149,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (Vector2.Distance(killer.transform.position, player.transform.position) <= Bomber.BomberRadius.GetFloat()) { Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Bombed; - player.SetRealKiller(killer); player.RpcMurderPlayer(player); + player.SetRealKiller(killer); } } } From d4fe844f7d673e9f46195891acd2e885f3f4b024 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 18 May 2024 18:10:45 -0400 Subject: [PATCH 022/778] auto options --- Modules/OptionHolder.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 477bb8f938..4e28b6652a 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -842,14 +842,8 @@ public static void Load() TransformedNeutralApocalypseCanBeGuessed = BooleanOptionItem.Create(60024, "TNACanBeGuessed", false, TabGroup.NeutralRoles, false) .SetGameMode(CustomGameMode.Standard) .SetHeader(true); - CustomRoles.Baker.GetStaticRoleClass().SetupCustomOption(); - - CustomRoles.Berserker.GetStaticRoleClass().SetupCustomOption(); - - CustomRoles.PlagueBearer.GetStaticRoleClass().SetupCustomOption(); - - CustomRoles.SoulCollector.GetStaticRoleClass().SetupCustomOption(); + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralApocalypse).ForEach(r => r.SetupCustomOption()); #endregion #region Add-Ons Settings From dceeaa1c1ea98dfe665211078b5f6968de441cc3 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 19 May 2024 12:30:32 -0400 Subject: [PATCH 023/778] conflicts --- Roles/Neutral/Baker.cs | 3 ++- Roles/Neutral/Berserker.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 0f7b238f1d..0d2f5f557d 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -15,9 +15,10 @@ internal class Baker : RoleBase { //===========================SETUP================================\\ private const int Id = 28500; + public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 730718f1c7..36d45ac020 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -15,7 +15,7 @@ internal class Berserker : RoleBase private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ From dd04db48598dcc7d3af56e563a1c8e4b05afec2d Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 19 May 2024 12:35:10 -0400 Subject: [PATCH 024/778] 2nd class stuff --- Roles/Neutral/Baker.cs | 9 +-------- Roles/Neutral/Berserker.cs | 9 +-------- Roles/Neutral/PlagueBearer.cs | 5 +++++ Roles/Neutral/SoulCollector.cs | 9 +-------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 0d2f5f557d..0f384c5aae 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -182,19 +182,12 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para internal class Famine : RoleBase { //===========================SETUP================================\\ - public static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); - public override bool IsEnable => HasEnabled; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Baker); public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Init() - { - playerIdList.Clear(); - } public override void Add(byte playerId) { - playerIdList.Add(playerId); if (!AmongUsClient.Instance.AmHost) return; if (!Main.ResetCamPlayerList.Contains(playerId)) diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 36d45ac020..431708104c 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -167,20 +167,13 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t internal class War : RoleBase { //===========================SETUP================================\\ - public static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); - public override bool IsEnable => HasEnabled; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Berserker); public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Init() - { - playerIdList.Clear(); - } public override void Add(byte playerId) { - playerIdList.Add(playerId); if (!AmongUsClient.Instance.AmHost) return; if (!Main.ResetCamPlayerList.Contains(playerId)) diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 9186be3dd1..876a0cee77 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -239,6 +239,11 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t target.RpcMurderPlayer(killer); return false; } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (target.IsNeutralApocalypse()) return false; + return true; + } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl pc, CustomRoles role, ref bool guesserSuicide) { diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 84fe64147c..75bc7e63a0 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -189,20 +189,13 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para internal class Death : RoleBase { //===========================SETUP================================\\ - public static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); - public override bool IsEnable => HasEnabled; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.SoulCollector); public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Init() - { - playerIdList.Clear(); - } public override void Add(byte playerId) { - playerIdList.Add(playerId); if (!AmongUsClient.Instance.AmHost) return; if (!Main.ResetCamPlayerList.Contains(playerId)) From 39cbc6cee11954fd6b832ccf373831f7935ecc5e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 20 May 2024 02:28:09 +0800 Subject: [PATCH 025/778] Add TextBoxPatch (Ported from EHR) --- Patches/ChatCommandPatch.cs | 15 ------- Patches/ChatControlPatch.cs | 49 ++++------------------- Patches/TextBoxPatch.cs | 79 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 57 deletions(-) create mode 100644 Patches/TextBoxPatch.cs diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 5ac3f89f8d..8ca4d0787f 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2571,21 +2571,6 @@ public static void Postfix(ChatController __instance) __instance.timeSinceLastMessage = 0f; } } - -[HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))] -internal class AddChatPatch -{ - public static void Postfix(string chatText) - { - switch (chatText) - { - default: - break; - } - if (!AmongUsClient.Instance.AmHost) return; - } -} - [HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] internal class UpdateCharCountPatch { diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs index 9fbb420b3c..c24ec85d57 100644 --- a/Patches/ChatControlPatch.cs +++ b/Patches/ChatControlPatch.cs @@ -9,21 +9,6 @@ class ChatControllerUpdatePatch { public static int CurrentHistorySelection = -1; - static readonly Dictionary replaceDic = new() - { - { "(", " (" }, - { ")", ") " }, - { ",", ", " }, - { ":", ": " }, - { "[", "【" }, - { "]", "】" }, - { "‘", " '" }, - { "’", "' " }, - { "“", " ''" }, - { "”", "'' " }, - { "!", "! " }, - { Environment.NewLine, " " } - }; public static void Prefix() { if (AmongUsClient.Instance.AmHost && DataManager.Settings.Multiplayer.ChatMode == InnerNet.QuickChatModes.QuickChatOnly) @@ -33,13 +18,15 @@ public static void Postfix(ChatController __instance) { if (Main.DarkTheme.Value) { + var backgroundColor = new Color32(40, 40, 40, byte.MaxValue); + // free chat - __instance.freeChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + __instance.freeChatField.background.color = backgroundColor; __instance.freeChatField.textArea.compoText.Color(Color.white); __instance.freeChatField.textArea.outputText.color = Color.white; // quick chat - __instance.quickChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + __instance.quickChatField.background.color = backgroundColor; __instance.quickChatField.text.color = Color.white; } else @@ -50,35 +37,13 @@ public static void Postfix(ChatController __instance) if (!__instance.freeChatField.textArea.hasFocus) return; if (!GameStates.IsModHost) return; - __instance.freeChatField.textArea.characterLimit = AmongUsClient.Instance.AmHost ? 999 : 300; + __instance.freeChatField.textArea.characterLimit = AmongUsClient.Instance.AmHost ? 2000 : 300; if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.V)) - { - if (!string.IsNullOrEmpty(GUIUtility.systemCopyBuffer)) - { - string replacedText = GUIUtility.systemCopyBuffer; - foreach (var pair in replaceDic) - { - replacedText = replacedText.Replace(pair.Key, pair.Value); - } - - if ((__instance.freeChatField.textArea.text + replacedText).Length < __instance.freeChatField.textArea.characterLimit) - __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + replacedText); - else - { - int remainingLength = __instance.freeChatField.textArea.characterLimit - __instance.freeChatField.textArea.text.Length; - if (remainingLength > 0) - { - string text = replacedText[..remainingLength]; - __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + text); - } - } - } - } - + __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + GUIUtility.systemCopyBuffer); if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X)) { @@ -100,4 +65,4 @@ public static void Postfix(ChatController __instance) else __instance.freeChatField.textArea.SetText(""); } } -} +} \ No newline at end of file diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs new file mode 100644 index 0000000000..665ef54f92 --- /dev/null +++ b/Patches/TextBoxPatch.cs @@ -0,0 +1,79 @@ +using System; + +namespace TOHE.Patches; + +// Originally code by Gurge44. Reference: https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/TextBoxPatch.cs + +[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] +class TextBoxTMPSetTextPatch +{ + public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string input, [HarmonyArgument(1)] string inputCompo = "") + { + bool flag = false; + char ch = ' '; + __instance.tempTxt.Clear(); + + foreach (var str in input) + { + char upperInvariant = str; + if (ch != ' ' || upperInvariant != ' ') + { + switch (upperInvariant) + { + case '\r' or '\n': + flag = true; + break; + case '\b': + __instance.tempTxt.Length = Math.Max(__instance.tempTxt.Length - 1, 0); + break; + } + + if (__instance.ForceUppercase) upperInvariant = char.ToUpperInvariant(upperInvariant); + if (upperInvariant is not '\b' and not '\n' and not '\r') + { + __instance.tempTxt.Append(upperInvariant); + ch = upperInvariant; + } + } + } + + if (!__instance.tempTxt.ToString().Equals(DestroyableSingleton.Instance.GetString(StringNames.EnterName), StringComparison.OrdinalIgnoreCase) && __instance.characterLimit > 0) + __instance.tempTxt.Length = Math.Min(__instance.tempTxt.Length, __instance.characterLimit); + input = __instance.tempTxt.ToString(); + + if (!input.Equals(__instance.text) || !inputCompo.Equals(__instance.compoText)) + { + __instance.text = input; + __instance.compoText = inputCompo; + string str = __instance.text; + string compoText = __instance.compoText; + + if (__instance.Hidden) + { + str = ""; + for (int index = 0; index < __instance.text.Length; ++index) + str += "*"; + } + + __instance.outputText.text = str + compoText; + __instance.outputText.ForceMeshUpdate(true, true); + if (__instance.keyboard != null) __instance.keyboard.text = __instance.text; + __instance.OnChange.Invoke(); + } + + if (flag) __instance.OnEnter.Invoke(); + __instance.Pipe.transform.localPosition = __instance.outputText.CursorPos(); + + return false; + } +} +/* Originally by KARPED1EM. Reference: https://github.com/KARPED1EM/TownOfNext/blob/TONX/TONX/Patches/TextBoxPatch.cs */ +[HarmonyPatch(typeof(TextBoxTMP))] +public class TextBoxPatch +{ + [HarmonyPatch(nameof(TextBoxTMP.SetText)), HarmonyPrefix] + public static void ModifyCharacterLimit(TextBoxTMP __instance) + { + __instance.characterLimit = 1200; + } +} \ No newline at end of file From 1528a9bf9660e8ff0a22d6a1d89a013154c9c3c9 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 10:04:22 +0200 Subject: [PATCH 026/778] first commit!!! --- Patches/MeetingHudPatch.cs | 14 +++++++------- Roles/Core/RoleBase.cs | 7 +++++++ Roles/Crewmate/Keeper.cs | 5 +++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 2681ede5c6..5918a05aba 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -607,6 +607,13 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } + if (!voter.GetRoleClass().CheckVote(voter, target)) + { + Logger.Info($"Canceling vote for {voter.GetRealName()} because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); + __instance.RpcClearVote(voter.GetClientId()); + return false; + } + switch (voter.GetCustomRole()) { case CustomRoles.Dictator: @@ -623,13 +630,6 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } //patch here so checkend is not triggered break; - case CustomRoles.Keeper: - if (!Keeper.OnVotes(voter, target)) - { - __instance.RpcClearVote(voter.GetClientId()); - return false; - } - break; } } diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 42dd9633bc..e643119cd8 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -319,6 +319,13 @@ public virtual void OnVote(PlayerControl votePlayer, PlayerControl voteTarget) ///
public virtual void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarget) { } + + /// + /// If role wants to return the vote to the player + /// + public virtual bool CheckVote(PlayerControl voter, PlayerControl target) => voter != null && target != null; + + /// /// When need hide vote /// diff --git a/Roles/Crewmate/Keeper.cs b/Roles/Crewmate/Keeper.cs index 71099ab3aa..6b7ea34fc6 100644 --- a/Roles/Crewmate/Keeper.cs +++ b/Roles/Crewmate/Keeper.cs @@ -116,7 +116,8 @@ public void ReceiveRPC(MessageReader reader) } } - public static bool OnVotes(PlayerControl voter, PlayerControl target) + + public override bool CheckVote(PlayerControl voter, PlayerControl target) { if (!CustomRoles.Keeper.HasEnabled()) return true; if (voter == null || target == null) return true; @@ -129,7 +130,7 @@ public static bool OnVotes(PlayerControl voter, PlayerControl target) keeperUses[voter.PlayerId]++; keeperTarget.Add(target.PlayerId); Logger.Info($"{voter.GetNameWithRole()} chosen as keeper target by {target.GetNameWithRole()}", "Keeper"); - SendRPC(type:0, keeperId: voter.PlayerId, targetId: target.PlayerId); // add keeperUses, KeeperTarget and DidVote + SendRPC(type: 0, keeperId: voter.PlayerId, targetId: target.PlayerId); // add keeperUses, KeeperTarget and DidVote Utils.SendMessage(string.Format(GetString("KeeperProtect"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Keeper), GetString("KeeperTitle"))); return false; } From 21fa876660eae55608ede5af7d6ccff96f274a94 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 10:13:06 +0200 Subject: [PATCH 027/778] update method names --- Roles/Core/RoleBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index e643119cd8..4026d5ba2f 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -310,18 +310,18 @@ public virtual void OnCoEndGame() { } /// - /// When player vote for target + /// A check for any abilities that should happen at the end of the meeting as the person who voted. /// public virtual void OnVote(PlayerControl votePlayer, PlayerControl voteTarget) { } /// - /// When the player was voted + /// A check for any abilities that should happen at the end of the meeting as the person who got voted. /// public virtual void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarget) { } /// - /// If role wants to return the vote to the player + /// If role wants to return the vote to the player during meeting. Can also work to check any abilities during meeting. /// public virtual bool CheckVote(PlayerControl voter, PlayerControl target) => voter != null && target != null; From 3d54e9cc3605fe9da9bc75e77d1d1221516b75a4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 10:57:31 +0200 Subject: [PATCH 028/778] almost done --- Resources/Lang/en_US.json | 2 ++ Roles/Crewmate/Cleanser.cs | 18 +++++++++--------- Roles/Crewmate/FortuneTeller.cs | 20 +++++++++++++------- Roles/Crewmate/Oracle.cs | 18 ++++++++++-------- Roles/Crewmate/Tracker.cs | 19 ++++++++++++------- Roles/Impostor/Eraser.cs | 19 ++++++++++--------- Roles/Impostor/Godfather.cs | 13 ++++++++++--- Roles/Neutral/SoulCollector.cs | 15 +++++++++------ 8 files changed, 75 insertions(+), 49 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3d42b2a446..7c9b5bb5c9 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1124,6 +1124,8 @@ "HideGameSettings": "Hide Game Settings", "DIYGameSettings": "Enable only custom /n messages", "Settings:": "Settings:", + "VoteAbilityUsed": "Used {0} Ability", + "VoteHasReturned": "Your vote has been returned! (Meaning you can cast a vote normally)", "PlayerCanSetColor": "Players can use the /color command", "PlayerCanSetName": "Players can use the /rn command", "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index 5384d986e4..42cd30b923 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -51,31 +51,31 @@ public override string GetProgressText(byte playerId, bool comms) else x = Color.gray; return (Utils.ColorString(x, $"({AbilityLimit})")); } - - public override void OnVote(PlayerControl voter, PlayerControl target) + public override bool CheckVote(PlayerControl voter, PlayerControl target) { - if (!voter.Is(CustomRoles.Cleanser)) return; - if (DidVote) return; + if (!voter.Is(CustomRoles.Cleanser)) return true; + if (DidVote) return true; DidVote = true; - if (AbilityLimit >= CleanserUsesOpt.GetInt()) return; + if (AbilityLimit >= CleanserUsesOpt.GetInt()) return true; if (target.PlayerId == voter.PlayerId) { Utils.SendMessage(GetString("CleanserRemoveSelf"), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); - return; + return true; } if (target.Is(CustomRoles.Stubborn)) { Utils.SendMessage(GetString("CleanserCantRemove"), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); - return; + return true; } - if (CleanserTarget[voter.PlayerId] != byte.MaxValue) return; + if (CleanserTarget[voter.PlayerId] != byte.MaxValue) return true; AbilityLimit--; CleanserTarget[voter.PlayerId] = target.PlayerId; Logger.Info($"{voter.GetNameWithRole()} cleansed {target.GetNameWithRole()}", "Cleansed"); CleansedPlayers.Add(target.PlayerId); - Utils.SendMessage(string.Format(GetString("CleanserRemovedRole"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser),GetString("CleanserTitle"))); + Utils.SendMessage(string.Format(GetString("CleanserRemovedRole"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); SendSkillRPC(); + return false; } public override void OnReportDeadBody(PlayerControl baba, PlayerControl lilelam) { diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 65e21a6eb6..ef26b48f3c 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -86,7 +86,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) } } - public override bool HideVote(PlayerVoteArea pva) => HidesVote.GetBool() && TempCheckLimit > 0; + public override bool HideVote(PlayerVoteArea pva) => HidesVote.GetBool() && TempCheckLimit > 0 && didVote.Contains(pva.TargetPlayerId); private static string GetTargetRoleList(CustomRoles[] roles) { return roles != null ? string.Join("\n", roles.Select(role => $" ★ {GetRoleName(role)}")) : ""; @@ -100,16 +100,16 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } return true; } - public override void OnVote(PlayerControl player, PlayerControl target) + public override bool CheckVote(PlayerControl player, PlayerControl target) { - if (player == null || target == null) return; - if (didVote.Contains(player.PlayerId)) return; + if (player == null || target == null) return true; + if (didVote.Contains(player.PlayerId)) return true; didVote.Add(player.PlayerId); if (AbilityLimit < 1) { SendMessage(GetString("FortuneTellerCheckReachLimit"), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); - return; + return true; } if (RandomActiveRoles.GetBool()) @@ -117,7 +117,7 @@ public override void OnVote(PlayerControl player, PlayerControl target) if (targetList.Contains(target.PlayerId)) { SendMessage(GetString("FortuneTellerAlreadyCheckedMsg") + "\n\n" + string.Format(GetString("FortuneTellerCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); - return; + return true; } } @@ -127,7 +127,7 @@ public override void OnVote(PlayerControl player, PlayerControl target) if (player.PlayerId == target.PlayerId) { SendMessage(GetString("FortuneTellerCheckSelfMsg") + "\n\n" + string.Format(GetString("FortuneTellerCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); - return; + return true; } string msg; @@ -178,6 +178,12 @@ public override void OnVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("FortuneTellerCheck") + "\n" + msg + "\n\n" + string.Format(GetString("FortuneTellerCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); + SendMessage(ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), CustomRoles.FortuneTeller.ToString())), player.PlayerId, title: GetString("VoteHasReturned")); + return false; + } + public override void OnVote(PlayerControl player, PlayerControl target) + { + } public override string GetProgressText(byte playerId, bool comms) { diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index ae11770ee8..bb291e199b 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -79,16 +79,16 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) TempCheckLimit[pid] = tempLimit; } } - public override void OnVote(PlayerControl player, PlayerControl target) + public override bool CheckVote(PlayerControl player, PlayerControl target) { - if (player == null || target == null) return; - if (DidVote.Contains(player.PlayerId)) return; + if (player == null || target == null) return true; + if (DidVote.Contains(player.PlayerId)) return true; DidVote.Add(player.PlayerId); if (AbilityLimit < 1) { SendMessage(GetString("OracleCheckReachLimit"), player.PlayerId, ColorString(GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - return; + return true; } AbilityLimit -= 1; @@ -97,7 +97,7 @@ public override void OnVote(PlayerControl player, PlayerControl target) if (player.PlayerId == target.PlayerId) { SendMessage(GetString("OracleCheckSelfMsg") + "\n\n" + string.Format(GetString("OracleCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - return; + return true; } { @@ -113,13 +113,13 @@ public override void OnVote(PlayerControl player, PlayerControl target) else if (target.GetCustomRole().IsNeutralTeamV2() || target.GetCustomSubRoles().Any(role => role.IsNeutralTeamV2())) text = "Neutral"; else if (target.GetCustomRole().IsCrewmateTeamV2() && (target.GetCustomSubRoles().Any(role => role.IsCrewmateTeamV2()) || (target.GetCustomSubRoles().Count == 0))) text = "Crewmate"; } - else - { + else + { if (target.GetCustomRole().IsImpostor() && !target.Is(CustomRoles.Trickster)) text = "Impostor"; else if (target.GetCustomRole().IsNeutral()) text = "Neutral"; else text = "Crewmate"; } - + if (FailChance.GetInt() > 0) { int random_number_1 = HashRandom.Next(1, 100); @@ -147,6 +147,8 @@ public override void OnVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("OracleCheck") + "\n" + msg + "\n\n" + string.Format(GetString("OracleCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); + SendMessage(ColorString(GetRoleColor(CustomRoles.Oracle), string.Format(GetString("VoteAbilityUsed"), CustomRoles.Oracle)), player.PlayerId, title: GetString("VoteHasReturned")); + return false; } } public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) diff --git a/Roles/Crewmate/Tracker.cs b/Roles/Crewmate/Tracker.cs index 63391b492b..9102188542 100644 --- a/Roles/Crewmate/Tracker.cs +++ b/Roles/Crewmate/Tracker.cs @@ -28,6 +28,7 @@ internal class Tracker : RoleBase private static readonly Dictionary> TrackerTarget = []; private static readonly Dictionary TempTrackLimit = []; + private bool Didvote = false; public override void SetupCustomOption() { @@ -92,22 +93,26 @@ public void ReceiveRPC(MessageReader reader) } } public override string GetMark(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) => !(seer == null || target == null) && TrackerTarget.ContainsKey(seer.PlayerId) && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) ? Utils.ColorString(seer.GetRoleColor(), "◀") : ""; - - public override void OnVote(PlayerControl player, PlayerControl target) + public override void AfterMeetingTasks() => Didvote = false; + public override bool CheckVote(PlayerControl player, PlayerControl target) { - if (player == null || target == null) return; - if (AbilityLimit < 1) return; - if (player.PlayerId == target.PlayerId) return; - if (TrackerTarget[player.PlayerId].Contains(target.PlayerId)) return; + if (player == null || target == null) return true; + if (AbilityLimit < 1 || Didvote) return true; + if (player.PlayerId == target.PlayerId) return true; + if (TrackerTarget[player.PlayerId].Contains(target.PlayerId)) return true; + Didvote = true; AbilityLimit--; TrackerTarget[player.PlayerId].Add(target.PlayerId); TargetArrow.Add(player.PlayerId, target.PlayerId); - SendRPC(0,player.PlayerId, target.PlayerId); + SendRPC(0, player.PlayerId, target.PlayerId); + SendMessage(ColorString(GetRoleColor(CustomRoles.Tracker), string.Format(GetString("VoteAbilityUsed"), CustomRoles.Tracker)), player.PlayerId, title: GetString("VoteHasReturned")); + return false; } + public override void OnReportDeadBody(PlayerControl reported, PlayerControl repoted) { foreach (var trackerId in _playerIdList) diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index 7197ea2371..121ae3e03b 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -42,15 +42,15 @@ public override string GetProgressText(byte playerId, bool comms) public override bool HideVote(PlayerVoteArea votedPlayer) => CheckForEndVotingPatch.CheckRole(votedPlayer.TargetPlayerId, CustomRoles.Eraser) && HideVoteOpt.GetBool() && TempEraseLimit <= 0; - - public override void OnVote(PlayerControl player, PlayerControl target) + public override bool CheckVote(PlayerControl player, PlayerControl target) { - if (!HasEnabled) return; - if (player == null || target == null) return; - if (target.Is(CustomRoles.Eraser)) return; - if (AbilityLimit <= 0) return; - if (didVote.Contains(player.PlayerId)) return; + if (!HasEnabled) return true; + if (player == null || target == null) return true; + if (target.Is(CustomRoles.Eraser)) return true; + if (AbilityLimit <= 0) return true; + + if (didVote.Contains(player.PlayerId)) return true; didVote.Add(player.PlayerId); Logger.Info($"{player.GetCustomRole()} votes for {target.GetCustomRole()}", "Vote Eraser"); @@ -58,14 +58,14 @@ public override void OnVote(PlayerControl player, PlayerControl target) if (target.PlayerId == player.PlayerId) { Utils.SendMessage(GetString("EraserEraseSelf"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); - return; + return true; } var targetRole = target.GetCustomRole(); if (targetRole.IsTasklessCrewmate() || targetRole.IsNeutral() || Main.TasklessCrewmate.Contains(target.PlayerId) || CopyCat.playerIdList.Contains(target.PlayerId) || target.Is(CustomRoles.Stubborn)) { Utils.SendMessage(string.Format(GetString("EraserEraseBaseImpostorOrNeutralRoleNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); - return; + return true; } AbilityLimit--; @@ -77,6 +77,7 @@ public override void OnVote(PlayerControl player, PlayerControl target) Utils.SendMessage(string.Format(GetString("EraserEraseNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); Utils.NotifyRoles(SpecifySeer: player); + return false; } public override void OnReportDeadBody(PlayerControl reporter, PlayerControl target) { diff --git a/Roles/Impostor/Godfather.cs b/Roles/Impostor/Godfather.cs index 08fb80210a..fa161ac3a6 100644 --- a/Roles/Impostor/Godfather.cs +++ b/Roles/Impostor/Godfather.cs @@ -1,4 +1,6 @@ using TOHE.Roles.Core; +using static TOHE.Utils; +using static TOHE.Translator; namespace TOHE.Roles.Impostor; @@ -16,6 +18,7 @@ internal class Godfather : RoleBase private static OptionItem GodfatherChangeOpt; private static readonly HashSet GodfatherTarget = []; + private bool Didvote = false; private enum GodfatherChangeMode { @@ -54,11 +57,15 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe else killer.RpcSetCustomRole(CustomRoles.Madmate); } } - - public override void OnVote(PlayerControl votePlayer, PlayerControl voteTarget) + public override void AfterMeetingTasks() => Didvote = false; + public override bool CheckVote(PlayerControl votePlayer, PlayerControl voteTarget) { - if (votePlayer == null || voteTarget == null) return; + if (votePlayer == null || voteTarget == null) return true; + if (Didvote == true) return false; + Didvote = true; GodfatherTarget.Add(voteTarget.PlayerId); + SendMessage(ColorString(GetRoleColor(CustomRoles.Godfather), string.Format(GetString("VoteAbilityUsed"), CustomRoles.Godfather)), votePlayer.PlayerId, title: GetString("VoteHasReturned")); + return false; } } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 11d4e96cb9..75091b2c3c 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -2,6 +2,7 @@ using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; +using static TOHE.Utils; namespace TOHE.Roles.Neutral; internal class SoulCollector : RoleBase @@ -75,29 +76,31 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) else SoulCollectorTarget.Add(SoulCollectorId, byte.MaxValue); } - - public override void OnVote(PlayerControl voter, PlayerControl target) + public override bool CheckVote(PlayerControl voter, PlayerControl target) { - if (DidVote.TryGetValue(voter.PlayerId, out var voted) && voted) return; - if (SoulCollectorTarget[voter.PlayerId] != byte.MaxValue) return; + if (DidVote.TryGetValue(voter.PlayerId, out var voted) && voted) return true; + if (SoulCollectorTarget[voter.PlayerId] != byte.MaxValue) return true; DidVote[voter.PlayerId] = true; - + if (!CollectOwnSoulOpt.GetBool() && voter.PlayerId == target.PlayerId) { Utils.SendMessage(GetString("SoulCollectorSelfVote"), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); Logger.Info($"{voter.GetNameWithRole()} self vote not allowed", "SoulCollector"); SoulCollectorTarget[voter.PlayerId] = byte.MaxValue; - return; + return true; } SoulCollectorTarget.Remove(voter.PlayerId); SoulCollectorTarget.TryAdd(voter.PlayerId, target.PlayerId); Logger.Info($"{voter.GetNameWithRole()} predicted the death of {target.GetNameWithRole()}", "SoulCollector"); Utils.SendMessage(string.Format(GetString("SoulCollectorTarget"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + SendMessage(ColorString(GetRoleColor(CustomRoles.SoulCollector), string.Format(GetString("VoteAbilityUsed"), CustomRoles.SoulCollector)), voter.PlayerId, title: GetString("VoteHasReturned")); SendRPC(voter.PlayerId); + return false; } + public override void OnReportDeadBody(PlayerControl ryuak, PlayerControl iscute) { foreach (var playerId in SoulCollectorTarget.Keys) From 833153cd66d38a5ea638e542911eb2e662cda35e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 10:59:13 +0200 Subject: [PATCH 029/778] update comments --- Roles/Core/RoleBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 4026d5ba2f..a89fcfb4a2 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -310,12 +310,12 @@ public virtual void OnCoEndGame() { } /// - /// A check for any abilities that should happen at the end of the meeting as the person who voted. + /// A check for any role abilites of the player which voted, when the vote hasn't been canceled by any other means. /// public virtual void OnVote(PlayerControl votePlayer, PlayerControl voteTarget) { } /// - /// A check for any abilities that should happen at the end of the meeting as the person who got voted. + /// A check for any role abilites of the player that was voted, when the vote hasn't been canceled by any other means. /// public virtual void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarget) { } From 47b0156855c31eec52fa6ce36c298273ceb3099e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 10:59:34 +0200 Subject: [PATCH 030/778] move it up --- Roles/Core/RoleBase.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index a89fcfb4a2..1c5c6c9fa8 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -309,6 +309,12 @@ public virtual void OnPlayerLeft(ClientData clientData) public virtual void OnCoEndGame() { } + + /// + /// If role wants to return the vote to the player during meeting. Can also work to check any abilities during meeting. + /// + public virtual bool CheckVote(PlayerControl voter, PlayerControl target) => voter != null && target != null; + /// /// A check for any role abilites of the player which voted, when the vote hasn't been canceled by any other means. /// @@ -320,10 +326,6 @@ public virtual void OnVote(PlayerControl votePlayer, PlayerControl voteTarget) public virtual void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarget) { } - /// - /// If role wants to return the vote to the player during meeting. Can also work to check any abilities during meeting. - /// - public virtual bool CheckVote(PlayerControl voter, PlayerControl target) => voter != null && target != null; /// From 472faaf1e8816958064da49d1f35bc403065e2b5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 12:09:10 +0200 Subject: [PATCH 031/778] /vote ? --- Modules/ExtendedPlayerControl.cs | 21 +++++++++++++++++++++ Patches/ChatCommandPatch.cs | 26 ++++++++++++++++++++++++++ Resources/Lang/en_US.json | 2 ++ 3 files changed, 49 insertions(+) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 245af65f35..30d166e3c4 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -64,6 +64,27 @@ public static ClientData GetClient(this PlayerControl player) return null; } } + public static void RPCCastVote(byte playerId, byte suspectIdx) + { + if(!GameStates.IsMeeting) + { + var player = Utils.GetPlayerById(playerId); + Logger.Info($"Cancelled RPCCastVote for {player?.GetRealName()} because there is no meeting", "ExtendedPlayerControls..RPCCastVote"); + return; + } + + if (AmongUsClient.Instance.AmHost) + { + MeetingHud.Instance.CastVote(playerId, suspectIdx); + } + else + { + MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.CastVote, SendOption.Reliable, -1); + msg.Write(playerId); + msg.Write(suspectIdx); + AmongUsClient.Instance.FinishRpcImmediately(msg); + } + } public static int GetClientId(this PlayerControl player) { if (player == null) return -1; diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 27d569d76b..5c8b4afcfb 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2278,6 +2278,32 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can player.RpcTeleport(new Vector2(-0.2f, 1.3f)); break; + case "/vote": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), player.PlayerId); + break; + } + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + if(!player.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + ExtendedPlayerControl.RPCCastVote(player.PlayerId, (byte)arg); + } + break; + case "/say": case "/s": if (player.FriendCode.GetDevUser().IsDev) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7c9b5bb5c9..ec889046b3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1440,6 +1440,8 @@ "MayorHideVote": "Hide additional vote(s)", "HideJesterVote": "Hide Jester's vote", "MeetingsNeededForWin": "Meetings needed to win", + "CannotVoteWhenDead": "Cannot cast a vote while dead", + "VoteDead": "Cannot vote a dead player", "ExecutionerCanTargetImpostor": "Can Target Impostors", "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", From 784671eb595629496e394af684c5119524fd6ffe Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 12:21:15 +0200 Subject: [PATCH 032/778] use cmdvote --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 30d166e3c4..1607f90ba3 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -75,7 +75,7 @@ public static void RPCCastVote(byte playerId, byte suspectIdx) if (AmongUsClient.Instance.AmHost) { - MeetingHud.Instance.CastVote(playerId, suspectIdx); + MeetingHud.Instance.CmdCastVote(playerId, suspectIdx); } else { From f44c596e5d41bb9c1159518c167f3a18e5bc9938 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 12:23:07 +0200 Subject: [PATCH 033/778] remove --- Roles/Crewmate/FortuneTeller.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index ef26b48f3c..74d09c7fde 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -178,12 +178,8 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("FortuneTellerCheck") + "\n" + msg + "\n\n" + string.Format(GetString("FortuneTellerCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); - SendMessage(ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), CustomRoles.FortuneTeller.ToString())), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), CustomRoles.FortuneTeller)), player.PlayerId, title: GetString("VoteHasReturned")); return false; - } - public override void OnVote(PlayerControl player, PlayerControl target) - { - } public override string GetProgressText(byte playerId, bool comms) { From 425b0b04421309d839f965a79c32bbaf62f44212 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 12:27:28 +0200 Subject: [PATCH 034/778] use getstrings --- Roles/Crewmate/FortuneTeller.cs | 2 +- Roles/Crewmate/Oracle.cs | 2 +- Roles/Crewmate/Tracker.cs | 2 +- Roles/Impostor/Godfather.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 74d09c7fde..588c7d891c 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -178,7 +178,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("FortuneTellerCheck") + "\n" + msg + "\n\n" + string.Format(GetString("FortuneTellerCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); - SendMessage(ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), CustomRoles.FortuneTeller)), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), GetString("FortuneTeller"))), player.PlayerId, title: GetString("VoteHasReturned")); return false; } public override string GetProgressText(byte playerId, bool comms) diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index bb291e199b..c009f967af 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -147,7 +147,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("OracleCheck") + "\n" + msg + "\n\n" + string.Format(GetString("OracleCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - SendMessage(ColorString(GetRoleColor(CustomRoles.Oracle), string.Format(GetString("VoteAbilityUsed"), CustomRoles.Oracle)), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(ColorString(GetRoleColor(CustomRoles.Oracle), string.Format(GetString("VoteAbilityUsed"), GetString("Oracle"))), player.PlayerId, title: GetString("VoteHasReturned")); return false; } } diff --git a/Roles/Crewmate/Tracker.cs b/Roles/Crewmate/Tracker.cs index 9102188542..057826fd62 100644 --- a/Roles/Crewmate/Tracker.cs +++ b/Roles/Crewmate/Tracker.cs @@ -108,7 +108,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) TargetArrow.Add(player.PlayerId, target.PlayerId); SendRPC(0, player.PlayerId, target.PlayerId); - SendMessage(ColorString(GetRoleColor(CustomRoles.Tracker), string.Format(GetString("VoteAbilityUsed"), CustomRoles.Tracker)), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(ColorString(GetRoleColor(CustomRoles.Tracker), string.Format(GetString("VoteAbilityUsed"), GetString("Tracker"))), player.PlayerId, title: GetString("VoteHasReturned")); return false; } diff --git a/Roles/Impostor/Godfather.cs b/Roles/Impostor/Godfather.cs index fa161ac3a6..8cff6c6202 100644 --- a/Roles/Impostor/Godfather.cs +++ b/Roles/Impostor/Godfather.cs @@ -65,7 +65,7 @@ public override bool CheckVote(PlayerControl votePlayer, PlayerControl voteTarge Didvote = true; GodfatherTarget.Add(voteTarget.PlayerId); - SendMessage(ColorString(GetRoleColor(CustomRoles.Godfather), string.Format(GetString("VoteAbilityUsed"), CustomRoles.Godfather)), votePlayer.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(ColorString(GetRoleColor(CustomRoles.Godfather), string.Format(GetString("VoteAbilityUsed"), GetString("Godfather"))), votePlayer.PlayerId, title: GetString("VoteHasReturned")); return false; } } From 4edba5da6dc6239ff2cd58bcd78018b452978f39 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 14:27:28 +0200 Subject: [PATCH 035/778] almsot done /vote --- Modules/ExtendedPlayerControl.cs | 11 +++++++---- Modules/OptionHolder.cs | 11 +++++++++++ Patches/ChatCommandPatch.cs | 19 +++++++++++++++---- Resources/Config/template/English.txt | 2 +- Resources/Lang/en_US.json | 4 +++- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 1607f90ba3..e81ec080d9 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -11,6 +11,7 @@ using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; using UnityEngine; +using static Il2CppMono.Security.X509.X520; using static TOHE.Translator; namespace TOHE; @@ -79,10 +80,12 @@ public static void RPCCastVote(byte playerId, byte suspectIdx) } else { - MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.CastVote, SendOption.Reliable, -1); - msg.Write(playerId); - msg.Write(suspectIdx); - AmongUsClient.Instance.FinishRpcImmediately(msg); + var writer = CustomRpcSender.Create("Cast Vote", SendOption.Reliable); + writer.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.CastVote) + .Write(playerId) + .Write(suspectIdx) + .EndRpc(); + writer.SendMessage(); } } public static int GetClientId(this PlayerControl player) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 17f6a8f683..dc4df757ac 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -183,6 +183,9 @@ private enum RatesZeroOne public static OptionItem AutoPlayAgain; public static OptionItem AutoPlayAgainCountdown; + public static OptionItem EnableVoteCommand; + public static OptionItem ShouldVoteCmdsSpamChat; + //public static OptionItem ShowLobbyCode; public static OptionItem LowLoadMode; public static OptionItem EndWhenPlayerBug; @@ -1809,6 +1812,14 @@ public static void Load() WhenTie = StringOptionItem.Create(60745, "WhenTie", tieModes, 0, TabGroup.GameSettings, false) .SetParent(VoteMode) .SetGameMode(CustomGameMode.Standard); + + + EnableVoteCommand = BooleanOptionItem.Create(60746, "EnableVote", true, TabGroup.GameSettings, false) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard); + ShouldVoteCmdsSpamChat = BooleanOptionItem.Create(60747, "ShouldVoteSpam", false, TabGroup.GameSettings, false) + .SetParent(EnableVoteCommand) + .SetGameMode(CustomGameMode.Standard); // 其它设定 TextOptionItem.Create(10000031, "MenuTitle.Other", TabGroup.GameSettings) .HideInFFA() diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 5c8b4afcfb..b731baaebc 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2283,16 +2283,27 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can if (subArgs == "" || !int.TryParse(subArgs, out int arg)) break; var plr = Utils.GetPlayerById(arg); - if (plr == null || !plr.IsAlive()) + + if (GameStates.IsLobby) { - Utils.SendMessage(GetString("VoteDead"), player.PlayerId); + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); break; } - if (GameStates.IsLobby) + + if (Options.ShouldVoteCmdsSpamChat.GetBool()) ChatManager.SendPreviousMessagesToAll(); + if (!Options.EnableVoteCommand.GetBool()) { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); break; } + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), player.PlayerId); + break; + } + } if(!player.IsAlive()) { Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); diff --git a/Resources/Config/template/English.txt b/Resources/Config/template/English.txt index ee51f78fb0..a94a76784e 100644 --- a/Resources/Config/template/English.txt +++ b/Resources/Config/template/English.txt @@ -1,2 +1,2 @@ welcome:Hi {{PlayerName}}, Welcome to Town of Host Enhanced\n\nThis lobby is hosted by {{HostName}} and this is not the ordinary Among Us experience.\n\nMod Version: v{{ModVersion}}\n\nCommands\n/r - Get role list\n/r [role] - Get role details\n\nLinks\nDiscord - https://discord.gg/tohe\nWebsite - https://tohre.dev/\nKo-Fi - https://ko-fi.com/TOHEN -OnMeeting:Command Usage\n/m - Get your role details\n\n/r - Get role list\n/r [role] - Get role details\n/death - Get your death info\n/kcount - Get number of alive killers (if setting is on)\n/bt [num] [role] - Guess a player's role\n/tl [num] - Trial as Judge or Councillor\n/rv [num] - Revenge a player as Nemesis\n/ret [num] - Kill a player as Retributionist\n/cmp [num1] [num2] - Compare the alignments of two players as Inspector\n/duel [0-2] - Participate in a Pirate duel\n/finish - End a meeting as President\n/reveal - Reveal yourself as President\n\nDied too early?\n/gno\n/rps\n/coinflip\n/rand\n +OnMeeting:Command Usage\n/m - Get your role details\n\n/r - Get role list\n/r [role] - Get role details\n/death - Get your death info\n/kcount - Get number of alive killers (if setting is on)\n/bt [num] [role] - Guess a player's role\n/vote [num] - Vote a player (or use /vote 253 to skip)\n/tl [num] - Trial as Judge or Councillor\n/rv [num] - Revenge a player as Nemesis\n/ret [num] - Kill a player as Retributionist\n/cmp [num1] [num2] - Compare the alignments of two players as Inspector\n/duel [0-2] - Participate in a Pirate duel\n/finish - End a meeting as President\n/reveal - Reveal yourself as President\n\nDied too early?\n/gno\n/rps\n/coinflip\n/rand\n diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ec889046b3..e79a38e24e 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1441,7 +1441,9 @@ "HideJesterVote": "Hide Jester's vote", "MeetingsNeededForWin": "Meetings needed to win", "CannotVoteWhenDead": "Cannot cast a vote while dead", - "VoteDead": "Cannot vote a dead player", + "EnableVote": "Enable /vote command", + "ShouldVoteSpam": "Try to hide /vote command", + "VoteDisabled": "/vote command has been disabled by the host.", "ExecutionerCanTargetImpostor": "Can Target Impostors", "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", From c71a75f5a956989be160a1c7ca2253b1ec8cdd63 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 14:39:13 +0200 Subject: [PATCH 036/778] localplayer --- Modules/ExtendedPlayerControl.cs | 2 +- Modules/Utils.cs | 1 + Patches/ChatCommandPatch.cs | 36 +++++++++++++++++++++++++++ Resources/Config/template/English.txt | 2 +- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e81ec080d9..95ca351655 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -65,7 +65,7 @@ public static ClientData GetClient(this PlayerControl player) return null; } } - public static void RPCCastVote(byte playerId, byte suspectIdx) + public static void RPCCastVote(this byte playerId, byte suspectIdx) { if(!GameStates.IsMeeting) { diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 52ef9e2bee..8cbc6a08b1 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -236,6 +236,7 @@ public static void SetDeathReason(this PlayerControl target, PlayerState.DeathRe { Main.PlayerStates[target.PlayerId].deathReason = reason; } + public static void RPCCastVote(this PlayerControl voter, PlayerControl voteTarget) => ExtendedPlayerControl.RPCCastVote(voter.PlayerId, voteTarget.PlayerId); public static void TargetDies(PlayerControl killer, PlayerControl target) { diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index b731baaebc..6223301d99 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -348,7 +348,43 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(GetString("Remaining.ImpostorCount") + impnum + "\n\r" + GetString("Remaining.NeutralCount") + neutralnum, PlayerControl.LocalPlayer.PlayerId); } break; + case "/vote": + canceled = true; + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) ChatManager.SendPreviousMessagesToAll(); + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + } + if (!PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + ExtendedPlayerControl.RPCCastVote(PlayerControl.LocalPlayer.PlayerId, (byte)arg); + } + break; case "/d": case "/death": diff --git a/Resources/Config/template/English.txt b/Resources/Config/template/English.txt index a94a76784e..82b8c4ad25 100644 --- a/Resources/Config/template/English.txt +++ b/Resources/Config/template/English.txt @@ -1,2 +1,2 @@ welcome:Hi {{PlayerName}}, Welcome to Town of Host Enhanced\n\nThis lobby is hosted by {{HostName}} and this is not the ordinary Among Us experience.\n\nMod Version: v{{ModVersion}}\n\nCommands\n/r - Get role list\n/r [role] - Get role details\n\nLinks\nDiscord - https://discord.gg/tohe\nWebsite - https://tohre.dev/\nKo-Fi - https://ko-fi.com/TOHEN -OnMeeting:Command Usage\n/m - Get your role details\n\n/r - Get role list\n/r [role] - Get role details\n/death - Get your death info\n/kcount - Get number of alive killers (if setting is on)\n/bt [num] [role] - Guess a player's role\n/vote [num] - Vote a player (or use /vote 253 to skip)\n/tl [num] - Trial as Judge or Councillor\n/rv [num] - Revenge a player as Nemesis\n/ret [num] - Kill a player as Retributionist\n/cmp [num1] [num2] - Compare the alignments of two players as Inspector\n/duel [0-2] - Participate in a Pirate duel\n/finish - End a meeting as President\n/reveal - Reveal yourself as President\n\nDied too early?\n/gno\n/rps\n/coinflip\n/rand\n +OnMeeting:Command Usage\n/m - Get your role details\n\n/r - Get role list\n/r [role] - Get role details\n/death - Get your death info\n/kcount - Get number of alive killers (if setting is on)\n/bt [num] [role] - Guess a player's role\n/vote [num] - Vote a player (or use /vote 253 to skip)\n/tl [num] - Trial as Judge or Councillor\n/rv [num] - Revenge a player as Nemesis\n/ret [num] - Kill a player as Retributionist\n/cmp [num1] [num2] - Compare the alignments of two players as Inspector\n/duel [0-2] - Participate in a Pirate duel\n/finish - End a meeting as President\n/reveal - Reveal yourself as President\n\nDied too early?\n/gno\n/rps\n/coinflip\n/rand\n From 3f151280a8bcc69a6cf73050a4bb375ce40d773f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 14:39:45 +0200 Subject: [PATCH 037/778] remove indian security --- Modules/ExtendedPlayerControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 95ca351655..f2cdb3f7d2 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -11,7 +11,6 @@ using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; using UnityEngine; -using static Il2CppMono.Security.X509.X520; using static TOHE.Translator; namespace TOHE; From 5bc5a7c67fdc827125d715d63bbc327017a30046 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 14:41:31 +0200 Subject: [PATCH 038/778] nothing --- Modules/OptionHolder.cs | 2 -- Patches/ChatCommandPatch.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index dc4df757ac..cd9326a1af 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1812,8 +1812,6 @@ public static void Load() WhenTie = StringOptionItem.Create(60745, "WhenTie", tieModes, 0, TabGroup.GameSettings, false) .SetParent(VoteMode) .SetGameMode(CustomGameMode.Standard); - - EnableVoteCommand = BooleanOptionItem.Create(60746, "EnableVote", true, TabGroup.GameSettings, false) .SetColor(new Color32(147, 241, 240, byte.MaxValue)) .SetGameMode(CustomGameMode.Standard); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 6223301d99..d17923a94b 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -361,7 +361,6 @@ public static bool Prefix(ChatController __instance) break; } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) ChatManager.SendPreviousMessagesToAll(); if (!Options.EnableVoteCommand.GetBool()) { Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); From 766f212731f15fe3b0f4e776c49096625c61d564 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 14:44:08 +0200 Subject: [PATCH 039/778] canceled --- Patches/ChatCommandPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index d17923a94b..0b06eddf99 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2314,6 +2314,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can break; case "/vote": + canceled = true; subArgs = args.Length != 2 ? "" : args[1]; if (subArgs == "" || !int.TryParse(subArgs, out int arg)) break; From f42eb6353959ffe0f1a42beab39f9718af632989 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 15:13:30 +0200 Subject: [PATCH 040/778] some changes --- Patches/MeetingHudPatch.cs | 1 + Resources/Lang/en_US.json | 2 +- Roles/Crewmate/Cleanser.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 5918a05aba..129fa6f92c 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -607,6 +607,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } + if (!voter.GetRoleClass().CheckVote(voter, target)) { Logger.Info($"Canceling vote for {voter.GetRealName()} because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e79a38e24e..25c67d75c3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2882,7 +2882,7 @@ "CleanserTitle": "CLEANSER", "CleanserRemoveSelf": "You can not cleanse yourself", "CleanserCantRemove": "Oops! the player can not be cleansed.", - "CleanserRemovedRole": "{0} has been cleansed. All their Addons will be removed after the meeting.", + "CleanserRemovedRole": "{0} has been cleansed. All their Addons will be removed after the meeting.\n\nYour vote has been returned and you can vote for someone.", "LostAddonByCleanser": "All your Addons were removed by the cleanser", "MaxProtections": "Max protections", diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index 42cd30b923..9ffdaedb5c 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -56,7 +56,7 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) if (!voter.Is(CustomRoles.Cleanser)) return true; if (DidVote) return true; DidVote = true; - if (AbilityLimit >= CleanserUsesOpt.GetInt()) return true; + if (AbilityLimit < 1) return true; if (target.PlayerId == voter.PlayerId) { Utils.SendMessage(GetString("CleanserRemoveSelf"), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); From 18ac86de3bdb5fdf2abcf6d472421dda966a64e8 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 15:16:37 +0200 Subject: [PATCH 041/778] change netid --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f2cdb3f7d2..4bb2869e16 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -80,7 +80,7 @@ public static void RPCCastVote(this byte playerId, byte suspectIdx) else { var writer = CustomRpcSender.Create("Cast Vote", SendOption.Reliable); - writer.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.CastVote) + writer.AutoStartRpc(MeetingHud.Instance.NetId, (byte)RpcCalls.CastVote) .Write(playerId) .Write(suspectIdx) .EndRpc(); From 12f0ae43b763826a039c4c548ac7fc279bb7e5f5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 16:12:07 +0200 Subject: [PATCH 042/778] fix string --- Patches/MeetingHudPatch.cs | 9 ++++++++- Roles/Crewmate/FortuneTeller.cs | 2 +- Roles/Crewmate/Oracle.cs | 2 +- Roles/Crewmate/Tracker.cs | 2 +- Roles/Impostor/Godfather.cs | 2 +- Roles/Neutral/SoulCollector.cs | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 129fa6f92c..52ad1ec72b 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -610,8 +610,15 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (!voter.GetRoleClass().CheckVote(voter, target)) { - Logger.Info($"Canceling vote for {voter.GetRealName()} because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); + Logger.Info($"Canceling {voter.GetRealName()}'s because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); __instance.RpcClearVote(voter.GetClientId()); + if (target != null) + { + // Attempts to set thumbsdown color to the same as playerrole to signify player ability used on (only for modded client) + PlayerVoteArea pva = MeetingHud.Instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); + Color color = Utils.GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); + pva.ThumbsDown.set_color_Injected(ref color); + } return false; } diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 588c7d891c..0431f895b7 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -178,7 +178,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("FortuneTellerCheck") + "\n" + msg + "\n\n" + string.Format(GetString("FortuneTellerCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.FortuneTeller), GetString("FortuneTellerCheckMsgTitle"))); - SendMessage(ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), GetString("FortuneTeller"))), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(GetString("VoteHasReturned"), player.PlayerId, title: ColorString(GetRoleColor(CustomRoles.FortuneTeller), string.Format(GetString("VoteAbilityUsed"), GetString("FortuneTeller")))); return false; } public override string GetProgressText(byte playerId, bool comms) diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index c009f967af..059f873f9d 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -147,7 +147,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } SendMessage(GetString("OracleCheck") + "\n" + msg + "\n\n" + string.Format(GetString("OracleCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - SendMessage(ColorString(GetRoleColor(CustomRoles.Oracle), string.Format(GetString("VoteAbilityUsed"), GetString("Oracle"))), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(GetString("VoteHasReturned"), player.PlayerId, title: ColorString(GetRoleColor(CustomRoles.Oracle), string.Format(GetString("VoteAbilityUsed"), GetString("Oracle")))); return false; } } diff --git a/Roles/Crewmate/Tracker.cs b/Roles/Crewmate/Tracker.cs index 057826fd62..760b13f657 100644 --- a/Roles/Crewmate/Tracker.cs +++ b/Roles/Crewmate/Tracker.cs @@ -108,7 +108,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) TargetArrow.Add(player.PlayerId, target.PlayerId); SendRPC(0, player.PlayerId, target.PlayerId); - SendMessage(ColorString(GetRoleColor(CustomRoles.Tracker), string.Format(GetString("VoteAbilityUsed"), GetString("Tracker"))), player.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(GetString("VoteHasReturned"), player.PlayerId, title: ColorString(GetRoleColor(CustomRoles.Tracker), string.Format(GetString("VoteAbilityUsed"), GetString("Tracker")))); return false; } diff --git a/Roles/Impostor/Godfather.cs b/Roles/Impostor/Godfather.cs index 8cff6c6202..c9a312369f 100644 --- a/Roles/Impostor/Godfather.cs +++ b/Roles/Impostor/Godfather.cs @@ -65,7 +65,7 @@ public override bool CheckVote(PlayerControl votePlayer, PlayerControl voteTarge Didvote = true; GodfatherTarget.Add(voteTarget.PlayerId); - SendMessage(ColorString(GetRoleColor(CustomRoles.Godfather), string.Format(GetString("VoteAbilityUsed"), GetString("Godfather"))), votePlayer.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(GetString("VoteHasReturned"), votePlayer.PlayerId, title: ColorString(GetRoleColor(CustomRoles.Godfather), string.Format(GetString("VoteAbilityUsed"), GetString("Godfather")))); return false; } } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 75091b2c3c..543fa0ead3 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -95,7 +95,7 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) SoulCollectorTarget.TryAdd(voter.PlayerId, target.PlayerId); Logger.Info($"{voter.GetNameWithRole()} predicted the death of {target.GetNameWithRole()}", "SoulCollector"); Utils.SendMessage(string.Format(GetString("SoulCollectorTarget"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - SendMessage(ColorString(GetRoleColor(CustomRoles.SoulCollector), string.Format(GetString("VoteAbilityUsed"), CustomRoles.SoulCollector)), voter.PlayerId, title: GetString("VoteHasReturned")); + SendMessage(GetString("VoteHasReturned"), voter.PlayerId, title: ColorString(GetRoleColor(CustomRoles.SoulCollector), string.Format(GetString("VoteAbilityUsed"), GetString("SoulCollector")))); SendRPC(voter.PlayerId); return false; } From b503b1e627d8d0df26fe9d316efc951524d5ad37 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 16:21:31 +0200 Subject: [PATCH 043/778] fix logs --- Patches/MeetingHudPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 52ad1ec72b..98575b4c6a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -610,7 +610,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (!voter.GetRoleClass().CheckVote(voter, target)) { - Logger.Info($"Canceling {voter.GetRealName()}'s because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); + Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); __instance.RpcClearVote(voter.GetClientId()); if (target != null) { From a916add0e49f1fcd47e23a9e1d70756ec00353b8 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 18:00:12 +0200 Subject: [PATCH 044/778] move below enablevote --- Patches/ChatCommandPatch.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 0b06eddf99..3f16b48070 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2326,12 +2326,14 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can break; } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) ChatManager.SendPreviousMessagesToAll(); + if (!Options.EnableVoteCommand.GetBool()) { Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); break; } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) ChatManager.SendPreviousMessagesToAll(); + if (arg != 253) // skip { if (plr == null || !plr.IsAlive()) From fd1f588eee9bc1b12f0d936960e9ce18649fd580 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 21 May 2024 18:41:28 +0200 Subject: [PATCH 045/778] remove hide vote --- Patches/MeetingHudPatch.cs | 2 -- Roles/Core/RoleBase.cs | 6 ------ Roles/Crewmate/Cleanser.cs | 4 ---- Roles/Crewmate/FortuneTeller.cs | 2 -- Roles/Crewmate/Keeper.cs | 4 +--- Roles/Crewmate/Oracle.cs | 3 --- Roles/Crewmate/Tracker.cs | 4 +--- Roles/Impostor/Eraser.cs | 3 --- Roles/Neutral/Jester.cs | 3 --- 9 files changed, 2 insertions(+), 29 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 98575b4c6a..9117cf07a6 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -190,8 +190,6 @@ public static bool Prefix(MeetingHud __instance) var player = Utils.GetPlayerById(ps.TargetPlayerId); var playerRoleClass = player.GetRoleClass(); - // Hide roles vote - if (playerRoleClass.HideVote(ps)) continue; // Assing Madmate Slef Vote if (ps.TargetPlayerId == ps.VotedFor && Madmate.MadmateSpawnMode.GetInt() == 2) continue; diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 1c5c6c9fa8..7fa4e5a1a9 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -327,12 +327,6 @@ public virtual void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarget { } - - /// - /// When need hide vote - /// - public virtual bool HideVote(PlayerVoteArea votedPlayer) => false; - /// /// When need add visual votes /// diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index 9ffdaedb5c..fe93708001 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -30,9 +30,6 @@ public override void SetupCustomOption() CleanserUsesOpt = IntegerOptionItem.Create(Id + 10, "MaxCleanserUses", new(1, 14, 1), 3, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]) .SetValueFormat(OptionFormat.Times); CleansedCanGetAddon = BooleanOptionItem.Create(Id + 11, "CleansedCanGetAddon", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]); - HidesVote = BooleanOptionItem.Create(Id + 12, "CleanserHideVote", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]); - // AbilityUseGainWithEachTaskCompleted = IntegerOptionItem.Create(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0, 5, 1), 1, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]) - // .SetValueFormat(OptionFormat.Times); } public override void Add(byte playerId) @@ -42,7 +39,6 @@ public override void Add(byte playerId) DidVote = false; } public static bool CantGetAddon() => !CleansedCanGetAddon.GetBool(); - public override bool HideVote(PlayerVoteArea ps) => HidesVote.GetBool() && AbilityLimit > 0; public override string GetProgressText(byte playerId, bool comms) { Color x; diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 0431f895b7..3e61cf4ab0 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -39,7 +39,6 @@ public override void SetupCustomOption() RandomActiveRoles = BooleanOptionItem.Create(Id + 11, "RandomActiveRoles", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]); AccurateCheckMode = BooleanOptionItem.Create(Id + 12, "AccurateCheckMode", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]); ShowSpecificRole = BooleanOptionItem.Create(Id + 13, "ShowSpecificRole", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]); - HidesVote = BooleanOptionItem.Create(Id + 14, "FortuneTellerHideVote", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]); AbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 15, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]) .SetValueFormat(OptionFormat.Times); OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.FortuneTeller); @@ -86,7 +85,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) } } - public override bool HideVote(PlayerVoteArea pva) => HidesVote.GetBool() && TempCheckLimit > 0 && didVote.Contains(pva.TargetPlayerId); private static string GetTargetRoleList(CustomRoles[] roles) { return roles != null ? string.Join("\n", roles.Select(role => $" ★ {GetRoleName(role)}")) : ""; diff --git a/Roles/Crewmate/Keeper.cs b/Roles/Crewmate/Keeper.cs index 6b7ea34fc6..1e8f4a93ec 100644 --- a/Roles/Crewmate/Keeper.cs +++ b/Roles/Crewmate/Keeper.cs @@ -31,8 +31,7 @@ public override void SetupCustomOption() SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Keeper); KeeperUsesOpt = IntegerOptionItem.Create(Id + 10, "MaxProtections", new(1, 14, 1), 3, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Keeper]) .SetValueFormat(OptionFormat.Times); - HidesVote = BooleanOptionItem.Create(Id + 11, "KeeperHideVote", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Keeper]); - + } public override void Init() { @@ -54,7 +53,6 @@ public override void Remove(byte playerId) DidVote.Remove(playerId); keeperUses.Remove(playerId); } - public override bool HideVote(PlayerVoteArea pva) => HidesVote.GetBool() && keeperUses[pva.TargetPlayerId] > 0; public override string GetProgressText(byte playerId, bool comms) { diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index 059f873f9d..6ce46275a2 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -33,8 +33,6 @@ public override void SetupCustomOption() CheckLimitOpt = IntegerOptionItem.Create(Id + 10, "OracleSkillLimit", new(0, 10, 1), 1, TabGroup.CrewmateRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) .SetValueFormat(OptionFormat.Times); - HidesVote = BooleanOptionItem.Create(Id + 12, "OracleHideVote", false, TabGroup.CrewmateRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]); FailChance = IntegerOptionItem.Create(Id + 13, "FailChance", new(0, 100, 5), 0, TabGroup.CrewmateRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) .SetValueFormat(OptionFormat.Percent); @@ -53,7 +51,6 @@ public override void Add(byte playerId) { AbilityLimit = CheckLimitOpt.GetFloat(); } - public override bool HideVote(PlayerVoteArea pva) => HidesVote.GetBool() && TempCheckLimit[pva.TargetPlayerId] > 0; public void SendRPC(byte playerId, bool isTemp = false) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); diff --git a/Roles/Crewmate/Tracker.cs b/Roles/Crewmate/Tracker.cs index 760b13f657..acb8be71af 100644 --- a/Roles/Crewmate/Tracker.cs +++ b/Roles/Crewmate/Tracker.cs @@ -39,8 +39,7 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Tracker]); OptionCanSeeLastRoomInMeeting = BooleanOptionItem.Create(Id + 7, "EvilTrackerCanSeeLastRoomInMeeting", true, TabGroup.CrewmateRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Tracker]); - HidesVote = BooleanOptionItem.Create(Id + 8, "TrackerHideVote", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tracker]); - TrackerAbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 9, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles, false) + TrackerAbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 9, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Tracker]) .SetValueFormat(OptionFormat.Times); } @@ -59,7 +58,6 @@ public override void Remove(byte playerId) { TrackerTarget.Remove(playerId); } - public override bool HideVote(PlayerVoteArea pva) => HidesVote.GetBool() && TempTrackLimit[pva.TargetPlayerId] > 0; public void SendRPC(int operate, byte trackerId = byte.MaxValue, byte targetId = byte.MaxValue) { if (!AmongUsClient.Instance.AmHost) return; diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index 121ae3e03b..c0598ed201 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -29,7 +29,6 @@ public override void SetupCustomOption() Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Eraser); EraseLimitOpt = IntegerOptionItem.Create(Id + 10, "EraseLimit", new(1, 15, 1), 2, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]) .SetValueFormat(OptionFormat.Times); - HideVoteOpt = BooleanOptionItem.Create(Id + 11, "EraserHideVote", false, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); } public override void Init() { @@ -40,8 +39,6 @@ public override void Init() public override string GetProgressText(byte playerId, bool comms) => Utils.ColorString(AbilityLimit <= 0 ? Utils.GetRoleColor(CustomRoles.Eraser) : Color.gray, $"({AbilityLimit})"); - public override bool HideVote(PlayerVoteArea votedPlayer) - => CheckForEndVotingPatch.CheckRole(votedPlayer.TargetPlayerId, CustomRoles.Eraser) && HideVoteOpt.GetBool() && TempEraseLimit <= 0; public override bool CheckVote(PlayerControl player, PlayerControl target) { diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index f280dbbb52..82e064e470 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -30,8 +30,6 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); JesterHasImpostorVision = BooleanOptionItem.Create(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - HideJesterVote = BooleanOptionItem.Create(Id + 5, "HideJesterVote", true, TabGroup.NeutralRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); MeetingsNeededForJesterWin = IntegerOptionItem.Create(Id + 6, "MeetingsNeededForWin", new(0, 10, 1), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Times); @@ -54,7 +52,6 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) opt.SetVision(JesterHasImpostorVision.GetBool()); } - public override bool HideVote(PlayerVoteArea votedPlayer) => HideJesterVote.GetBool(); public override bool OnCheckStartMeeting(PlayerControl reporter) => JesterCanUseButton.GetBool(); public override void CheckExileTarget(GameData.PlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) From 9dd454de3f749c34ddcfa08cc5631239cb0ecda0 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 22 May 2024 18:45:10 +0200 Subject: [PATCH 046/778] return hidevote --- Patches/MeetingHudPatch.cs | 4 +++- Roles/Core/RoleBase.cs | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9117cf07a6..35c500d869 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -190,7 +190,9 @@ public static bool Prefix(MeetingHud __instance) var player = Utils.GetPlayerById(ps.TargetPlayerId); var playerRoleClass = player.GetRoleClass(); - + //Hides vote + if (playerRoleClass.HideVote(ps)) continue; + // Assing Madmate Slef Vote if (ps.TargetPlayerId == ps.VotedFor && Madmate.MadmateSpawnMode.GetInt() == 2) continue; diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 7fa4e5a1a9..66d9737097 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -325,6 +325,10 @@ public virtual void OnVote(PlayerControl votePlayer, PlayerControl voteTarget) /// public virtual void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarget) { } + /// + /// Hides the playervote + /// + public virtual bool HideVote(PlayerVoteArea PVA) => false; /// From 355bfa220886ed14c0d3e0c30d088681ff23a72d Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 23 May 2024 18:52:27 -0400 Subject: [PATCH 047/778] deathreasons --- Modules/Utils.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 52ef9e2bee..9a02af67a3 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1949,7 +1949,8 @@ static bool BannedReason(PlayerState.DeathReason rso) or PlayerState.DeathReason.Overtired or PlayerState.DeathReason.etc or PlayerState.DeathReason.Vote - or PlayerState.DeathReason.Gambled; + or PlayerState.DeathReason.Gambled + or PlayerState.DeathReason.Armageddon; } return checkbanned ? !BannedReason(reason) : reason switch @@ -2000,6 +2001,7 @@ or PlayerState.DeathReason.Vote var Breason when BannedReason(Breason) => false, PlayerState.DeathReason.Slice => CustomRoles.Hawk.IsEnable(), PlayerState.DeathReason.BloodLet => CustomRoles.Bloodmoon.IsEnable(), + PlayerState.DeathReason.Starved => CustomRoles.Baker.IsEnable(), PlayerState.DeathReason.Kill => true, _ => true, }; From a6e5ea02781cac2daef70276381eba086124daff Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 23 May 2024 19:02:32 -0400 Subject: [PATCH 048/778] baker id change --- Roles/Neutral/Baker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 0f384c5aae..5b108cc90c 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -14,7 +14,7 @@ namespace TOHE.Roles.Neutral; internal class Baker : RoleBase { //===========================SETUP================================\\ - private const int Id = 28500; + private const int Id = 28600; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); From a39d56040f1262690369eb31fc725aa6c3c4952c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 26 May 2024 04:20:00 +0800 Subject: [PATCH 049/778] Remove TextBoxTMP.SetText --- Patches/TextBoxPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index 665ef54f92..ffc1979db8 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -68,7 +68,7 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp } } /* Originally by KARPED1EM. Reference: https://github.com/KARPED1EM/TownOfNext/blob/TONX/TONX/Patches/TextBoxPatch.cs */ -[HarmonyPatch(typeof(TextBoxTMP))] +/*[HarmonyPatch(typeof(TextBoxTMP))] public class TextBoxPatch { [HarmonyPatch(nameof(TextBoxTMP.SetText)), HarmonyPrefix] @@ -76,4 +76,4 @@ public static void ModifyCharacterLimit(TextBoxTMP __instance) { __instance.characterLimit = 1200; } -} \ No newline at end of file +}*/ \ No newline at end of file From 59313eeb633dda3d7cf5c714402906e16b1c9d08 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 26 May 2024 04:23:39 +0800 Subject: [PATCH 050/778] Update Readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0956f49408..2f590f4954 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ > - Improved Role Summary > - Dark Theme > - Improved Role Spawns +> - Improve text box (TextBoxPatch.cs) > - Some ideas to improve performance ### :star: [TONEX (Formerly TOHEX)](https://github.com/XtremeWave/TownOfNewEpic_Xtreme) > From 2e3eee4f0e574335ea08db764af3ffe5781c9004 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 26 May 2024 16:59:17 +0200 Subject: [PATCH 051/778] enable everyone to do /id if vote command is enabled --- Patches/ChatCommandPatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 3f16b48070..5d8a0efad8 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1891,7 +1891,8 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can } break; case "/id": - if (Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) break; + if ((Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + && !Options.EnableVoteCommand.GetBool()) break; string msgText = GetString("PlayerIdList"); foreach (var pc in Main.AllPlayerControls) From 47440dd142b1d573ebae56ff7f2ba82e0d13c9c0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 31 May 2024 04:03:29 +0800 Subject: [PATCH 052/778] Do Assign Desync Shapeshifter --- Modules/CustomRolesHelper.cs | 4 ++-- Patches/ChatBubblePatch.cs | 24 ++++++++++++++++++++++-- Patches/HudPatch.cs | 7 +++++++ Patches/MeetingHudPatch.cs | 12 ++++++++++++ Patches/onGameStartedPatch.cs | 33 +++++++++++++++++++++++++++++++-- Roles/Neutral/Glitch.cs | 5 ++--- 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 3f13860ec6..3f210ecf12 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -35,8 +35,8 @@ public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor } public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill button (Non-Impostor) - => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor) && !role.IsImpostor() || role is CustomRoles.Killer // FFA - ? RoleTypes.Impostor + => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter) && !role.IsImpostor() || role is CustomRoles.Killer // FFA + ? role.GetStaticRoleClass().ThisRoleBase.GetRoleTypes() : RoleTypes.GuardianAngel; diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 9dbf168390..613bc12bbd 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -1,4 +1,8 @@ +using AmongUs.GameOptions; +using TOHE.Roles.AddOns.Common; +using TOHE.Roles.Core; using UnityEngine; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Patches; @@ -15,8 +19,24 @@ class ChatBubbleSetNamePatch { public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDead, [HarmonyArgument(2)] bool voted) { - if (GameStates.IsInGame && !voted && __instance.playerInfo.PlayerId == PlayerControl.LocalPlayer.PlayerId) - __instance.NameText.color = PlayerControl.LocalPlayer.GetRoleColor(); + var seer = PlayerControl.LocalPlayer; + var target = Utils.GetPlayerById(__instance.playerInfo.PlayerId); + + if (GameStates.IsInGame && !voted && seer.PlayerId == target.PlayerId) + __instance.NameText.color = seer.GetRoleColor(); + + var seerRoleClass = seer.GetRoleClass(); + + // if based role is Shapeshifter + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) + { + // Is Desync Shapeshifter + if (Main.ResetCamPlayerList.Contains(seer.PlayerId)) + { + // When target is impostor, set name color as white + __instance.NameText.color = Color.white; + } + } if (Main.DarkTheme.Value) { diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 20a02709a1..c63711dfa2 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -3,6 +3,7 @@ using TMPro; using TOHE.Roles.Core; using UnityEngine; +using AmongUs.GameOptions; using static TOHE.Translator; namespace TOHE; @@ -252,6 +253,12 @@ public static void Postfix(HudManager __instance, [HarmonyArgument(0)] PlayerCon __instance.KillButton.ToggleVisible(player.CanUseKillButton()); __instance.ImpostorVentButton.ToggleVisible(player.CanUseImpostorVentButton()); __instance.SabotageButton.ToggleVisible(player.CanUseSabotage()); + + if (player.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) + { + __instance.AbilityButton?.ToggleVisible(true); + __instance.AbilityButton?.Show(); + } } } [HarmonyPatch(typeof(VentButton), nameof(VentButton.DoClick))] diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index f25b124fcf..20bbf05d66 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1,3 +1,4 @@ +using AmongUs.GameOptions; using System; using System.Text; using TOHE.Roles.AddOns.Common; @@ -984,6 +985,17 @@ public static void Postfix(MeetingHud __instance) PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); if (target == null) continue; + // if based role is Shapeshifter + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) + { + // Is Desync Shapeshifter + if (Main.ResetCamPlayerList.Contains(seer.PlayerId)) + { + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + pva.NameText.color = Color.white; + } + } var sb = new StringBuilder(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 17c9bb8b1a..a2393a0bed 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using Hazel; using System; +using UnityEngine; using TOHE.Modules; using TOHE.Modules.ChatManager; using TOHE.Roles.AddOns.Common; @@ -418,9 +419,28 @@ public static void SetRolesAfterSelect() foreach (var pc in Main.AllPlayerControls) { - if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); + var roleClass = pc.GetRoleClass(); - pc.GetRoleClass()?.OnAdd(pc.PlayerId); + roleClass?.OnAdd(pc.PlayerId); + + // if based role is Shapeshifter + if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) + { + // Is Desync Shapeshifter + if (Main.ResetCamPlayerList.Contains(pc.PlayerId)) + { + foreach (var target in Main.AllPlayerControls) + { + // Set all players as killable players + target.Data.Role.CanBeKilled = true; + + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + target.Data.Role.NameColor = Color.white; + } + } + Main.CheckShapeshift.Add(pc.PlayerId, false); + } foreach (var subRole in pc.GetCustomSubRoles().ToArray()) { @@ -566,8 +586,17 @@ private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dic rolesMap[(seer.PlayerId, player.PlayerId)] = othersRole; RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); + //Set role for host player.SetRole(othersRole); + + // Override RoleType for host + if (isHost && BaseRole == RoleTypes.Shapeshifter) + { + DestroyableSingleton.Instance.SetRole(player, BaseRole); + DestroyableSingleton.Instance.CanBeKilled = true; + } + player.Data.IsDead = true; Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index 5492c37a84..c5f2adc987 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -13,7 +13,7 @@ internal class Glitch : RoleBase //===========================SETUP================================\\ private const int Id = 16300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Glitch); - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -230,8 +230,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) public override string GetLowerText(PlayerControl player, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - if (player == null) return string.Empty; - if (!player.IsAlive()) return string.Empty; + if (!player.IsAlive() || isForMeeting) return string.Empty; var sb = new StringBuilder(string.Empty); From ae9895565601ac794abb7efad72c3f7fa4373e5e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 31 May 2024 04:08:58 +0800 Subject: [PATCH 053/778] Remove --- Patches/ChatBubblePatch.cs | 2 -- Patches/HudPatch.cs | 6 ------ 2 files changed, 8 deletions(-) diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 613bc12bbd..07ee06499b 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -1,8 +1,6 @@ using AmongUs.GameOptions; -using TOHE.Roles.AddOns.Common; using TOHE.Roles.Core; using UnityEngine; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Patches; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index c63711dfa2..83985a0312 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -253,12 +253,6 @@ public static void Postfix(HudManager __instance, [HarmonyArgument(0)] PlayerCon __instance.KillButton.ToggleVisible(player.CanUseKillButton()); __instance.ImpostorVentButton.ToggleVisible(player.CanUseImpostorVentButton()); __instance.SabotageButton.ToggleVisible(player.CanUseSabotage()); - - if (player.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) - { - __instance.AbilityButton?.ToggleVisible(true); - __instance.AbilityButton?.Show(); - } } } [HarmonyPatch(typeof(VentButton), nameof(VentButton.DoClick))] From 4eaef9a0078a4721c870b1d17e73dc9094f11231 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 31 May 2024 04:13:06 +0800 Subject: [PATCH 054/778] Some changes --- Patches/ChatBubblePatch.cs | 12 ++++-------- Patches/MeetingHudPatch.cs | 14 +++++--------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 07ee06499b..8e0015cbad 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -25,15 +25,11 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe var seerRoleClass = seer.GetRoleClass(); - // if based role is Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) + // if based role is Shapeshifter and is Desync Shapeshifter + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && Main.ResetCamPlayerList.Contains(seer.PlayerId)) { - // Is Desync Shapeshifter - if (Main.ResetCamPlayerList.Contains(seer.PlayerId)) - { - // When target is impostor, set name color as white - __instance.NameText.color = Color.white; - } + // When target is impostor, set name color as white + __instance.NameText.color = Color.white; } if (Main.DarkTheme.Value) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 20bbf05d66..4c834e2ecd 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -985,16 +985,12 @@ public static void Postfix(MeetingHud __instance) PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); if (target == null) continue; - // if based role is Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) + // if based role is Shapeshifter and is Desync Shapeshifter + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && Main.ResetCamPlayerList.Contains(seer.PlayerId)) { - // Is Desync Shapeshifter - if (Main.ResetCamPlayerList.Contains(seer.PlayerId)) - { - // When target is impostor, set name color as white - target.cosmetics.SetNameColor(Color.white); - pva.NameText.color = Color.white; - } + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + pva.NameText.color = Color.white; } var sb = new StringBuilder(); From b23cc1aa7b8a720e1de2730a18326b1a3c848061 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 31 May 2024 04:21:27 +0800 Subject: [PATCH 055/778] Add comment --- Patches/IntroPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 5c156333d6..f301463a0a 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -548,6 +548,7 @@ public static void Postfix() } } + // Not entirely sure if this is really necessary _ = new LateTask(() => Main.AllPlayerControls.Do(pc => pc.RpcSetRoleDesync(RoleTypes.Shapeshifter, -3)), 2f, "Set Impostor For Server"); } From a6f2025d08149ed14bab9a29e0c95e1e91fe1395 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 12:51:32 +0200 Subject: [PATCH 056/778] first commit --- Modules/ExtendedPlayerControl.cs | 12 +++++++++ .../PlayerGameOptionsSender.cs | 7 +++++ Modules/GameState.cs | 1 + Modules/Utils.cs | 10 +++++++ Patches/IntroPatch.cs | 16 ++++++++++++ Patches/PlayerControlPatch.cs | 26 +++++++++++++++++++ Patches/onGameStartedPatch.cs | 22 +++++++++++++++- Roles/Core/CustomRoleManager.cs | 8 ++++++ Roles/Core/RoleBase.cs | 12 +++++++++ Roles/Impostor/Pitfall.cs | 11 ++++---- main.cs | 5 ++++ 11 files changed, 124 insertions(+), 6 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 0a0b954919..f873c5bbbd 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; +using MS.Internal.Xml.XPath; using System; using System.Text; using TOHE.Modules; @@ -254,6 +255,17 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, } player.ResetKillCooldown(); } + public static void ResetPlayerOutfit(this PlayerControl player, GameData.PlayerOutfit Outfit = null) + { + Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; + + player.RpcSetName(Outfit.PlayerName); + player.RpcSetColor((byte)Outfit.ColorId); + player.RpcSetHat(Outfit.HatId); + player.RpcSetSkin(Outfit.SkinId); + player.RpcSetVisor(Outfit.VisorId); + player.RpcSetPet(Outfit.PetId); + } public static void SetKillCooldownV3(this PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) { if (player == null) return; diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index e2702beefc..87bd2c7697 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -125,6 +125,13 @@ public override IGameOptions BuildGameOptions() state.taskState.hasTasks = Utils.HasTasks(player.Data, false); + if (Main.UnShapeShifter.ContainsKey(player.PlayerId)) + { + if (Main.ForcedUnShapeshift[player.PlayerId]) AURoleOptions.ShapeshifterCooldown = 1f; + + Main.UnShapeShifter[player.PlayerId] = AURoleOptions.ShapeshifterCooldown; + } + if (Options.GhostCanSeeOtherVotes.GetBool() && player.Data.IsDead) { opt.SetBool(BoolOptionNames.AnonymousVotes, false); diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 29d94d14b0..a343d871a6 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -28,6 +28,7 @@ public class PlayerState(byte playerId) public PlainShipRoom LastRoom = null; public bool HasSpawned { get; set; } = false; public Dictionary TargetColorData = []; + public GameData.PlayerOutfit NormalOutfit; public CustomRoles GetCustomRoleFromRoleType() { diff --git a/Modules/Utils.cs b/Modules/Utils.cs index e43b325b9c..23f8462457 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1572,6 +1572,16 @@ public static IEnumerable GetRoleBasesByType () where t : RoleBase return null; } + public static bool IsMethodOverridden(RoleBase roleInstance, string methodName) + { + Type baseType = typeof(RoleBase); + Type derivedType = roleInstance.GetType(); + + MethodInfo baseMethod = baseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + MethodInfo derivedMethod = derivedType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + + return baseMethod.DeclaringType != derivedMethod.DeclaringType; + } public static GameData.PlayerInfo GetPlayerInfoById(int PlayerId) => GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == PlayerId); private static readonly StringBuilder SelfSuffix = new(); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 5c156333d6..1473e944bc 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -566,6 +566,22 @@ public static void Postfix() }); } + if (Main.UnShapeShifter.Any()) + { + _ = new LateTask(() => + { + Main.UnShapeShifter.Do(x => + { + var PC = Utils.GetPlayerById(x.Key); + var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); + PC.RpcShapeshift(randomplayer, false); + PC.RpcRejectShapeshift(); // It's a funny thing which just makes the button "shift" again but player is still in "ShapeShifted" state. + PC.ResetPlayerOutfit(); + + }); + }, 3f, "Set UnShapeShift Button"); + } + if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index d34ce29a9f..396806e81f 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -538,6 +538,20 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC return false; } + if(Main.ForcedUnShapeshift.TryGetValue(__instance.PlayerId, out var isforced) && isforced && __instance == target) + { + __instance.SyncSettings(); + Main.ForcedUnShapeshift[__instance.PlayerId] = false; + Main.Synclist.Add(__instance.PlayerId); + return false; + } + + if (Main.Synclist.Contains(__instance.PlayerId)) + { + Main.Synclist.Remove(__instance.PlayerId); + __instance.SyncSettings(); + } + // No called code if is invalid shapeshifting if (!CheckInvalidShapeshifting(__instance, target, shouldAnimate)) { @@ -611,6 +625,12 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); return false; } + if (instance == target && Main.UnShapeShifter.ContainsKey(instance.PlayerId)) + { + instance.GetRoleClass().UnShapeShiftButton(instance); + logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); + return false; + } return true; } public static void RejectShapeshiftAndReset(this PlayerControl player, bool reset = true) @@ -948,6 +968,12 @@ public static Task DoPostfix(PlayerControl __instance) { Sniper.OnFixedUpdateGlobal(player); + if (Main.UnShapeShifterCount.TryGetValue(player.PlayerId, out var count) && count > 0) + { + Main.UnShapeShifterCount[player.PlayerId] -= Time.deltaTime; + } + + if (!lowLoad) { NameNotifyManager.OnFixedUpdate(player); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 17c9bb8b1a..64c05efd38 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -61,6 +61,8 @@ public static void Postfix(AmongUsClient __instance) Main.ShapeshiftTarget.Clear(); Main.AllKillers.Clear(); Main.OverDeadPlayerList.Clear(); + Main.UnShapeShifter.Clear(); + Main.UnShapeShifterCount.Clear(); Main.LastNotifyNames.Clear(); Main.PlayerColors.Clear(); @@ -124,7 +126,11 @@ public static void Postfix(AmongUsClient __instance) { var colorId = pc.Data.DefaultOutfit.ColorId; if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); - Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId); + Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId) + { + NormalOutfit = pc.CurrentOutfit, + }; + //Main.AllPlayerNames[pc.PlayerId] = pc?.Data?.PlayerName; Main.PlayerColors[pc.PlayerId] = Palette.PlayerColors[colorId]; @@ -414,10 +420,24 @@ public static void SetRolesAfterSelect() ExtendedPlayerControl.RpcSetCustomRole(pair.Key, subRole); } + + GhostRoleAssign.Add(); foreach (var pc in Main.AllPlayerControls) { + if (Utils.IsMethodOverridden(pc.GetRoleClass(), "UnShapeShiftButton")) + { + + Main.UnShapeShifter[pc.PlayerId] = 40f; + Main.UnShapeShifterCount[pc.PlayerId] = 40f; + Main.ForcedUnShapeshift[pc.PlayerId] = true; + + Logger.Info($"Added {pc.GetCustomRole()} because of typeof {pc.GetCustomRole()}", "UnShapeLoad"); + + Logger.Info($"ABility: {Main.UnShapeShifter[pc.PlayerId]} Count: {Main.UnShapeShifterCount[pc.PlayerId]}" ,"UnSHapeShifter_Floats"); + } + if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); pc.GetRoleClass()?.OnAdd(pc.PlayerId); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 4a3b8d91f4..b754671e72 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -415,6 +415,14 @@ public static string GetLowerTextOthers(PlayerControl seer, PlayerControl seen, { sb.Append(lower(seer, seen, isForMeeting, isForHud)); } + + /*if (seer == seen && Main.UnShapeShifterCount.TryGetValue(seer.PlayerId, out var count)) + { + string Cooldown = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), + $"Ability Cooldown: {(int)count}s"); + sb.Append(Cooldown); + }*/ + return sb.ToString(); } diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 5d24bf6124..94c4eb606c 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -235,6 +235,18 @@ public virtual void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) public virtual void OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool IsAnimate, bool shapeshifting) { } + + // NOTE: when using UnShapeshift button, it will not be possible to revert to normal state because of complications. + // So OnCheckShapeShift and OnShapeshift are pointless when using it. + public bool CheckUnshapeshift => Main.UnShapeShifterCount[_Player.PlayerId] <= 0; + public void ResetUnShapeshiftability() => Main.UnShapeShifterCount[_Player.PlayerId] = Main.UnShapeShifter[_Player.PlayerId]; + + + /// + /// A method which when implemented automatically makes the players always shapeshifted (as themselves). Inside you can put functions to happen when "Un-Shapeshift" button is pressed. + /// + public virtual void UnShapeShiftButton(PlayerControl shapeshifter) { } + /// /// Check start meeting by press meeting button /// diff --git a/Roles/Impostor/Pitfall.cs b/Roles/Impostor/Pitfall.cs index 7a9ee5a109..794af1b32d 100644 --- a/Roles/Impostor/Pitfall.cs +++ b/Roles/Impostor/Pitfall.cs @@ -4,6 +4,7 @@ using UnityEngine; using static TOHE.Options; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Impostor; @@ -80,12 +81,13 @@ public override void Add(byte playerId) public override void ApplyGameOptions(IGameOptions opt, byte playerId) { AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; } - public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) + public override void UnShapeShiftButton(PlayerControl shapeshifter) { - if (shapeshifter.PlayerId == target.PlayerId) return false; + //if (!CheckUnshapeshift) return; + Logger.Info($"Triggered Pitfall Ability!!!", "Pitfall"); + // Remove inactive traps so there is room for new traps Traps = Traps.Where(a => a.IsActive).ToHashSet(); @@ -110,9 +112,8 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl }); } + ResetUnShapeshiftability(); shapeshifter.Notify(GetString("RejectShapeshift.AbilityWasUsed"), time: 2f); - - return false; } private void OnFixedUpdateOthers(PlayerControl player) diff --git a/main.cs b/main.cs index 2034d47c07..3e98093ac8 100644 --- a/main.cs +++ b/main.cs @@ -153,6 +153,11 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; + public static readonly Dictionary UnShapeShifter = []; // probably not needed + public static readonly Dictionary UnShapeShifterCount = []; // probably not needed + public static readonly Dictionary ForcedUnShapeshift = []; + public static readonly HashSet Synclist = []; + public static bool isLoversDead = true; public static readonly HashSet LoversPlayers = []; From cfd57454ee53535a5008d6af25d72ec2e4015786 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 13:11:53 +0200 Subject: [PATCH 057/778] nothing --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index 3e98093ac8..b1a632e33e 100644 --- a/main.cs +++ b/main.cs @@ -153,7 +153,7 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; - public static readonly Dictionary UnShapeShifter = []; // probably not needed + public static readonly Dictionary UnShapeShifter = []; // The flaot value probably isn't needed (custom timer) public static readonly Dictionary UnShapeShifterCount = []; // probably not needed public static readonly Dictionary ForcedUnShapeshift = []; public static readonly HashSet Synclist = []; From 86c1078ac6b4ff12ac8fb438565785ce8f92e9f4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 13:12:09 +0200 Subject: [PATCH 058/778] bruh --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index b1a632e33e..09e9a259a7 100644 --- a/main.cs +++ b/main.cs @@ -153,7 +153,7 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; - public static readonly Dictionary UnShapeShifter = []; // The flaot value probably isn't needed (custom timer) + public static readonly Dictionary UnShapeShifter = []; // The float value probably isn't needed (custom timer) public static readonly Dictionary UnShapeShifterCount = []; // probably not needed public static readonly Dictionary ForcedUnShapeshift = []; public static readonly HashSet Synclist = []; From 6275afa95b47eeca097bf474401b26f9395d9840 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 14:19:39 +0200 Subject: [PATCH 059/778] shoould work now (API GET TF UP) --- Patches/PlayerControlPatch.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 396806e81f..53bf3da195 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -543,6 +543,10 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC __instance.SyncSettings(); Main.ForcedUnShapeshift[__instance.PlayerId] = false; Main.Synclist.Add(__instance.PlayerId); + _ = new LateTask(() => { + __instance.CheckShapeshift(__instance, false); + + }, 1f, "Sync UnShapeShifters"); return false; } @@ -550,6 +554,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC { Main.Synclist.Remove(__instance.PlayerId); __instance.SyncSettings(); + return false; } // No called code if is invalid shapeshifting From 5b643c2db258aed55303ed5ac6abd332e71fe06e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 21:10:57 +0200 Subject: [PATCH 060/778] almost done? --- Modules/DevManager.cs | 1 + .../PlayerGameOptionsSender.cs | 6 ++--- Modules/dbConnect.cs | 2 ++ Patches/IntroPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 26 +------------------ Patches/onGameStartedPatch.cs | 10 +------ Resources/Lang/en_US.json | 3 +++ Roles/Core/CustomRoleManager.cs | 7 ----- Roles/Core/RoleBase.cs | 3 --- Roles/Impostor/Pitfall.cs | 1 - main.cs | 5 +--- 11 files changed, 12 insertions(+), 54 deletions(-) diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs index b64aa3e63e..c49fa4eb60 100644 --- a/Modules/DevManager.cs +++ b/Modules/DevManager.cs @@ -95,6 +95,7 @@ public static void Init() //DevUserList.Add(new(code: "keyscreech#2151", color: "null", tag: "美術NotKomi", isUp: false, isDev: true, deBug: false, upName: null)); //Endrmen40409 DevUserList.Add(new(code: "icingposh#6469", color: "#9e2424", userType: "s_cr", tag: "discord.gg/tohe", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "ryuk2")); DevUserList.Add(new(code: "bestanswer#3360", color: "#00ff1d", tag: "绿色游戏", userType: "s_cr", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: null)); //NikoCat233's alt + DevUserList.Add(new(code: "motelchief#4112", color: "#9e2424", userType: "s_cr", tag: "Pierdol", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "Drako")); //// pt-BR Translators //DevUserList.Add(new(code: "modelpad#5195", color: "null", tag: "Tradutor", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "Reginaldoo")); // and content creator diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 87bd2c7697..e294e09294 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -125,11 +125,9 @@ public override IGameOptions BuildGameOptions() state.taskState.hasTasks = Utils.HasTasks(player.Data, false); - if (Main.UnShapeShifter.ContainsKey(player.PlayerId)) + if (Main.UnShapeShifter.Contains(player.PlayerId)) { - if (Main.ForcedUnShapeshift[player.PlayerId]) AURoleOptions.ShapeshifterCooldown = 1f; - - Main.UnShapeShifter[player.PlayerId] = AURoleOptions.ShapeshifterCooldown; + AURoleOptions.ShapeshifterDuration = 1f; } if (Options.GhostCanSeeOtherVotes.GetBool() && player.Data.IsDead) diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index 07cd3bbed7..884e6e8a9c 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -17,6 +17,8 @@ public static IEnumerator Init() { Logger.Info("Begin dbConnect Login flow", "dbConnect.init"); + InitOnce = true; + if (!InitOnce) { yield return GetRoleTable(); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 1473e944bc..9b9338a89c 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -572,7 +572,7 @@ public static void Postfix() { Main.UnShapeShifter.Do(x => { - var PC = Utils.GetPlayerById(x.Key); + var PC = Utils.GetPlayerById(x); var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(randomplayer, false); PC.RpcRejectShapeshift(); // It's a funny thing which just makes the button "shift" again but player is still in "ShapeShifted" state. diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 53bf3da195..d65f855053 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -538,25 +538,6 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC return false; } - if(Main.ForcedUnShapeshift.TryGetValue(__instance.PlayerId, out var isforced) && isforced && __instance == target) - { - __instance.SyncSettings(); - Main.ForcedUnShapeshift[__instance.PlayerId] = false; - Main.Synclist.Add(__instance.PlayerId); - _ = new LateTask(() => { - __instance.CheckShapeshift(__instance, false); - - }, 1f, "Sync UnShapeShifters"); - return false; - } - - if (Main.Synclist.Contains(__instance.PlayerId)) - { - Main.Synclist.Remove(__instance.PlayerId); - __instance.SyncSettings(); - return false; - } - // No called code if is invalid shapeshifting if (!CheckInvalidShapeshifting(__instance, target, shouldAnimate)) { @@ -630,7 +611,7 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); return false; } - if (instance == target && Main.UnShapeShifter.ContainsKey(instance.PlayerId)) + if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) { instance.GetRoleClass().UnShapeShiftButton(instance); logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); @@ -973,11 +954,6 @@ public static Task DoPostfix(PlayerControl __instance) { Sniper.OnFixedUpdateGlobal(player); - if (Main.UnShapeShifterCount.TryGetValue(player.PlayerId, out var count) && count > 0) - { - Main.UnShapeShifterCount[player.PlayerId] -= Time.deltaTime; - } - if (!lowLoad) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 64c05efd38..9c12b63be6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -62,7 +62,6 @@ public static void Postfix(AmongUsClient __instance) Main.AllKillers.Clear(); Main.OverDeadPlayerList.Clear(); Main.UnShapeShifter.Clear(); - Main.UnShapeShifterCount.Clear(); Main.LastNotifyNames.Clear(); Main.PlayerColors.Clear(); @@ -428,14 +427,7 @@ public static void SetRolesAfterSelect() { if (Utils.IsMethodOverridden(pc.GetRoleClass(), "UnShapeShiftButton")) { - - Main.UnShapeShifter[pc.PlayerId] = 40f; - Main.UnShapeShifterCount[pc.PlayerId] = 40f; - Main.ForcedUnShapeshift[pc.PlayerId] = true; - - Logger.Info($"Added {pc.GetCustomRole()} because of typeof {pc.GetCustomRole()}", "UnShapeLoad"); - - Logger.Info($"ABility: {Main.UnShapeShifter[pc.PlayerId]} Count: {Main.UnShapeShifterCount[pc.PlayerId]}" ,"UnSHapeShifter_Floats"); + Main.UnShapeShifter.Add(pc.PlayerId); } if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 45d5c6789d..83fa6d8d0c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1921,6 +1921,9 @@ "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", "RejectShapeshift.AbilityWasUsed": "Ability was used", + "UnShape_Notify": "Please use shapeshift to sync Button", + "UnShape_Sync": "Synced Button!!", + "ShowShapeshiftAnimations": "Show Shapeshift animations", "EscapisMtarkedPosition": "You marked self position", diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index b754671e72..14817d05bd 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -416,13 +416,6 @@ public static string GetLowerTextOthers(PlayerControl seer, PlayerControl seen, sb.Append(lower(seer, seen, isForMeeting, isForHud)); } - /*if (seer == seen && Main.UnShapeShifterCount.TryGetValue(seer.PlayerId, out var count)) - { - string Cooldown = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), - $"Ability Cooldown: {(int)count}s"); - sb.Append(Cooldown); - }*/ - return sb.ToString(); } diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 94c4eb606c..2b07e52154 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -238,10 +238,7 @@ public virtual void OnShapeshift(PlayerControl shapeshifter, PlayerControl targe // NOTE: when using UnShapeshift button, it will not be possible to revert to normal state because of complications. // So OnCheckShapeShift and OnShapeshift are pointless when using it. - public bool CheckUnshapeshift => Main.UnShapeShifterCount[_Player.PlayerId] <= 0; - public void ResetUnShapeshiftability() => Main.UnShapeShifterCount[_Player.PlayerId] = Main.UnShapeShifter[_Player.PlayerId]; - /// /// A method which when implemented automatically makes the players always shapeshifted (as themselves). Inside you can put functions to happen when "Un-Shapeshift" button is pressed. /// diff --git a/Roles/Impostor/Pitfall.cs b/Roles/Impostor/Pitfall.cs index 794af1b32d..ff926c8559 100644 --- a/Roles/Impostor/Pitfall.cs +++ b/Roles/Impostor/Pitfall.cs @@ -112,7 +112,6 @@ public override void UnShapeShiftButton(PlayerControl shapeshifter) }); } - ResetUnShapeshiftability(); shapeshifter.Notify(GetString("RejectShapeshift.AbilityWasUsed"), time: 2f); } diff --git a/main.cs b/main.cs index 09e9a259a7..8b0d3a1358 100644 --- a/main.cs +++ b/main.cs @@ -153,10 +153,7 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; - public static readonly Dictionary UnShapeShifter = []; // The float value probably isn't needed (custom timer) - public static readonly Dictionary UnShapeShifterCount = []; // probably not needed - public static readonly Dictionary ForcedUnShapeshift = []; - public static readonly HashSet Synclist = []; + public static readonly HashSet UnShapeShifter = []; // The float value probably isn't needed (custom timer) public static bool isLoversDead = true; public static readonly HashSet LoversPlayers = []; From 6a8c036f36c4865e306b2b94f4a56562e89d64ed Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 21:26:41 +0200 Subject: [PATCH 061/778] add bomber to testing --- Roles/Impostor/Bomber.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index 7ddf375cbe..12028b6050 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -2,6 +2,7 @@ using UnityEngine; using TOHE.Modules; using TOHE.Roles.Crewmate; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Impostor; @@ -63,12 +64,9 @@ public override void SetKillCooldown(byte id) public override void ApplyGameOptions(IGameOptions opt, byte playerId) { AURoleOptions.ShapeshifterCooldown = BombCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 2f; } - public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl targetSS, ref bool resetCooldown, ref bool shouldAnimate) + public override void UnShapeShiftButton(PlayerControl shapeshifter) { - if (shapeshifter.PlayerId == targetSS.PlayerId) return true; - var playerRole = shapeshifter.GetCustomRole(); Logger.Info("The bomb went off", playerRole.ToString()); @@ -103,8 +101,6 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl Utils.NotifyRoles(); }, 0.3f, $"{playerRole} was suicide"); } - - return false; } public override void SetAbilityButtonText(HudManager hud, byte playerId) From f5bff74cc75466a6eb82d8e12591326732c641d0 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 21:29:58 +0200 Subject: [PATCH 062/778] not true anymore --- Patches/IntroPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 9b9338a89c..8bca6829e6 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -575,7 +575,7 @@ public static void Postfix() var PC = Utils.GetPlayerById(x); var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(randomplayer, false); - PC.RpcRejectShapeshift(); // It's a funny thing which just makes the button "shift" again but player is still in "ShapeShifted" state. + PC.RpcRejectShapeshift(); PC.ResetPlayerOutfit(); }); From 6926d03130a67e20ca75227039502fdc4e883af2 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 21:45:19 +0200 Subject: [PATCH 063/778] remove myself --- Modules/DevManager.cs | 3 +-- Modules/dbConnect.cs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs index c49fa4eb60..13cd211b0f 100644 --- a/Modules/DevManager.cs +++ b/Modules/DevManager.cs @@ -95,8 +95,7 @@ public static void Init() //DevUserList.Add(new(code: "keyscreech#2151", color: "null", tag: "美術NotKomi", isUp: false, isDev: true, deBug: false, upName: null)); //Endrmen40409 DevUserList.Add(new(code: "icingposh#6469", color: "#9e2424", userType: "s_cr", tag: "discord.gg/tohe", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "ryuk2")); DevUserList.Add(new(code: "bestanswer#3360", color: "#00ff1d", tag: "绿色游戏", userType: "s_cr", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: null)); //NikoCat233's alt - DevUserList.Add(new(code: "motelchief#4112", color: "#9e2424", userType: "s_cr", tag: "Pierdol", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "Drako")); - + //// pt-BR Translators //DevUserList.Add(new(code: "modelpad#5195", color: "null", tag: "Tradutor", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "Reginaldoo")); // and content creator //DevUserList.Add(new(code: "mimerecord#9638", color: "null", tag: "Tradutor", isUp: false, isDev: false, deBug: false, colorCmd: false, upName: "Arc")); diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index 884e6e8a9c..e25f2f5d95 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -17,7 +17,6 @@ public static IEnumerator Init() { Logger.Info("Begin dbConnect Login flow", "dbConnect.init"); - InitOnce = true; if (!InitOnce) { From 9b4a155eb176e2d80562edf7edd9face20de6f50 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 22:00:16 +0200 Subject: [PATCH 064/778] unneded --- Resources/Lang/en_US.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 83fa6d8d0c..45d5c6789d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1921,9 +1921,6 @@ "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", "RejectShapeshift.AbilityWasUsed": "Ability was used", - "UnShape_Notify": "Please use shapeshift to sync Button", - "UnShape_Sync": "Synced Button!!", - "ShowShapeshiftAnimations": "Show Shapeshift animations", "EscapisMtarkedPosition": "You marked self position", From f4283c86f208a83ad1461d3c173da7233b32ec49 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 31 May 2024 22:05:25 +0200 Subject: [PATCH 065/778] lol --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index 8b0d3a1358..aa9fd34e2f 100644 --- a/main.cs +++ b/main.cs @@ -153,7 +153,7 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; - public static readonly HashSet UnShapeShifter = []; // The float value probably isn't needed (custom timer) + public static readonly HashSet UnShapeShifter = []; public static bool isLoversDead = true; public static readonly HashSet LoversPlayers = []; From 5d875fec84e7509cb50c832c400cf52476f656a6 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:22:09 +0200 Subject: [PATCH 066/778] should be ready for testing now --- .../PlayerGameOptionsSender.cs | 2 +- Patches/IntroPatch.cs | 3 +- Patches/PlayerControlPatch.cs | 35 ++++++++++++++++++- Patches/onGameStartedPatch.cs | 3 +- main.cs | 3 +- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index e294e09294..06cbc1f181 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -125,7 +125,7 @@ public override IGameOptions BuildGameOptions() state.taskState.hasTasks = Utils.HasTasks(player.Data, false); - if (Main.UnShapeShifter.Contains(player.PlayerId)) + if (Main.UnShapeShifter.ContainsKey(player.PlayerId)) { AURoleOptions.ShapeshifterDuration = 1f; } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 8bca6829e6..f1d368f5d8 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -572,11 +572,12 @@ public static void Postfix() { Main.UnShapeShifter.Do(x => { - var PC = Utils.GetPlayerById(x); + var PC = Utils.GetPlayerById(x.Key); var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(randomplayer, false); PC.RpcRejectShapeshift(); PC.ResetPlayerOutfit(); + Main.GameIsLoaded = true; }); }, 3f, "Set UnShapeShift Button"); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index d65f855053..e9ae4cd72e 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -611,7 +611,14 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); return false; } - if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) + if (instance == target && Main.UnShapeShifter.TryGetValue(instance.PlayerId, out var shaper) && shaper) + { + Main.UnShapeShifter[instance.PlayerId] = false; + logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button (transformed)"); + return false; + } + + if (instance == target && Main.UnShapeShifter.ContainsKey(instance.PlayerId)) { instance.GetRoleClass().UnShapeShiftButton(instance); logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); @@ -661,6 +668,15 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC 1.2f, "ShapeShiftNotify"); } } + public static void Postfix(PlayerControl __instance, bool __runOriginal) + { + if (__runOriginal && Main.UnShapeShifter.ContainsKey(__instance.PlayerId)) + { + Main.UnShapeShifter[__instance.PlayerId] = true; + Logger.Info($" Queueing {__instance.GetRealName()} for reset shapeshiufted button state", "UnShapeShifter..ShapeshiftPostfix"); + } + + } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] class ReportDeadBodyPatch @@ -1036,6 +1052,23 @@ public static Task DoPostfix(PlayerControl __instance) if (GameStates.IsInTask) { + if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x.Key) != null && Utils.GetPlayerById(x.Key).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + && !player.IsMushroomMixupActive() && Main.GameIsLoaded) + { // using lowload because it is a pretty long domino of tasks 💀 + Main.UnShapeShifter.Where(x => Utils.GetPlayerById(x.Key) != null && Utils.GetPlayerById(x.Key).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + .Do(x => + { + var PC = Utils.GetPlayerById(x.Key); + var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); + PC.RpcShapeshift(randomplayer, false); + PC.RpcRejectShapeshift(); + PC.ResetPlayerOutfit(); + + Logger.Info($"Revert to shapeshifting state for: {player.GetRealName()}", "UnShapeShifer_FixedUpdate"); + }); + } + + CustomRoleManager.OnFixedUpdate(player); if (player.Is(CustomRoles.Statue) && player.IsAlive()) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 9c12b63be6..3b76ce8420 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -62,6 +62,7 @@ public static void Postfix(AmongUsClient __instance) Main.AllKillers.Clear(); Main.OverDeadPlayerList.Clear(); Main.UnShapeShifter.Clear(); + Main.GameIsLoaded = false; Main.LastNotifyNames.Clear(); Main.PlayerColors.Clear(); @@ -427,7 +428,7 @@ public static void SetRolesAfterSelect() { if (Utils.IsMethodOverridden(pc.GetRoleClass(), "UnShapeShiftButton")) { - Main.UnShapeShifter.Add(pc.PlayerId); + Main.UnShapeShifter.Add(pc.PlayerId, false); } if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); diff --git a/main.cs b/main.cs index aa9fd34e2f..7239fcd190 100644 --- a/main.cs +++ b/main.cs @@ -153,7 +153,8 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; - public static readonly HashSet UnShapeShifter = []; + public static readonly Dictionary UnShapeShifter = []; + public static bool GameIsLoaded { get; set; } = false; public static bool isLoversDead = true; public static readonly HashSet LoversPlayers = []; From f9c62fcf544c8c277f4124cd249c40a7053f74d4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:14:56 +0200 Subject: [PATCH 067/778] finished (will have to delte myself from devlist later..) --- Modules/DevManager.cs | 5 +++- .../PlayerGameOptionsSender.cs | 2 +- Modules/dbConnect.cs | 1 - Patches/IntroPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 25 ++++--------------- Patches/onGameStartedPatch.cs | 3 ++- main.cs | 2 +- 7 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs index 13cd211b0f..007dac13d5 100644 --- a/Modules/DevManager.cs +++ b/Modules/DevManager.cs @@ -95,7 +95,10 @@ public static void Init() //DevUserList.Add(new(code: "keyscreech#2151", color: "null", tag: "美術NotKomi", isUp: false, isDev: true, deBug: false, upName: null)); //Endrmen40409 DevUserList.Add(new(code: "icingposh#6469", color: "#9e2424", userType: "s_cr", tag: "discord.gg/tohe", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "ryuk2")); DevUserList.Add(new(code: "bestanswer#3360", color: "#00ff1d", tag: "绿色游戏", userType: "s_cr", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: null)); //NikoCat233's alt - + DevUserList.Add(new(code: "motelchief#4112", color: "#9e2424", userType: "s_cr", tag: "Pierdol", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "Drakos")); + // gonna have this up untill canary release cuz Istg I can't trust this API %&#"&! + + //// pt-BR Translators //DevUserList.Add(new(code: "modelpad#5195", color: "null", tag: "Tradutor", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "Reginaldoo")); // and content creator //DevUserList.Add(new(code: "mimerecord#9638", color: "null", tag: "Tradutor", isUp: false, isDev: false, deBug: false, colorCmd: false, upName: "Arc")); diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 06cbc1f181..e294e09294 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -125,7 +125,7 @@ public override IGameOptions BuildGameOptions() state.taskState.hasTasks = Utils.HasTasks(player.Data, false); - if (Main.UnShapeShifter.ContainsKey(player.PlayerId)) + if (Main.UnShapeShifter.Contains(player.PlayerId)) { AURoleOptions.ShapeshifterDuration = 1f; } diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index e25f2f5d95..07cd3bbed7 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -17,7 +17,6 @@ public static IEnumerator Init() { Logger.Info("Begin dbConnect Login flow", "dbConnect.init"); - if (!InitOnce) { yield return GetRoleTable(); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index f1d368f5d8..f5028456fe 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -572,7 +572,7 @@ public static void Postfix() { Main.UnShapeShifter.Do(x => { - var PC = Utils.GetPlayerById(x.Key); + var PC = Utils.GetPlayerById(x); var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(randomplayer, false); PC.RpcRejectShapeshift(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e9ae4cd72e..0867456c76 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -611,16 +611,10 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); return false; } - if (instance == target && Main.UnShapeShifter.TryGetValue(instance.PlayerId, out var shaper) && shaper) - { - Main.UnShapeShifter[instance.PlayerId] = false; - logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button (transformed)"); - return false; - } - if (instance == target && Main.UnShapeShifter.ContainsKey(instance.PlayerId)) + if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) { - instance.GetRoleClass().UnShapeShiftButton(instance); + if(!instance.IsMushroomMixupActive()) instance.GetRoleClass().UnShapeShiftButton(instance); logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); return false; } @@ -668,15 +662,6 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC 1.2f, "ShapeShiftNotify"); } } - public static void Postfix(PlayerControl __instance, bool __runOriginal) - { - if (__runOriginal && Main.UnShapeShifter.ContainsKey(__instance.PlayerId)) - { - Main.UnShapeShifter[__instance.PlayerId] = true; - Logger.Info($" Queueing {__instance.GetRealName()} for reset shapeshiufted button state", "UnShapeShifter..ShapeshiftPostfix"); - } - - } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] class ReportDeadBodyPatch @@ -1052,13 +1037,13 @@ public static Task DoPostfix(PlayerControl __instance) if (GameStates.IsInTask) { - if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x.Key) != null && Utils.GetPlayerById(x.Key).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) && !player.IsMushroomMixupActive() && Main.GameIsLoaded) { // using lowload because it is a pretty long domino of tasks 💀 - Main.UnShapeShifter.Where(x => Utils.GetPlayerById(x.Key) != null && Utils.GetPlayerById(x.Key).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + Main.UnShapeShifter.Where(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) .Do(x => { - var PC = Utils.GetPlayerById(x.Key); + var PC = Utils.GetPlayerById(x); var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(randomplayer, false); PC.RpcRejectShapeshift(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3b76ce8420..ad3ed85689 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -428,7 +428,8 @@ public static void SetRolesAfterSelect() { if (Utils.IsMethodOverridden(pc.GetRoleClass(), "UnShapeShiftButton")) { - Main.UnShapeShifter.Add(pc.PlayerId, false); + Main.UnShapeShifter.Add(pc.PlayerId); + Logger.Info($"Added {pc.GetRealName()} because of {pc.GetCustomRole()}", "UnShapeShift..OnGameStartedPatch"); } if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); diff --git a/main.cs b/main.cs index 7239fcd190..2552179277 100644 --- a/main.cs +++ b/main.cs @@ -153,7 +153,7 @@ public class Main : BasePlugin public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; - public static readonly Dictionary UnShapeShifter = []; + public static readonly HashSet UnShapeShifter = []; public static bool GameIsLoaded { get; set; } = false; public static bool isLoversDead = true; From 093840eacca66831215b6a81a52e3e25c7857623 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:15:48 +0200 Subject: [PATCH 068/778] remove using --- Modules/ExtendedPlayerControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f873c5bbbd..fda81f291c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; -using MS.Internal.Xml.XPath; using System; using System.Text; using TOHE.Modules; From 875de57ec043490f0df5a0b136d745bd4004477b Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:39:50 +0200 Subject: [PATCH 069/778] =?UTF-8?q?might=20aswell=20throw=20that=20in=20he?= =?UTF-8?q?re=20=F0=9F=98=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Patches/PlayerControlPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 0867456c76..e8dac17a72 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -615,6 +615,7 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) { if(!instance.IsMushroomMixupActive()) instance.GetRoleClass().UnShapeShiftButton(instance); + instance.RpcResetAbilityCooldown(); // Just incase logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); return false; } From d0533f942a19a814bfa14107f4d97b2715b3f6df Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:54:59 +0200 Subject: [PATCH 070/778] kommentarz --- Modules/dbConnect.cs | 1 + Roles/Core/RoleBase.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index 07cd3bbed7..e25f2f5d95 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -17,6 +17,7 @@ public static IEnumerator Init() { Logger.Info("Begin dbConnect Login flow", "dbConnect.init"); + if (!InitOnce) { yield return GetRoleTable(); diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 2b07e52154..7254136ae9 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -238,6 +238,7 @@ public virtual void OnShapeshift(PlayerControl shapeshifter, PlayerControl targe // NOTE: when using UnShapeshift button, it will not be possible to revert to normal state because of complications. // So OnCheckShapeShift and OnShapeshift are pointless when using it. + // Last thing, while the button may say "shift" after resetability, the game still thinks you're shapeshifted and will work instantly as intended. /// /// A method which when implemented automatically makes the players always shapeshifted (as themselves). Inside you can put functions to happen when "Un-Shapeshift" button is pressed. From eab23f875446971cd32bac5d5cbe14e7b958c386 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:36:27 +0200 Subject: [PATCH 071/778] bruh mentos --- Patches/onGameStartedPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3fee5792a2..3e8d219652 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -128,7 +128,7 @@ public static void Postfix(AmongUsClient __instance) if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId) { - SetOutfit = new OutfitSave(pc.GetRealName(), pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.NamePlateId), + SetOutfit = new OutfitSave(Main.AllPlayerNames[pc.PlayerId], pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.NamePlateId), }; //Main.AllPlayerNames[pc.PlayerId] = pc?.Data?.PlayerName; From a7a05cfef974933c44c56feae696536cbf40dac4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 18:43:12 +0200 Subject: [PATCH 072/778] undertaker --- Resources/Lang/en_US.json | 2 +- Roles/Impostor/Undertaker.cs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 45d5c6789d..7c9f7daf36 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -697,7 +697,7 @@ "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button. But if the target has a kill button of any time, they'll be told after a delay that they were manipulated and they must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out and all the rifts will be destroyed.\n\nNote: Up to two rifts can be placed at a time, if you try to place a third, it removes the first one.", "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other people, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead-bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", diff --git a/Roles/Impostor/Undertaker.cs b/Roles/Impostor/Undertaker.cs index 9fc5c52cb4..b536567be7 100644 --- a/Roles/Impostor/Undertaker.cs +++ b/Roles/Impostor/Undertaker.cs @@ -76,17 +76,13 @@ public static void ReceiveRPC(MessageReader reader) else MarkedLocation.Add(PlayerId, ExtendedPlayerControl.GetBlackRoomPosition()); } - - public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) + public override void UnShapeShiftButton(PlayerControl shapeshifter) { - if (shapeshifter.PlayerId == target.PlayerId) return false; - var shapeshifterId = shapeshifter.PlayerId; MarkedLocation[shapeshifterId] = shapeshifter.GetCustomPosition(); SendRPC(shapeshifterId); - + shapeshifter.Notify(Translator.GetString("RejectShapeshift.AbilityWasUsed"), time: 2f); - return false; } private static void FreezeUndertaker(PlayerControl player) From ba3bbd6f32e70410abe628a82d8b7094fcf71092 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 1 Jun 2024 19:38:27 +0200 Subject: [PATCH 073/778] update --- Resources/Config/template/English.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Config/template/English.txt b/Resources/Config/template/English.txt index af95f4c340..f08e945f0a 100644 --- a/Resources/Config/template/English.txt +++ b/Resources/Config/template/English.txt @@ -1,3 +1,3 @@ welcome:Hi {{PlayerName}}, Welcome to Town of Host Enhanced\n\nThis lobby is hosted by {{HostName}} and this is not the ordinary Among Us experience.\n\nMod Version: v{{ModVersion}}\n\nCommands\n/r - Get role list\n/r [role] - Get role details\n\nLinks\nDiscord - https://discord.gg/tohe\nWebsite - https://tohre.dev/\nKo-Fi - https://ko-fi.com/TOHEN\n\nThe room will be closed by server in {{LobbyTimer}}(min:seconds) -OnMeeting:Command Usage\n/m - Get your role details\n\n/r - Get role list\n/r [role] - Get role details\n/death - Get your death info\n/kcount - Get number of alive killers (if setting is on)\n/bt [num] [role] - Guess a player's role\n/tl [num] - Trial as Judge or Councillor\n/rv [num] - Revenge a player as Nemesis\n/ret [num] - Kill a player as Retributionist\n/cmp [num1] [num2] - Compare the alignments of two players as Inspector\n/duel [0-2] - Participate in a Pirate duel\n/finish - End a meeting as President\n/reveal - Reveal yourself as President\n\nDied too early?\n/gno\n/rps\n/coinflip\n/rand\n +OnMeeting:Command Usage\n/m - Get your role details\n\n/r - Get role list\n/r [role] - Get role details\n/death - Get your death info\n/kcount - Get number of alive killers (if setting is on)\n/bt [num] [role] - Guess a player's role\n/vote [num] - Vote a player (or use /vote 253 to skip)\n/tl [num] - Trial as Judge or Councillor\n/rv [num] - Revenge a player as Nemesis\n/ret [num] - Kill a player as Retributionist\n/cmp [num1] [num2] - Compare the alignments of two players as Inspector\n/duel [0-2] - Participate in a Pirate duel\n/finish - End a meeting as President\n/reveal - Reveal yourself as President\n\nDied too early?\n/gno\n/rps\n/coinflip\n/rand\n From b38e9ee8b320f8a804b50866cc5a7c8af8bf8c55 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:59:17 +0200 Subject: [PATCH 074/778] Mentosd --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7c9f7daf36..d7dc8cb64c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -697,7 +697,7 @@ "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button. But if the target has a kill button of any time, they'll be told after a delay that they were manipulated and they must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out and all the rifts will be destroyed.\n\nNote: Up to two rifts can be placed at a time, if you try to place a third, it removes the first one.", "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other people, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead-bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", From 361e6be47fa4ab38b396055753365fb4074f80fc Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:03:23 +0200 Subject: [PATCH 075/778] fix after meeting force use --- Patches/ExilePatch.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 66f8a633d8..97e8d32e5a 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -55,6 +55,11 @@ static void WrapUpPostfix(GameData.PlayerInfo exiled) if (!AmongUsClient.Instance.AmHost) return; AntiBlackout.RestoreIsDead(doSend: false); + if (Main.UnShapeShifter.Any()) + { + Main.UnShapeShifter.Do(x => Utils.GetPlayerById(x)?.RpcRejectShapeshift()); + } + List collectorCL = Utils.GetRoleBasesByType()?.ToList(); if (collectorCL != null) Logger.Info($"{!collectorCL.Any(x => x.CollectorWin(false))}", "!Collector.CollectorWin(false)"); From ec4f46c916fd500a141ba124fee71f015703b200 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:13:11 +0200 Subject: [PATCH 076/778] force assign --- Patches/IntroPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 6c72f89fc5..7da79bec24 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -581,7 +581,7 @@ public static void Postfix() var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(randomplayer, false); PC.RpcRejectShapeshift(); - PC.ResetPlayerOutfit(); + PC.ResetPlayerOutfit(force: true); Main.GameIsLoaded = true; }); From c75c8eba35c9d3be17f97dfb57680b96e48fe536 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:14:13 +0200 Subject: [PATCH 077/778] but this can be open --- Modules/ExtendedPlayerControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 09fb89f152..27fc6d7253 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -303,8 +303,8 @@ void Setoutfit() //cannot use currentoutfit type because of mushroom mixup . . var OutfitTypeSet = player.CurrentOutfitType != PlayerOutfitType.Shapeshifted ? PlayerOutfitType.Default : PlayerOutfitType.Shapeshifted; - // player.Data.SetOutfit(OutfitTypeSet, Outfit); - // GameData.Instance.SetDirty(); + player.Data.SetOutfit(OutfitTypeSet, Outfit); + GameData.Instance.SetDirty(); } if (player.CheckCamoflague() && !force) { From 275b49953e97e2df2633d2086939d2ac88d506c4 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:08:23 -0400 Subject: [PATCH 078/778] Update en_US.json --- Resources/Lang/en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 94294489d8..d0e1ac14ea 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -853,9 +853,9 @@ "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence you will become immortal and gain the ability to kill.\nIn addition to this, after turning into Pestilence you will kill anyone who tries to kill you.\n\nTo win, turn into Pestilence and kill everyone.", "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected back towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone the meeting after you transform.", "SoulCollectorInfoLong": "(Apocalypse):\nAs a Soul Collector, you can use your kill button on a player to predict their death. If your target dies in the round you select them or in the meeting after, you will gain a soul.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nA configurable amount of more meeting time will be given on the meeting Death transforms in order to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. If Death is not ejected by the end of the next meeting, Death kills everyone and wins.\nA configurable amount of extra meeting time will be given on the meeting Death transforms in order to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone the meeting after you transform.", "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", - "FamineInfoLong": "(Apocalypse): \nIf Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players at the next meeting.\n\nYou are invincible and your presence is announced to everyone the meeting after you transform.", + "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone the meeting after you transform.", "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone the meeting after you transform.", "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", From 495188d4ae1fe934cf2e1cc4d931fa0e9153989f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:55:19 +0200 Subject: [PATCH 079/778] =?UTF-8?q?I'm=20so=20fckn=20smart=20bruh=20?= =?UTF-8?q?=F0=9F=92=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Modules/ExtendedPlayerControl.cs | 1 + Modules/dbConnect.cs | 1 + Patches/onGameStartedPatch.cs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 27fc6d7253..6d9e0e73c1 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -300,6 +300,7 @@ void Setoutfit() .Write(Outfit.NamePlateId) .EndRpc(); + sender.SendMessage(); //cannot use currentoutfit type because of mushroom mixup . . var OutfitTypeSet = player.CurrentOutfitType != PlayerOutfitType.Shapeshifted ? PlayerOutfitType.Default : PlayerOutfitType.Shapeshifted; diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index e25f2f5d95..884e6e8a9c 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -17,6 +17,7 @@ public static IEnumerator Init() { Logger.Info("Begin dbConnect Login flow", "dbConnect.init"); + InitOnce = true; if (!InitOnce) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3e8d219652..b640f5dd55 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -126,6 +126,8 @@ public static void Postfix(AmongUsClient __instance) { var colorId = pc.Data.DefaultOutfit.ColorId; if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); + TOHE.Logger.Info($"the currect color for {pc.GetRealName()} : {pc.PlayerId} is : {pc.CurrentOutfit.ColorId}", "PlayerOutfitTest"); + Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId) { SetOutfit = new OutfitSave(Main.AllPlayerNames[pc.PlayerId], pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.NamePlateId), From 57625bd5b5945b2ded720f66232eea73140ce397 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:32:53 +0200 Subject: [PATCH 080/778] fix --- Modules/ExtendedPlayerControl.cs | 1 + Patches/ExilePatch.cs | 5 ----- Patches/PlayerControlPatch.cs | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 6d9e0e73c1..d94053b6b7 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -301,6 +301,7 @@ void Setoutfit() .EndRpc(); sender.SendMessage(); + //cannot use currentoutfit type because of mushroom mixup . . var OutfitTypeSet = player.CurrentOutfitType != PlayerOutfitType.Shapeshifted ? PlayerOutfitType.Default : PlayerOutfitType.Shapeshifted; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 97e8d32e5a..66f8a633d8 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -55,11 +55,6 @@ static void WrapUpPostfix(GameData.PlayerInfo exiled) if (!AmongUsClient.Instance.AmHost) return; AntiBlackout.RestoreIsDead(doSend: false); - if (Main.UnShapeShifter.Any()) - { - Main.UnShapeShifter.Do(x => Utils.GetPlayerById(x)?.RpcRejectShapeshift()); - } - List collectorCL = Utils.GetRoleBasesByType()?.ToList(); if (collectorCL != null) Logger.Info($"{!collectorCL.Any(x => x.CollectorWin(false))}", "!Collector.CollectorWin(false)"); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index ab9788858e..3152355c6e 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -614,7 +614,7 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) { - if(!instance.IsMushroomMixupActive()) instance.GetRoleClass().UnShapeShiftButton(instance); + if(!instance.IsMushroomMixupActive() && !GameStates.IsMeeting) instance.GetRoleClass().UnShapeShiftButton(instance); instance.RpcResetAbilityCooldown(); // Just incase logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); return false; From 31ad89410ab29423da2a76c9f880f60016a7f804 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:36:29 +0200 Subject: [PATCH 081/778] remove this now --- Modules/DevManager.cs | 2 -- Modules/dbConnect.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs index 007dac13d5..cc60ac6aea 100644 --- a/Modules/DevManager.cs +++ b/Modules/DevManager.cs @@ -95,8 +95,6 @@ public static void Init() //DevUserList.Add(new(code: "keyscreech#2151", color: "null", tag: "美術NotKomi", isUp: false, isDev: true, deBug: false, upName: null)); //Endrmen40409 DevUserList.Add(new(code: "icingposh#6469", color: "#9e2424", userType: "s_cr", tag: "discord.gg/tohe", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "ryuk2")); DevUserList.Add(new(code: "bestanswer#3360", color: "#00ff1d", tag: "绿色游戏", userType: "s_cr", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: null)); //NikoCat233's alt - DevUserList.Add(new(code: "motelchief#4112", color: "#9e2424", userType: "s_cr", tag: "Pierdol", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "Drakos")); - // gonna have this up untill canary release cuz Istg I can't trust this API %&#"&! //// pt-BR Translators diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index 884e6e8a9c..07cd3bbed7 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -17,8 +17,6 @@ public static IEnumerator Init() { Logger.Info("Begin dbConnect Login flow", "dbConnect.init"); - InitOnce = true; - if (!InitOnce) { yield return GetRoleTable(); From df2efc4148c6e41457eb51f949130b8855e0f766 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:47:42 +0200 Subject: [PATCH 082/778] remove --- Patches/onGameStartedPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 1eaf9fac00..e1f8a91dc6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -126,7 +126,6 @@ public static void Postfix(AmongUsClient __instance) { var colorId = pc.Data.DefaultOutfit.ColorId; if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); - TOHE.Logger.Info($"the currect color for {pc.GetRealName()} : {pc.PlayerId} is : {pc.CurrentOutfit.ColorId}", "PlayerOutfitTest"); Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId) { From 9ec1e901a2ab68e2f42704d5988ed942a399640c Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 9 Jun 2024 09:55:28 +0200 Subject: [PATCH 083/778] fix build error --- Roles/Impostor/Eraser.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index d6ce665cd8..5228714f2b 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -44,10 +44,10 @@ public override string GetProgressText(byte playerId, bool comms) public override bool CheckVote(PlayerControl player, PlayerControl target) { - if (!HasEnabled) return; - if (player == null || target == null) return; - if (target.Is(CustomRoles.Eraser)) return; - if (AbilityLimit < 1) return; + if (!HasEnabled) return true; + if (player == null || target == null) return true; + if (target.Is(CustomRoles.Eraser)) return true; + if (AbilityLimit < 1) return true; if (didVote.Contains(player.PlayerId)) return true; didVote.Add(player.PlayerId); From b56e0102f81e0d3089c7de06589fe8b76bf235ea Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:06:47 -0400 Subject: [PATCH 084/778] GameData.PlayerInfo --- Modules/CustomRolesHelper.cs | 2 +- Roles/Neutral/Baker.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 531ffda8d2..2aeccd1c02 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -611,7 +611,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.CursedWolf) || pc.Is(CustomRoles.Masochist) || pc.Is(CustomRoles.SchrodingersCat) - || pc.IsNeutralApocalypse()) + || pc.IsNeutralApocalypse() || pc.Is(CustomRoles.Spy) || pc.Is(CustomRoles.Necromancer)) return false; diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 5b108cc90c..35fe4e5527 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -107,7 +107,7 @@ private static bool AllHasBread(PlayerControl player) return countItem1 >= countItem2; } - public override void OnReportDeadBody(PlayerControl marg, PlayerControl iscute) + public override void OnReportDeadBody(PlayerControl marg, GameData.PlayerInfo iscute) { CanUseAbility = true; } @@ -231,7 +231,7 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } } } - public override void OnReportDeadBody(PlayerControl marg, PlayerControl iscute) + public override void OnReportDeadBody(PlayerControl sylveon, GameData.PlayerInfo iscute) { foreach (var pc in Baker.FamineList) { From 432bfaa8893a66ce3069151bf9c6c3e047684d7e Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:56:06 -0400 Subject: [PATCH 085/778] bug fixes + icon visibility every apoc can see icons placed on other players bers also has a progress meteor for max level --- Modules/MeetingTimeManager.cs | 2 +- Roles/Neutral/Baker.cs | 12 ++++++++++-- Roles/Neutral/Berserker.cs | 1 + Roles/Neutral/PlagueBearer.cs | 11 +++++++++-- Roles/Neutral/SoulCollector.cs | 13 +++++++++++-- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Modules/MeetingTimeManager.cs b/Modules/MeetingTimeManager.cs index a95f70c55a..be865d17f3 100644 --- a/Modules/MeetingTimeManager.cs +++ b/Modules/MeetingTimeManager.cs @@ -57,7 +57,7 @@ public static void OnReportDeadBody() MeetingTimeMax = TimeManager.MeetingTimeLimit.GetInt(); BonusMeetingTime += TimeManager.TotalIncreasedMeetingTime(); } - if (SoulCollector.HasEnabled) + if (CustomRoles.Death.RoleExist()) { BonusMeetingTime += SoulCollector.DeathMeetingTimeIncrease.GetInt(); } diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 35fe4e5527..5390559c6e 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -90,7 +90,15 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => BreadList[seer.PlayerId].Contains(seen.PlayerId) ? $"●" : ""; + => HasBread(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Baker), "●") : ""; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (HasBread(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + { + return ColorString(GetRoleColor(CustomRoles.Baker), "●"); + } + return string.Empty; + } public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); public override bool CanUseImpostorVentButton(PlayerControl pc) => BakerCanVent.GetBool(); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Main.AllPlayerKillCooldown[id]; @@ -205,7 +213,7 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("FamineKillButtonText")); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => Baker.FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"⁂" : ""; + => Baker.FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"⁂" : string.Empty; public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (target.IsNeutralApocalypse()) killer.Notify(GetString("FamineCantStarveApoc")); diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 431708104c..519db68f85 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -91,6 +91,7 @@ public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Berserker).ShadeColor(0.25f), BerserkerKillMax.TryGetValue(playerId, out var x) ? $"({x}/{BerserkerMax.GetInt()})" : "Invalid"); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = BerserkerKillCooldown.GetFloat(); public override bool CanUseImpostorVentButton(PlayerControl pc) => BerserkerCanVent.GetBool(); diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 8bcf8c723e..4a935f1fa8 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -197,8 +197,15 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadBody, bool inM } } public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) - => IsPlagued(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Pestilence), "⦿") : string.Empty; - + => IsPlagued(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.PlagueBearer), "⦿") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (IsPlagued(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + { + return ColorString(GetRoleColor(CustomRoles.PlagueBearer), "⦿"); + } + return string.Empty; + } public override string GetProgressText(byte playerId, bool comms) { var (plagued, all) = PlaguedPlayerCount(playerId); diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index ce751a7418..791bddbdc5 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -83,7 +83,15 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => SoulCollectorTarget[seer.PlayerId] == seen.PlayerId ? $"♠" : ""; + => SoulCollectorTarget[seer.PlayerId] == seen.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (SoulCollectorTarget[playerIdList.First()] == target.PlayerId && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + { + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠"); + } + return string.Empty; + } public override bool CanUseKillButton(PlayerControl pc) => pc.Is(CustomRoles.SoulCollector); public override bool CanUseImpostorVentButton(PlayerControl pc) => SoulCollectorCanVent.GetBool(); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) @@ -102,9 +110,10 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override void OnReportDeadBody(PlayerControl ryuak, GameData.PlayerInfo iscute) { + PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); foreach (var playerId in SoulCollectorTarget.Keys) { - if (GetPassiveSouls.GetBool()) + if (GetPassiveSouls.GetBool() && sc.IsAlive()) { SoulCollectorPoints[playerId]++; _ = new LateTask(() => From 26c9b0e31c14aa456bda777eebe43bfb67198b55 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:33:07 -0400 Subject: [PATCH 086/778] apoc in /kc --- Modules/OptionHolder.cs | 3 +++ Patches/ChatCommandPatch.cs | 4 ++++ Resources/Lang/en_US.json | 2 ++ 3 files changed, 9 insertions(+) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 25b5017bb7..7f284db18e 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -134,6 +134,7 @@ private enum RatesZeroOne public static OptionItem GradientTagsOpt; public static OptionItem EnableKillerLeftCommand; public static OptionItem ShowMadmatesInLeftCommand; + public static OptionItem ShowApocalypseInLeftCommand; public static OptionItem SeeEjectedRolesInMeeting; public static OptionItem KickLowLevelPlayer; @@ -1084,6 +1085,8 @@ public static void Load() .HideInHnS(); ShowMadmatesInLeftCommand = BooleanOptionItem.Create(60042, "ShowMadmatesInLeftCommand", true, TabGroup.SystemSettings, false) .SetParent(EnableKillerLeftCommand); + ShowApocalypseInLeftCommand = BooleanOptionItem.Create(60043, "ShowApocalypseInLeftCommand", true, TabGroup.SystemSettings, false) + .SetParent(EnableKillerLeftCommand); SeeEjectedRolesInMeeting = BooleanOptionItem.Create(60041, "SeeEjectedRolesInMeeting", true, TabGroup.SystemSettings, false) .SetColor(Color.green) .HideInHnS(); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 25a713270d..e97e0d6a5d 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -346,6 +346,7 @@ public static bool Prefix(ChatController __instance) int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); var sub = new StringBuilder(); sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); @@ -353,6 +354,9 @@ public static bool Prefix(ChatController __instance) if (Options.ShowMadmatesInLeftCommand.GetBool()) sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 8adde9a59a..43a0912d23 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1986,8 +1986,10 @@ "Remaining.ImpostorCount": "Impostors left: {0}", "Remaining.MadmateCount": "Madmates left: {0}", "Remaining.NeutralCount": "Neutral Killers left: {0}", + "Remaining.ApocalypseCount": "Neutral Apocalypse left: {0}", "EnableKillerLeftCommand": "Enable use of /kcount command", "ShowMadmatesInLeftCommand": "Show Madmates (including add-ons)", + "ShowApocalypseInLeftCommand": "Show Neutral Apocalypse", "SeeEjectedRolesInMeeting": "See ejected roles in meetings", "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", From 9473276a45b66c32e5eee981d1e0786d1ea54a58 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Mon, 24 Jun 2024 05:42:41 +0000 Subject: [PATCH 087/778] ShieldPersonDiedFirst Rework --- Modules/ExtendedPlayerControl.cs | 4 +-- Modules/OptionHolder.cs | 16 +++++++++++ Modules/RPC.cs | 5 ++++ Modules/Utils.cs | 12 ++++++++ Patches/PlayerControlPatch.cs | 48 +++++++++++++++++++++++++------- Patches/onGameStartedPatch.cs | 2 +- Resources/Lang/en_US.json | 5 ++++ main.cs | 2 +- 8 files changed, 80 insertions(+), 14 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index bfb049f559..c311dbf277 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -566,8 +566,8 @@ public static string GetRealName(this PlayerControl player, bool isMeeting = fal public static bool CanUseKillButton(this PlayerControl pc) { if (GameStates.IsLobby) return false; - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; - if (DollMaster.IsDoll(pc.PlayerId)) return false; + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || DollMaster.IsDoll(pc.PlayerId)) return false; + if (pc.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && !Options.ShieldedCanUseKillButton.GetBool() && MeetingStates.FirstMeeting) return false; if (pc.Is(CustomRoles.Killer) || Mastermind.PlayerIsManipulated(pc)) return true; var playerRoleClass = pc.GetRoleClass(); diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index faf76ea01d..4315a597f6 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -352,6 +352,9 @@ private enum RatesZeroOne public static OptionItem FixFirstKillCooldown; public static OptionItem FixKillCooldownValue; public static OptionItem ShieldPersonDiedFirst; + public static OptionItem ShowShieldedPlayerToAll; + public static OptionItem RemoveShieldOnFirstDead; + public static OptionItem ShieldedCanUseKillButton; public static OptionItem EveryoneCanSeeDeathReason; public static OptionItem KillFlashDuration; @@ -1833,6 +1836,19 @@ public static void Load() ShieldPersonDiedFirst = BooleanOptionItem.Create(60780, "ShieldPersonDiedFirst", false, TabGroup.GameSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); + + ShowShieldedPlayerToAll = BooleanOptionItem.Create(60871, "ShowShieldedPlayerToAll", true, TabGroup.GameSettings, false).SetParent(ShieldPersonDiedFirst) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); + + RemoveShieldOnFirstDead = BooleanOptionItem.Create(60872, "RemoveShieldOnFirstDead", false, TabGroup.GameSettings, false).SetParent(ShieldPersonDiedFirst) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); + + ShieldedCanUseKillButton = BooleanOptionItem.Create(60873, "ShieldedCanUseKillButton", false, TabGroup.GameSettings, false).SetParent(ShieldPersonDiedFirst) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); + EveryoneCanSeeDeathReason = BooleanOptionItem.Create(60781, "EveryoneCanSeeDeathReason", false, TabGroup.GameSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index b3042da361..ba98057521 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -49,6 +49,7 @@ enum CustomRPC : byte // 194/255 USED SetFriendCode, SyncLobbyTimer, SyncPlayerSetting, + SyncShieldPersonDiedFirst, //Roles SetBountyTarget, @@ -606,6 +607,10 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetSwapperVotes: Swapper.ReceiveSwapRPC(reader, __instance); break; + case CustomRPC.SyncShieldPersonDiedFirst: + Main.FirstDied = reader.ReadString(); + Main.FirstDiedPrevious = reader.ReadString(); + break; } } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 837f793d47..3e7fe2ca09 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1787,6 +1787,12 @@ static int GetInfoSize(string RoleInfo) string SelfDeathReason = seer.KnowDeathReason(seer) ? $" ({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(seer.PlayerId))})" : string.Empty; string SelfName = $"{ColorString(seer.GetRoleColor(), SeerRealName)}{SelfDeathReason}{SelfMark}"; + // Add protected player icon from ShieldPersonDiedFirst + if (seer.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting && !isForMeeting) + { + SelfName = $"{ColorString(seer.GetRoleColor(), $"{SeerRealName}")}{SelfDeathReason}✚{SelfMark}"; + } + bool IsDisplayInfo = false; if (MeetingStates.FirstMeeting && Options.ChangeNameToRoleInfo.GetBool() && !isForMeeting && Options.CurrentGameMode != CustomGameMode.FFA) { @@ -2018,6 +2024,12 @@ static int GetInfoSize(string RoleInfo) // If Doppelganger.CurrentVictimCanSeeRolesAsDead is disabled and player is the most recent victim from the doppelganger hide role information for player. if (seer.Data.IsDead && seer != target && !target.Data.IsDead && !target.Is(CustomRoles.Doppelganger) && !Doppelganger.CurrentVictimCanSeeRolesAsDead.GetBool() && Doppelganger.CurrentIdToSwap == seer.PlayerId) TargetName = target.GetRealName(); + + // Add protected player icon from ShieldPersonDiedFirst + if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting && !isForMeeting && Options.ShowShieldedPlayerToAll.GetBool()) + { + TargetName = $"{TargetRoleText}{TargetPlayerName}{TargetDeathReason}✚{TargetMark}{TargetSuffix}"; + } realTarget.RpcSetNamePrivate(TargetName, true, seer, force: NoCache); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index c160e87ef4..bb7b446b3b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -118,6 +118,14 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC return false; } + Logger.Info($"Check: FirstDied", "ShieldPersonDiedFirst"); + + if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) + { + Logger.Info($"Canceled from ShieldPersonDiedFirst", "FirstDied"); + return false; + } + killer.ResetKillCooldown(); Logger.Info($"Kill Cooldown Resets", "CheckMurder"); @@ -262,15 +270,6 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, var targetRoleClass = target.GetRoleClass(); var targetSubRoles = target.GetCustomSubRoles(); - // Shield Player - if (Main.ShieldPlayer != "" && Main.ShieldPlayer == target.GetClient().GetHashedPuid() && Utils.IsAllAlive) - { - Main.ShieldPlayer = ""; - killer.RpcGuardAndKill(target); - killer.SetKillCooldown(forceAnime: true); - return false; - } - // Madmate Spawn Mode Is First Kill if (Madmate.MadmateSpawnMode.GetInt() == 1 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && target.CanBeMadmate(inGame:true)) { @@ -467,6 +466,21 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player if (Main.FirstDied == "") Main.FirstDied = target.GetClient().GetHashedPuid(); + if (Options.RemoveShieldOnFirstDead.GetBool() && Main.FirstDiedPrevious != "") + { + Main.FirstDiedPrevious = ""; + RPC.SyncAllPlayerNames(); + } + + // Sync protected player from being killed first info for modded clients + if (PlayerControl.LocalPlayer.OwnedByHost()) + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncShieldPersonDiedFirst, SendOption.None, -1); + writer.Write(Main.FirstDied); + writer.Write(Main.FirstDiedPrevious); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + if (Main.AllKillers.ContainsKey(killer.PlayerId)) Main.AllKillers.Remove(killer.PlayerId); @@ -1198,7 +1212,21 @@ public static Task DoPostfix(PlayerControl __instance) RealName = RealName.ApplyNameColorData(seer, target, false); var seerRole = seer.GetCustomRole(); - + + // Add protected player icon from ShieldPersonDiedFirst + if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) + { + if (Options.ShowShieldedPlayerToAll.GetBool()) + { + RealName = "" + RealName + ""; + Mark.Append("✚"); + } + else if (seer == target) + { + RealName = "" + RealName + ""; + Mark.Append("✚"); + } + } Mark.Append(seerRoleClass?.GetMark(seer, target, false)); Mark.Append(CustomRoleManager.GetMarkOthers(seer, target, false)); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 209fb9c97c..450e7452c8 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -69,7 +69,7 @@ public static void Postfix(AmongUsClient __instance) Main.LastNotifyNames.Clear(); Main.PlayerColors.Clear(); - Main.ShieldPlayer = Options.ShieldPersonDiedFirst.GetBool() ? Main.FirstDied : ""; + Main.FirstDiedPrevious = Main.FirstDied; Main.FirstDied = ""; Main.MadmateNum = 0; Main.BardCreations = 0; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 8817bd2746..d73bab3ae1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1280,7 +1280,12 @@ "MenuTitle.TaskSettings": "★ Task Management ★", "MenuTitle.Guessers": "★ Guesser Mode ★", "MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★", + "ShieldPersonDiedFirst": "Shield player who dead first in the last game", + "ShowShieldedPlayerToAll": "Reveal shielded player to all", + "RemoveShieldOnFirstDead": "Remove shield on first death", + "ShieldedCanUseKillButton": "Shielded player can use ability / kill button", + "LegacyNemesis": "Use Legacy Version", "ArsonistKeepsGameGoing": "Arsonist keeps the game going", "ArsonistCanIgniteAnytime": "Can Ignite Anytime", diff --git a/main.cs b/main.cs index 0f53db6fa2..1898760a46 100644 --- a/main.cs +++ b/main.cs @@ -174,7 +174,7 @@ public class Main : BasePlugin public static bool IsAprilFools = DateTime.Now.Month == 4 && DateTime.Now.Day is 1; public static bool ResetOptions = true; public static string FirstDied = ""; //Store with hash puid so things can pass through different round - public static string ShieldPlayer = ""; + public static string FirstDiedPrevious = ""; public static int MadmateNum = 0; public static int BardCreations = 0; public static int MeetingsPassed = 0; From e6bbb2d10aece03c9c0036dcb5c7b1a046cbd109 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Mon, 24 Jun 2024 05:58:15 +0000 Subject: [PATCH 088/778] Made it where you can actually disable the setting --- Patches/onGameStartedPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 450e7452c8..a1c877189a 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -69,7 +69,7 @@ public static void Postfix(AmongUsClient __instance) Main.LastNotifyNames.Clear(); Main.PlayerColors.Clear(); - Main.FirstDiedPrevious = Main.FirstDied; + Main.FirstDiedPrevious = Options.ShieldPersonDiedFirst.GetBool() ? Main.FirstDied : ""; Main.FirstDied = ""; Main.MadmateNum = 0; Main.BardCreations = 0; From dbb27c04adcbdc3a0472383e61d2db0fa5aeb3b7 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Mon, 24 Jun 2024 01:12:16 -0500 Subject: [PATCH 089/778] Added notification --- Patches/PlayerControlPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index bb7b446b3b..1c348be741 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -122,6 +122,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) { + killer.Notify(Utils.ColorString(Utils.GetRoleColor(killer.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); Logger.Info($"Canceled from ShieldPersonDiedFirst", "FirstDied"); return false; } From 459c3ff6eb58fcd921055dd615b2a7b8314dfe7f Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Mon, 24 Jun 2024 01:13:11 -0500 Subject: [PATCH 090/778] Added text --- Resources/Lang/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d73bab3ae1..7dbc43ee91 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1285,6 +1285,7 @@ "ShowShieldedPlayerToAll": "Reveal shielded player to all", "RemoveShieldOnFirstDead": "Remove shield on first death", "ShieldedCanUseKillButton": "Shielded player can use ability / kill button", + "PlayerIsShieldedByGame": "Player is protected by the game!", "LegacyNemesis": "Use Legacy Version", "ArsonistKeepsGameGoing": "Arsonist keeps the game going", From 05e7899b8e56b0a8a8e8ff7f1727a01884fe1a79 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:45:48 -0500 Subject: [PATCH 091/778] Set Cooldown And Play Shield Animation --- Patches/PlayerControlPatch.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 1c348be741..e31438a725 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -122,6 +122,8 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) { + killer.SetKillCooldown(5f); + killer.RpcGuardAndKill(target); killer.Notify(Utils.ColorString(Utils.GetRoleColor(killer.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); Logger.Info($"Canceled from ShieldPersonDiedFirst", "FirstDied"); return false; From bbbd55f0c0803694bec9ca691375a892588c76c5 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:33:23 -0500 Subject: [PATCH 092/778] Protect player from shapeshifting abilities --- Patches/PlayerControlPatch.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e31438a725..8abc98a9d1 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -621,6 +621,13 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont logger.Info("Cancel shapeshifting in meeting"); return false; } + if (!(instance.Is(CustomRoles.ShapeshifterTOHE) || instance.Is(CustomRoles.Shapeshifter)) && target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) + { + instance.RpcGuardAndKill(instance); + instance.Notify(Utils.ColorString(Utils.GetRoleColor(instance.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); + logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is protected by the game"); + return false; + } if (Pelican.IsEaten(instance.PlayerId)) { logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); From e3a6eb59df9e6749d833c951c0051db8b1973162 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Tue, 25 Jun 2024 20:42:00 -0500 Subject: [PATCH 093/778] logger info --- Patches/PlayerControlPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 8abc98a9d1..12ea7005cc 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -625,7 +625,7 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont { instance.RpcGuardAndKill(instance); instance.Notify(Utils.ColorString(Utils.GetRoleColor(instance.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); - logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is protected by the game"); + logger.Info($"Cancel shapeshifting because {target.GetRealName()} is protected by the game"); return false; } if (Pelican.IsEaten(instance.PlayerId)) From f93613b01187a93212ce41b14bb6fbd8bd5d97e3 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 29 Jun 2024 12:17:59 -0400 Subject: [PATCH 094/778] remove berserker from swift because its no longer an impostor this line is useless --- Modules/CustomRolesHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 1459d34fb7..502e743730 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -856,7 +856,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Butcher) || pc.Is(CustomRoles.KillingMachine) || pc.Is(CustomRoles.Gangster) - || pc.Is(CustomRoles.Berserker)) || pc.Is(CustomRoles.BountyHunter)) return false; if (!pc.GetCustomRole().IsImpostor()) From 8767bd406497882eca47920517bced53d06fca68 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:56:31 +0000 Subject: [PATCH 095/778] RPC & Murder fix --- Patches/PlayerControlPatch.cs | 49 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 12ea7005cc..c898b24dd1 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -118,17 +118,6 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC return false; } - Logger.Info($"Check: FirstDied", "ShieldPersonDiedFirst"); - - if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) - { - killer.SetKillCooldown(5f); - killer.RpcGuardAndKill(target); - killer.Notify(Utils.ColorString(Utils.GetRoleColor(killer.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); - Logger.Info($"Canceled from ShieldPersonDiedFirst", "FirstDied"); - return false; - } - killer.ResetKillCooldown(); Logger.Info($"Kill Cooldown Resets", "CheckMurder"); @@ -273,6 +262,17 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, var targetRoleClass = target.GetRoleClass(); var targetSubRoles = target.GetCustomSubRoles(); + Logger.Info($"Start", "FirstDied.CheckMurder"); + + if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) + { + killer.SetKillCooldown(5f); + killer.RpcGuardAndKill(target); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(killer.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); + Logger.Info($"Canceled from ShieldPersonDiedFirst", "FirstDied"); + return false; + } + // Madmate Spawn Mode Is First Kill if (Madmate.MadmateSpawnMode.GetInt() == 1 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && target.CanBeMadmate(inGame:true)) { @@ -466,22 +466,25 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player return; //Imagine youtuber is converted } + if (Main.FirstDied == "") + { Main.FirstDied = target.GetClient().GetHashedPuid(); - if (Options.RemoveShieldOnFirstDead.GetBool() && Main.FirstDiedPrevious != "") - { - Main.FirstDiedPrevious = ""; - RPC.SyncAllPlayerNames(); - } + if (Options.RemoveShieldOnFirstDead.GetBool() && Main.FirstDiedPrevious != "") + { + Main.FirstDiedPrevious = ""; + RPC.SyncAllPlayerNames(); + } - // Sync protected player from being killed first info for modded clients - if (PlayerControl.LocalPlayer.OwnedByHost()) - { - var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncShieldPersonDiedFirst, SendOption.None, -1); - writer.Write(Main.FirstDied); - writer.Write(Main.FirstDiedPrevious); - AmongUsClient.Instance.FinishRpcImmediately(writer); + // Sync protected player from being killed first info for modded clients + if (PlayerControl.LocalPlayer.OwnedByHost()) + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncShieldPersonDiedFirst, SendOption.None, -1); + writer.Write(Main.FirstDied); + writer.Write(Main.FirstDiedPrevious); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } } if (Main.AllKillers.ContainsKey(killer.PlayerId)) From ba2f9663175bdc03e52df794d2810fe7d3c7e6fc Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:27:41 -0400 Subject: [PATCH 096/778] h --- Patches/CheckGameEndPatch.cs | 8 ++++---- Roles/Crewmate/Retributionist.cs | 2 +- Roles/Neutral/Baker.cs | 9 +++++++-- Roles/Neutral/Berserker.cs | 11 ++++++----- Roles/Neutral/SoulCollector.cs | 5 +++++ 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index a0f5d02ddb..63f3305fb4 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -98,9 +98,9 @@ public static bool Prefix() break; case CustomWinner.Apocalypse: if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless)) - && !CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) + && !WinnerIds.Contains(pc.PlayerId)) { - CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + WinnerIds.Add(pc.PlayerId); } break; case CustomWinner.Cultist: @@ -345,8 +345,8 @@ public static bool Prefix() } foreach (var pc in Main.AllPlayerControls.Where(x => x.IsNeutralApocalypse() && Main.AllAlivePlayerControls.All(p => p.IsNeutralApocalypse()))) { - if (!CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) - CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + if (!WinnerIds.Contains(pc.PlayerId)) + WinnerIds.Add(pc.PlayerId); //Lovers follow winner if (WinnerTeam is not CustomWinner.Lovers) diff --git a/Roles/Crewmate/Retributionist.cs b/Roles/Crewmate/Retributionist.cs index 6d7025ce46..4d2dd1c3f5 100644 --- a/Roles/Crewmate/Retributionist.cs +++ b/Roles/Crewmate/Retributionist.cs @@ -135,7 +135,7 @@ public static bool RetributionistMsgCheck(PlayerControl pc, string msg, bool isU } else if (target.IsTransformedNeutralApocalypse()) { - pc.ShowInfoMessage(isUI, GetString("GuessImmune")); + pc.ShowInfoMessage(isUI, GetString("ApocalypseImmune")); return true; } else if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 5390559c6e..884599b6fa 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -115,7 +115,7 @@ private static bool AllHasBread(PlayerControl player) return countItem1 >= countItem2; } - public override void OnReportDeadBody(PlayerControl marg, GameData.PlayerInfo iscute) + public override void OnReportDeadBody(PlayerControl marg, NetworkedPlayerInfo iscute) { CanUseAbility = true; } @@ -239,7 +239,7 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } } } - public override void OnReportDeadBody(PlayerControl sylveon, GameData.PlayerInfo iscute) + public override void OnReportDeadBody(PlayerControl sylveon, NetworkedPlayerInfo iscute) { foreach (var pc in Baker.FamineList) { @@ -262,4 +262,9 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para { Baker.OnCheckForEndVoting(deathReason, exileIds); } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); + return true; + } } diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 519db68f85..220b46eb65 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -188,13 +188,14 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(Berserker.WarHasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => Berserker.WarCanVent.GetBool(); - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return false; - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { return CustomRoles.Berserker.GetStaticRoleClass().OnCheckMurderAsKiller(killer, target); } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); + return true; + } } \ No newline at end of file diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 019470eb54..d493dedb48 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -222,4 +222,9 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para { SoulCollector.OnCheckForEndVoting(deathReason, exileIds); } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); + return true; + } } \ No newline at end of file From 6fdf44453d60955b05c88056ec1bfd74b5cd84f4 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:47:25 -0400 Subject: [PATCH 097/778] one stupid } --- Patches/CheckGameEndPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 44af096634..8c0544de83 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -343,7 +343,7 @@ public static bool Prefix() { if (!WinnerIds.Contains(pc.PlayerId)) WinnerIds.Add(pc.PlayerId); - + } if (WinnerTeam is CustomWinner.Youtuber) { var youTuber = Main.AllPlayerControls.FirstOrDefault(x => x.Is(CustomRoles.Youtuber) && WinnerIds.Contains(x.PlayerId)); From 1f2a4c6ad412e0c273b2d884a2a5dd80d5b3191b Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 20 Jul 2024 13:43:17 -0400 Subject: [PATCH 098/778] fixed lawyer, snitch, added guesser mode --- Modules/GuessManager.cs | 2 ++ Modules/OptionHolder.cs | 1 + Modules/Utils.cs | 3 +++ Patches/MeetingHudPatch.cs | 3 +++ Roles/Crewmate/Snitch.cs | 2 +- Roles/Neutral/Baker.cs | 8 ++++++-- Roles/Neutral/Berserker.cs | 8 ++++++-- Roles/Neutral/Lawyer.cs | 4 ++-- Roles/Neutral/SoulCollector.cs | 8 ++++++-- 9 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 84e0b82a67..710ca8124d 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -575,6 +575,8 @@ public static void Postfix(MeetingHud __instance) if (PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.GetCustomRole().IsNK() && Options.NeutralKillersCanGuess.GetBool()) CreateGuesserButton(__instance); + if (PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.GetCustomRole().IsNA() && Options.NeutralApocalypseCanGuess.GetBool()) + CreateGuesserButton(__instance); if (PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.GetCustomRole().IsNonNK() && Options.PassiveNeutralsCanGuess.GetBool()) CreateGuesserButton(__instance); else if (PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.Doomsayer && !Options.PassiveNeutralsCanGuess.GetBool() && !Doomsayer.CheckCantGuess) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index a11309c106..6f1c91da79 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -459,6 +459,7 @@ private enum RatesZeroOne public static OptionItem CrewmatesCanGuess; public static OptionItem ImpostorsCanGuess; public static OptionItem NeutralKillersCanGuess; + public static OptionItem NeutralApocalypseCanGuess; public static OptionItem PassiveNeutralsCanGuess; public static OptionItem CanGuessAddons; public static OptionItem ImpCanGuessImp; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 28cfb4f22f..dbda9b4c50 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2070,6 +2070,9 @@ static int GetInfoSize(string RoleInfo) if (Options.NeutralKillersCanGuess.GetBool() && seer.GetCustomRole().IsNK()) TargetPlayerName = GetTragetId; + if (Options.NeutralApocalypseCanGuess.GetBool() && seer.GetCustomRole().IsNA()) + TargetPlayerName = GetTragetId; + if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) TargetPlayerName = GetTragetId; } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 6a78279efc..3934b25e8b 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1095,6 +1095,9 @@ public static void Postfix(MeetingHud __instance) if (Options.NeutralKillersCanGuess.GetBool() && seer.GetCustomRole().IsNK()) if (!seer.Data.IsDead && !target.Data.IsDead) pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + if (Options.NeutralApocalypseCanGuess.GetBool() && seer.GetCustomRole().IsNA()) + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) if (!seer.Data.IsDead && !target.Data.IsDead) pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index ef0b979a20..a5c98ab7b9 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -94,7 +94,7 @@ private static bool GetExpose(PlayerControl pc) } private static bool IsSnitchTarget(PlayerControl target) - => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate)); + => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)|| (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate)); private void CheckTask(PlayerControl snitch) { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 884599b6fa..5ad65d463d 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -264,7 +264,11 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { - guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); - return true; + if (TransformedNeutralApocalypseCanBeGuessed.GetBool()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); + return true; + } + return false; } } diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 220b46eb65..48a1852429 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -195,7 +195,11 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { - guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); - return true; + if (TransformedNeutralApocalypseCanBeGuessed.GetBool()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); + return true; + } + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index 26cdece7b7..7b36657618 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -79,11 +79,11 @@ public override void Add(byte playerId) { if (playerId == target.PlayerId) continue; else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; - else if (!CanTargetNeutralApoc.GetBool() && target.GetCustomRole().IsNA()) continue; + else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; else if (!CanTargetCrewmate.GetBool() && target.Is(Custom_Team.Crewmate)) continue; else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; - else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester)) continue; + else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester) && !target.IsNeutralApocalypse()) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index d493dedb48..6f3cf9958f 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -224,7 +224,11 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { - guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); - return true; + if (TransformedNeutralApocalypseCanBeGuessed.GetBool()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); + return true; + } + return false; } } \ No newline at end of file From e6dcd770bf70f3a63ea2e4028f0dc5297ccd88c5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 20 Jul 2024 19:10:24 -0400 Subject: [PATCH 099/778] Guesser Mode NA's + BTOS2 Baker --- Modules/OptionHolder.cs | 2 + Resources/Lang/en_US.json | 8 ++- Roles/Neutral/Baker.cs | 119 +++++++++++++++++++++++++++++++++++--- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 6f1c91da79..4c063abf09 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1770,6 +1770,8 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(GuesserMode); NeutralKillersCanGuess = BooleanOptionItem.Create(60683, "NeutralKillersCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); + NeutralApocalypseCanGuess = BooleanOptionItem.Create(60690, "NeutralApocalypseCanGuess", false, TabGroup.ModifierSettings, false) + .SetParent(GuesserMode); PassiveNeutralsCanGuess = BooleanOptionItem.Create(60684, "PassiveNeutralsCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); CanGuessAddons = BooleanOptionItem.Create(60685, "CanGuessAddons", true, TabGroup.ModifierSettings, false) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 56d846505f..9df0105d13 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -62,6 +62,7 @@ "CrewmatesCanGuess": "Crewmates can guess", "ImpostorsCanGuess": "Impostors can guess", "NeutralKillersCanGuess": "Neutral Killers can guess", + "NeutralApocalypseCanGuess": "Neutral Apocalypse can guess", "PassiveNeutralsCanGuess": "Passive Neutrals can guess", "CanGuessAddons": "Can Guess Add-ons", @@ -859,7 +860,7 @@ "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone at the meeting after you transform.", "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.", + "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after meeting)", "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", @@ -2682,7 +2683,12 @@ "BakerBreadNeededToTransform": "Required number of bread to become Famine", "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", "BakerKillButtonText": "Bread", + "BakerRevealBread": "Reveal", + "BakerRoleblockBread": "Roleblock", + "BakerBarrierBread": "Barrier", + "BakerCurrentBread": "Current Bread: ", "BakerCanVent": "Baker can Vent", + "BakerBreadGivesEffects": "Bread gives additional effects", "FamineKillButtonText": "Starve", "FamineStarveCooldown": "Famine starve cooldown", "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 5ad65d463d..88ee88cf01 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using Hazel; using System.Linq; +using System.Text; using TOHE.Roles.Core; using TOHE.Roles.Impostor; using static TOHE.Options; @@ -25,9 +26,12 @@ internal class Baker : RoleBase private static OptionItem BreadNeededToTransform; public static OptionItem FamineStarveCooldown; - public static OptionItem BakerCanVent; + private static OptionItem BTOS2Baker; + private static byte BreadID = 0; public static readonly Dictionary> BreadList = []; + public static readonly Dictionary> RevealList = []; + public static readonly Dictionary> BarrierList = []; public static readonly Dictionary> FamineList = []; private static bool CanUseAbility; public static bool StarvedNonBreaded; @@ -37,14 +41,16 @@ public override void SetupCustomOption() SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Baker, 1, zeroOne: false); BreadNeededToTransform = IntegerOptionItem.Create(Id + 10, "BakerBreadNeededToTransform", new(1, 5, 1), 3, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]) .SetValueFormat(OptionFormat.Times); - FamineStarveCooldown = FloatOptionItem.Create(Id + 11, "FamineStarveCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Baker]) + FamineStarveCooldown = FloatOptionItem.Create(Id + 11, "FamineStarveCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]) .SetValueFormat(OptionFormat.Seconds); - BakerCanVent = BooleanOptionItem.Create(Id + 13, "BakerCanVent", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]); + BTOS2Baker = BooleanOptionItem.Create(Id + 12, "BakerBreadGivesEffects", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Baker]); } public override void Init() { playerIdList.Clear(); BreadList.Clear(); + RevealList.Clear(); + BarrierList.Clear(); FamineList.Clear(); CanUseAbility = false; StarvedNonBreaded = false; @@ -53,6 +59,8 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); BreadList[playerId] = []; + RevealList[playerId] = []; + BarrierList[playerId] = []; FamineList[playerId] = []; CanUseAbility = true; StarvedNonBreaded = false; @@ -88,9 +96,32 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override string GetProgressText(byte playerId, bool comms) => ColorString(GetRoleColor(CustomRoles.Baker).ShadeColor(0.25f), $"({BreadedPlayerCount(playerId).Item1}/{BreadedPlayerCount(playerId).Item2})"); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) - => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + { + if (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()) return true; + // i swear this isn't consigliere's code i swear + var revealed = false; + RevealList.Do(x => + { + if (x.Value != null && seer.PlayerId == x.Key && x.Value.Contains(target.PlayerId) && GetPlayerById(x.Key).IsAlive()) + revealed = true; + }); + return revealed; + } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => HasBread(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Baker), "●") : ""; + { + StringBuilder sb = new StringBuilder(); + if (BarrierList[seer.PlayerId].Contains(seen.PlayerId)) + { + sb.Append(ColorString(GetRoleColor(CustomRoles.Baker), "●") + ColorString(GetRoleColor(CustomRoles.Medic), "✚")); + return sb.ToString(); + } + else if (HasBread(seer.PlayerId, seen.PlayerId)) + { + sb.Append(ColorString(GetRoleColor(CustomRoles.Baker), "●")); + return sb.ToString(); + } + return string.Empty; + } public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (HasBread(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) @@ -100,7 +131,7 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b return string.Empty; } public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); - public override bool CanUseImpostorVentButton(PlayerControl pc) => BakerCanVent.GetBool(); + public override bool CanUseImpostorVentButton(PlayerControl pc) => true; public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Main.AllPlayerKillCooldown[id]; public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("BakerKillButtonText")); public static bool HasBread(byte pc, byte target) @@ -129,6 +160,45 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } } } + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (BTOS2Baker.GetBool()) { + switch (BreadID) // 0 = Reveal, 1 = Roleblock, 2 = Barrier + { + case 0: // Switch to Roleblock + BreadID = 1; + break; + case 1: // Switch to Barrier + BreadID = 2; + break; + case 2: // Switch to Reveal + BreadID = 0; + break; + } + } + } + public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + { + if (seer == null || !seer.IsAlive() || isForMeeting || !isForHud) return string.Empty; + if (!BTOS2Baker.GetBool()) return string.Empty; + else + { + var sb = new StringBuilder(); + switch (BreadID) + { + case 0: // Reveal + sb.Append(GetString("BakerCurrentBread") + GetString("BakerRevealBread")); + break; + case 1: // Roleblock + sb.Append(GetString("BakerCurrentBread") + GetString("BakerRoleblockBread")); + break; + case 2: // Barrier + sb.Append(GetString("BakerCurrentBread") + GetString("BakerBarrierBread")); + break; + } + return sb.ToString(); + } + } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (!CanUseAbility) @@ -147,9 +217,41 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr killer.Notify(GetString("BakerBreaded")); Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); CanUseAbility = false; + if (BTOS2Baker.GetBool()) { + switch (BreadID) + { + case 0: // Reveal + RevealList[killer.PlayerId].Add(target.PlayerId); + break; + case 1: // Roleblock + target.SetKillCooldownV3(999f); + break; + case 2: // Barrier + BarrierList[killer.PlayerId].Add(target.PlayerId); + break; + } + } } return false; } + public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) + { + var baker = Utils.GetPlayerListByRole(CustomRoles.Baker); + if (killer == null || target == null || baker == null || !baker.Any()) return true; + if (!BarrierList[playerIdList.First()].Contains(target.PlayerId)) return false; + + killer.RpcGuardAndKill(target); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + + NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); + return true; + } + public override void AfterMeetingTasks() + { + BarrierList[playerIdList.First()].Clear(); + } public override void OnFixedUpdate(PlayerControl player) { if (!AllHasBread(player) || player.Is(CustomRoles.Famine)) return; @@ -203,12 +305,11 @@ public override void Add(byte playerId) CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); - public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) - => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => CustomRoles.Baker.GetStaticRoleClass().KnowRoleTarget(seer, target); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Baker.FamineStarveCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(true); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => Baker.BakerCanVent.GetBool(); + public override bool CanUseImpostorVentButton(PlayerControl pc) => true; public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("FamineKillButtonText")); From 03ce69a08717241fa0b6d20bcf934d0d5e918fd3 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:15:40 +0200 Subject: [PATCH 100/778] semi complete now, just have to fix bugs --- Modules/OptionHolder.cs | 2 +- Patches/GameOptionsMenuPatch.cs | 32 +++-- Patches/GameSettingMenuPatch.cs | 174 ++++++++++++++++++++++++++ Resources/Images/PresetBox.png | Bin 0 -> 6896 bytes Resources/Images/SearchIcon.png | Bin 0 -> 5794 bytes Resources/Images/SearchIconActive.png | Bin 0 -> 5853 bytes Resources/Images/SearchIconHover.png | Bin 0 -> 5870 bytes Resources/Lang/en_US.json | 2 + 8 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 Resources/Images/PresetBox.png create mode 100644 Resources/Images/SearchIcon.png create mode 100644 Resources/Images/SearchIconActive.png create mode 100644 Resources/Images/SearchIconHover.png diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 329606c78c..405c26367b 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -607,7 +607,7 @@ private static System.Collections.IEnumerator CoLoadOptions() // Preset Option _ = PresetOptionItem.Create(0, TabGroup.SystemSettings) .SetColor(new Color32(255, 235, 4, byte.MaxValue)) - .SetHeader(true); + .SetHidden(true); // Game Mode GameMode = StringOptionItem.Create(60000, "GameMode", gameModes, 0, TabGroup.ModSettings, false) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 8075b27f6d..babc4e4920 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -52,9 +52,14 @@ private static void InitializePostfix() [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings)), HarmonyPrefix] private static bool CreateSettingsPrefix(GameOptionsMenu __instance) { + + Logger.Info("GameOptionsMenu Create Setgginsssds ().ReceiveClickDown(); }, 0.1f, "Click Edit Button"); - // Change tab to "System Settings" - _ = new LateTask(() => + // Change tab to "Mod Settings" + if (!IsPresset) { - if (!GameStates.IsLobby || GameSettingMenu.Instance == null) return; - GameSettingMenu.Instance.ChangeTab(IsPresset ? 3 : 4, Controller.currentTouchType == Controller.TouchType.Joystick); - }, 0.28f, "Change Tab"); + _ = new LateTask(() => + { + if (!GameStates.IsLobby || GameSettingMenu.Instance == null) return; + GameSettingMenu.Instance.ChangeTab(index, Controller.currentTouchType == Controller.TouchType.Joystick); + }, 0.28f, "Change Tab"); + } } [HarmonyPatch(nameof(GameOptionsMenu.ValueChanged)), HarmonyPrefix] private static bool ValueChangedPrefix(GameOptionsMenu __instance, OptionBehaviour option) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index c489f0313b..2e57be1d4c 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -1,7 +1,9 @@ using System; using TMPro; using UnityEngine; +using UnityEngine.UI; using static TOHE.Translator; +using static UnityEngine.RemoteConfigSettingsHelper; using Object = UnityEngine.Object; namespace TOHE; @@ -94,6 +96,14 @@ public static void StartPostfix(GameSettingMenu __instance) __instance.ControllerSelectable.Add(button); } } + + if (ShouldReveal) + { + HiddenBySearch.Do(x => x.SetHidden(false)); + HiddenBySearch.Clear(); + } + + SetupAdittionalButtons(__instance); } private static void SetDefaultButton(GameSettingMenu __instance) { @@ -123,6 +133,146 @@ private static void SetDefaultButton(GameSettingMenu __instance) __instance.ControllerSelectable = new(); __instance.ControllerSelectable.Add(gameSettingButton); } + public static StringOption PresetBehaviour; + public static StringOption GameModeBehaviour; + public static FreeChatInputField InputField; + public static List HiddenBySearch = []; + public static bool ShouldReveal = false; + + private static void SetupAdittionalButtons(GameSettingMenu __instance) + { + var ParentLeftPanel = __instance.GamePresetsButton.transform.parent; + + var labeltag = GameObject.Find("Privacy (Not Interactive)"); + var preset = Object.Instantiate(labeltag, ParentLeftPanel); + preset.transform.localPosition = new Vector3(-4.6f, -3.75f, -2.0f); + preset.transform.localScale = new Vector3(0.65f, 0.63f, 1f); + var SpriteRenderer = preset.GetComponentInChildren(); + SpriteRenderer.color = Color.white; + //SpriteRenderer.material = null; + SpriteRenderer.sprite = Utils.LoadSprite("TOHE.Resources.Images.PresetBox.png", 55f); + + Color clr = new(-1, -1, -1); + var PLabel = preset.GetComponentInChildren(); + PLabel.DestroyTranslator(); + PLabel.text = GetString($"Preset_{OptionItem.CurrentPreset + 1}"); + //PLabel.font = PLuLabel.font; + PLabel.fontSizeMax = 2.45f; PLabel.fontSizeMin = 2.45f; + + var TempMinus = GameObject.Find("MinusButton").gameObject; + var GMinus = GameObject.Instantiate(__instance.GamePresetsButton.gameObject, preset.transform); + GMinus.gameObject.SetActive(true); + GMinus.transform.localScale = new Vector3(0.08f, 0.4f, 1f); + + + var MLabel = GMinus.transform.Find("FontPlacer/Text_TMP").GetComponent(); + MLabel.alignment = TextAlignmentOptions.Center; + MLabel.DestroyTranslator(); + MLabel.text = "-"; + MLabel.transform.localPosition = new Vector3(MLabel.transform.localPosition.x, MLabel.transform.localPosition.y + 0.26f, MLabel.transform.localPosition.z); + MLabel.color = new Color(255f, 255f, 255f); + MLabel.SetFaceColor(new Color(255f, 255f, 255f)); + MLabel.transform.localScale = new Vector3(12f, 4f, 1f); + + + var Minus = GMinus.GetComponent(); + Minus.OnClick.RemoveAllListeners(); + Minus.OnClick.AddListener( + (Action)(() => { + if (PresetBehaviour == null) __instance.ChangeTab(3, false); + StringOption PresetBeh = PresetBehaviour; + PresetBeh.Decrease(); + })); + Minus.activeTextColor = new Color(255f, 255f, 255f); + Minus.inactiveTextColor = new Color(255f, 255f, 255f); + Minus.disabledTextColor = new Color(255f, 255f, 255f); + Minus.selectedTextColor = new Color(255f, 255f, 255f); + + Minus.transform.localPosition = new Vector3(0.01f, 1.87f, 1f); + Minus.inactiveSprites.GetComponent().sprite = TempMinus.GetComponentInChildren().sprite; + Minus.activeSprites.GetComponent().sprite = TempMinus.GetComponentInChildren().sprite; + Minus.selectedSprites.GetComponent().sprite = TempMinus.GetComponentInChildren().sprite; + + Minus.inactiveSprites.GetComponent().color = new Color32(55, 59, 60, 255); + Minus.activeSprites.GetComponent().color = new Color32(61, 62, 63, 255); + Minus.selectedSprites.GetComponent().color = new Color32(55, 59, 60, 255); + + + + var PlusFab = GameObject.Instantiate(GMinus, preset.transform); + var PLuLabel = PlusFab.transform.Find("FontPlacer/Text_TMP").GetComponent(); + PLuLabel.alignment = TextAlignmentOptions.Center; + PLuLabel.DestroyTranslator(); + PLuLabel.text = "+"; + PLuLabel.color = new Color(255f, 255f, 255f); + PLuLabel.transform.localPosition = new Vector3(PLuLabel.transform.localPosition.x, PLuLabel.transform.localPosition.y + 0.26f, PLuLabel.transform.localPosition.z); + PLuLabel.transform.localScale = new Vector3(12f, 4f, 1f); + + var plus = PlusFab.GetComponent(); + plus.OnClick.RemoveAllListeners(); + plus.OnClick.AddListener( + (Action)(() => { + if (PresetBehaviour == null) __instance.ChangeTab(3, false); + StringOption PresetBeh = PresetBehaviour; + + PresetBeh.Increase(); + })); + plus.activeTextColor = new Color(255f, 255f, 255f); + plus.inactiveTextColor = new Color(255f, 255f, 255f); + plus.disabledTextColor = new Color(255f, 255f, 255f); + plus.selectedTextColor = new Color(255f, 255f, 255f); + + + plus.transform.localPosition = new Vector3(1.62f, 1.87f, 1f); + + var GameSettingsLabel = __instance.GameSettingsButton.transform.parent.parent.FindChild("GameSettingsLabel").GetComponent(); + GameSettingsLabel.DestroyTranslator(); + GameSettingsLabel.text = GetString($"{Options.CurrentGameMode}"); + + var FreeChatField = DestroyableSingleton.Instance.freeChatField; + var TextField = GameObject.Instantiate(FreeChatField, ParentLeftPanel.parent); + TextField.transform.localScale = new Vector3(0.3f, 0.59f, 1); + TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); + InputField = TextField; + TextField.textArea.outputText.transform.localScale = new Vector3(3f, 2f, 1f); + var button = TextField.transform.FindChild("ChatSendButton"); + + Object.Destroy(button.FindChild("Normal").FindChild("Icon").GetComponent()); + Object.Destroy(button.FindChild("Hover").FindChild("Icon").GetComponent()); + Object.Destroy(button.FindChild("Disabled").FindChild("Icon").GetComponent()); + Object.Destroy(button.transform.FindChild("Text").GetComponent()); + + button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconActive.png", 100f); + button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconHover.png", 100f); + button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIcon.png", 100f); + + PassiveButton passiveButton = button.GetComponent(); + + passiveButton.OnClick = new(); + passiveButton.OnClick.AddListener( + (Action)(() => { + SearchForOptions(__instance, TextField); + })); + + + static void SearchForOptions(GameSettingMenu __instance, FreeChatInputField textarea) + { + if (ModGameOptionsMenu.TabIndex < 3) return; + + HiddenBySearch.Do(x => x.SetHidden(false)); + string text = textarea.textArea.text.Trim().ToLower(); + if (text == "loonie") text = "dictator"; + Logger.Info($"Current text: {text}", "SearchBar Text "); + var Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) + && !GetString($"{x.Name}").ToLower().StartsWith(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); + HiddenBySearch = Result; + Result.Do(x => x.SetHidden(true)); + + ShouldReveal = false; + GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); + _ = new LateTask(() => ShouldReveal = true, 0.28f); + } + } [HarmonyPatch(nameof(GameSettingMenu.ChangeTab)), HarmonyPrefix] public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [HarmonyArgument(1)] bool previewOnly) @@ -206,6 +356,7 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ [HarmonyPatch(nameof(GameSettingMenu.OnEnable)), HarmonyPrefix] private static bool OnEnablePrefix(GameSettingMenu __instance) { + if (TemplateGameOptionsMenu == null) { TemplateGameOptionsMenu = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); @@ -240,6 +391,29 @@ private static void ClosePostfix(GameSettingMenu __instance) ModSettingsTabs = []; } } +[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] +public static class FixInputChatField +{ + public static bool Prefix(FreeChatInputField __instance) + { + if (GameSettingMenuPatch.InputField != null && __instance == GameSettingMenuPatch.InputField) + { + Vector2 size = __instance.Background.size; + size.y = Math.Max(0.62f, __instance.textArea.TextHeight + 0.2f); + __instance.Background.size = size; + int length = __instance.textArea.text.Length; + ((TMP_Text)__instance.charCountText).text = string.Format("{0}/100", (object)length); + if (length < 75) + ((Graphic)__instance.charCountText).color = Color.black; + else if (length < 100) + ((Graphic)__instance.charCountText).color = new Color(1f, 1f, 0.0f, 1f); + else + ((Graphic)__instance.charCountText).color = Color.red; + return false; + } + return true; + } +} [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] public class RpcSyncSettingsPatch diff --git a/Resources/Images/PresetBox.png b/Resources/Images/PresetBox.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e74402321eff3500505136266d117d7c14019f GIT binary patch literal 6896 zcmd5=c{J4jyVtiu*7^!#h*V_D5+P&_6;U+yY!fE?WFKZIOVLbHS!!%WlnITUVTeqG zjD08jzAs~U=QFzZobNsN-1EElk9)3jocEc}`+1)C^E~g@^Ljt;cjO%dT~;PA6CE8L z>&+Y2jp*o(gX!pwurSh7zu?B1h}7YP=M8f&Iy&aRf&U||1E^g(I`D^^*ER0>C(aMp zcvwv)2`oKLd@o{jJ~Hq7_s{(|>K5p4Cfz5Pon<87|4!hp$PR2HXcr)lBN)UhpZF|1 z&p{JIdeuF8pJ$^LL+q-)j`iNL{ei#m#-%o0+(?P&mpgH( zr+`u9HklC^-w4Bw0pl$Wxxaw%;|cH)>JZX;3ybkHQ@scmKU9(8z;+r{GiudQTP=b z>)qALx`m-dU+N#R{sf_CqJDFBVQ^uhv&$wo{G>H_1#YUvSV)ga>Rq6P`g+`|d!PxK ziWNYeT*%I56F+9fB$(K=^Vad7P`EsjsB^gc`dAJpV*JW?e67VF^j!R}(Q~x7iR+(E{?G6l zBHHd;`07kq+jJr~%}&*63q@av@%P6MQ6KRC67~OT$%dxF&v)SZXWX|<0JPZtWO+iu zeSUH2a8IVwe5iExzh(ga_j3yaZ=Mnb|LlByp8kK2g@18pj57%Mj}M_F?NG)$E6N^p{t(O|m-8h4Fzv%X@cz58lXtzti}H^Buqqul z>Vk4O+DU_xQgzf5F0p`6_r+S68NR~#iP^o-|NVk8SK>SMZ5+~fbdE4 z^S9j1EXj&}?p;1Kwnz}s_Wj`9)#YUs+lToUZb9?jwy~+PlWVm@K<-z!3T?qal=API8bwBm{LPS+A{7QWNF`wnfq9F!l>F1G3=5NMBx$$!>$R z9G3cMj=DBf!EJU;;S~c$xv#G@7t%p9JkI$UCh=-$+|$SN=1S`F;7ojJZqlE*kL~7- z@eVx-<1zK2BxRc~V?Ux4GS-zqaesMfTVdJ3Y>@;C}&4qpPSLGPa*|l1p4-f)5<*ybxxJElf)r*7E0$p&W zIoNAuFIS3yNogNP=0@0Dj%hwziCQrGWRlBg)q=W29V{(jzn7v;uL>o%_(KoW<^*0P z6lBSGbbmt9N{KBKR^;n9-|)s``>n<(R&?=IpaB$lZH#8NGwOwKBjNki%uEK1dEEVg zN5=X*0peiGi9_Ofe_C|pxmz21*}xmA?YEp5L5Cghpd}SMXv}&apkGyNq08-6iD|+p ze;(>LE;e9Cmm?95zMYA*gIPpG(meTLvDd--KpalgW?3ir+nRzH4#uL*RSio4I+hU4 zs23OX%NZwTULTC$Q)L?Rx=oRnItEsX42!u|;{9oLAlN8*ni;&9EtCyYQH0GA5?5XX z^kzb6E~?99*BUI$a+u0SlxskHuVMnYsuS91yIEQ*ammp+AbV1TUSdl5Jg9P24pLy4 zJ5VOGrUao;=}W_%%}7lTu|>()MEPx=5oj`JV8(r>*XL!fWYQXu7137tfdMmi%K`VF z3Ons{OXjpSY`%nLFHAh{D&UI#SS|!4LO96H=u(2S}@I>N@aJxx*;}SO*~0{%cCj_)2~1(jVYR0)t?D$q?P=lGbao?*fYrHQVc#R zw^5P2B0BPIKwCGaVLo+nSQFht zdNYhFHD}>nwIIX>^{qWNAAW2Y=g@;-9LBsclyw!=Z+XB4gq-oMzuvgikVpABht%T} zHDxKQ*Hi1`t63~RyY0}ZCrF%Yg46u3{6~9f?H*F@SR#UaTq~3$(O^&ZmmN1|on`mn zmN*JD6(Kj}{T_)@rp2-x%T?x{ls_CKvx|)k_BwaZxbtsbEHDIEMx8M`joEHvN^?Qs zuErH8%#{p6Fzjzu9SlmoYz1&qmHtlIM~PBze-VDX1MT5p?hEbKE~OR=Lq+Tuew#nm zQ(_t^>Dj67j8)Z8;;IWKjRvW`HXR*FGsc;;Kth!4^<+Z$ zW{n1!pXd1gf(XAgW#M`1mMWQdmjMtHuI-;5@4r~OHOv4>R5FpBSa>7}mLstbitOt* z%YVtjbK<~G&sBv1S#)mqG_PO6*eT{MY8zw%@6=2?%`D_^9+ypRz9e>UFn~-tKw6}Y zdSg+K6MzDSbM{Bq!%OIdJ-9ZW zQigE$NA%Yal~dBi)zkOm9N>^OOVJbruJ4ffs@k8=x8kQHqnRu-Jop_=8|)g6y_O6= z5g>mD=M1#xw$*3M_fa`VNtfC<3-xv5qfBHYYUlwvIU!h%Oi|(;8ShTayKzj7w&goiA-KrtOPGZHSDcO+f$lQmC~yR9lj71Im6z zZsTdG%FNT4UC1`4H8wuYQ~=LbFY0TqyG zMJf8~k7KBn1!X&2U-nEo{D}TEI@gj*QvynBT0F%%^j#yr2PvKrrxkJk0eS4D;2^3$o8BA``z!|5;)Gyi9$1y`w><9Rt1peE zL2xF{Z%eP==5Nak@eSuVhuP2WF%qyj(<|0`>j6iL)piPs%XZEN?DWw5kd(1ic?CUf z+gPoNq!dHc#$C+ggf7MFC;hswzZ-C^rnT=JF9L|ow%r~TtMH(FAA5lOt@!QOpaI@t z`{vD1(*EzdVq=u!RON5Q&6TqO4F)tqd}Cjyco51RF$ljdII5F%sm(C!!%)eqEK=+c zlCP)?%U~_*WKFx-v0i56d5YQNy;}#mUzhjg)s;@_A&l~%@P1ZPAdcBxYF}o?9_&|M z&IMRIr1y&S1GE><(3hDS+^sRm^#uN{frW4y^WTZ5k3h#ZI`rqhO2ylt zwh){tNtxjYD!b$izXL*fX)!yc*``Ty5ezfM5$hWV2dy0h2B*F4fBQ?WPf8|RF{$nJ z1*@%<&95JD7cB9GkL zIaUY$Q$AjL%{|wF-lpnCfuY1W6Hk}>0AuA&Q|#R^bPFzxkw>dT0~+UGs>$d9S?b^l1@dSD}Uw9t$cFl(m5q(QVJ4|K&O=N805Gd<|O^Jf}X&&z?|ZQ&9gE! zQAeO(9Qk4t@+1g~Gg5?B2k9LjrKE1=eIw2k^$xu2AvL_h#W4b(}8odT@P^x@N2rG5l_U0sFl?knq~%mBB50p{_zPUH}D(UqBKY z6Ec#)!LT||O)c(VH5h&QK{;1^C3?ou5NuBF9&%r`fTRdC7_Ooo20ZnmQN6lrY;yC} zXqlp$!E4P2F88n>wbUL22{q)!e0)b$nq5nj?7bLQ-T%2w3kOQGq+T=KgEyu{*sTNWK+a5H8#ck9I~|h(YWe-lysN#) zubf51&B|(8TSJ6qT;raIuT=9M*8Akd4~5;T1I;jkYtp_h8!-+GWNq)ts;LN|*5cKg zkw_LEXg5ep7g&+;I1aR!gX96KD9tsN?KTsS)_o1=r;>1`?ctP4zT|^80q$*CcA^C< z`=b0vAg%a>wX;BJv9kh{IL>IZG)>YSFS5UC*vT5wYskQPlPjbd(mCHU?C`f4x9oOc z5n~Y(Y0@o{3IvTuEWh>}Izj0)(cSw1=Dr-E6iQkWaV{g!c2H2G`>ykqxodm@S0w-gkt~0a%Dq4g&K8@O$(t`_ z=ihai$-AO2wd0evhe>DVX^Dbn3|u`mVIalL1air-NAIgPzq(r#5-a!RvAQIZ;& z1uZeAxWq2SrL@Z30L9gs)sL!Lzk#A9>pyjiVQczi-#;aVEVbx5%BRB-O6u<7vl%JP zT8usC%JLV0k`U4}%k)WuYupykld6wE`$dpDl$3V!TF^H3qfHa_wIzui4#F^lYo5i` zuzXLIp?@LD$Oj;0=2U3y#xlqILjSotluE*e!{+WDrP`#}a#CT_gmG9i&Y>{YD6XY0 zRYrx0*M?PamvXm%cJuA{(x}ZY!~+Fo8j`@2`gO%Q15QM}d0VM?5(k1}*OH`6X7=A% zH{{J34Y1$UfrgT#KiqCV3g__o;;J&IGvgH5flHjJd=#nt77Zw%%?I zsdKHxs^u1wdGU|&Nk2B_Fw|{-RzsTFQrpV++{jeZ>%6s>PJL#oD4I!H$RGo>|C@(+ zf_v0HUaLrFvAAPk?l_ti_WH#jqo5UER{+{jmWx_xg05e(Vz+O~c%vVW{&MWx0j{N0 za(ZVZBPLv5^LI}%pyRPi(xMn+a!LEp^}SY+#lSyQ2fsjx(tBVZxW2HrV>dawlXZrg z^N`3c%c;EX6Zy={(bcuD+*%AR2tMcgcb7uTCZ*Y|W}BhqyQ zw}#PCfsI3)pJI<&@1oQ!beiOLSZo4`lQaKvC-GVt$Z>Kq@CcNJ8%L#jDqlRM-XL!W zh;!sa5D9o@uvU{DG#2}r-9>>uTE@G7fR#WYM5C@O0+|Ehtv~rcP;HcyP~Bdy3(C9t zyym;MRIvX+h4687OW)p0__?B6?6Psx1YUJTzeq7(XmVG9PXzk(tHyUD&WQ8-3An3m z@BK2!J$HExcRoCZYE#;pUSse^V-W)KtcE4|q455NZ77+9}G_kh(EbpHDbXVqd zFdoket|_*I_LElyW)Kl@M!wZ&keD4-u?qq_ew0e(Hg#KSS4-B2Jnb*9QKiUOW1rkOMH)xQQV0=^FN;0mykfdhM-LFCPj~O*W6N|3_NO_z`SM*2sQ+nsVsA>Hf8LSkwtUZB ze=}7|n&-?MvmI*L>}WE8gVdgdApGC%2m7xx=+Sn)^0y{s;D>sAn*Oq3o;gRNR}(-= z$=lzUhfBHl3vbcb2I*T=9C&1uO3&3M6oTGE-g720*3?Jcyu}4L2ajBYhl0_MugmeQ z{)w4U^BwTW#>mBM0$lNN60ZQx$uLT+Eb(2{pOLk)PuT2mC(3BvNU4S}~c ztI3T%jIdOJUzU(enkh6Bu(4*FFFh|NKr5fqlTxG*{Cj5W4MJzGeR*r5u5)taA;ZU_ ze4x+?d4AHeY(s^bP!Mp_APvkkHP?jhVBDhlzs`LO`Aj`@6AFxg+aVd$o6h|1T`D}a zZQ6=Oa2G0@OnYC52p|jzBRjZ_8{3a#hWoPH;j(0E`e_&98NYl1Y2q|NG4V4Cm|^?5 zz2u9XIob)&*2G7;o}mO|iLNHC?#<}r(^{1b0KX-hWmnM;^^s&n^0c@0{K2?V7y2Sl z&Z*}-sAT8i|81HPOquq`Abv^$-d}9bodAhnfCWXNeuHql!5t+}0e4eHu<5$$WdD%; zU6S4*@8j2eE1xV!<>n%ukobgUx*RIWeJjisH!FxRk?k`!?el?%_p^gp#tmxDDyyNm#L z(X|pvrEIMzD6AIQEC%_0R9rC3izDC$bta^xw3D4%DsZyt0*vo5DsWwV2X#*waAAyN zQp&nWqipLK=E@X9bm-Eu1G5A5I~~6)ESl)LKeBmJPX4-DkjfA{|7f$(foVrHb+W3! zA15G5oaPl!dqR?-rj7nRw3}NUFsL83j;ex{Hk2?4?gj@4pqM=e*)9PFZ)zJ{&(XAf F@^9ABP~`vs literal 0 HcmV?d00001 diff --git a/Resources/Images/SearchIcon.png b/Resources/Images/SearchIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb80263c2dd57ec8f3a0aac752af023c893f0c52 GIT binary patch literal 5794 zcmV;T7G3FyP) z`fqpd#iV1QQe$pQtkmiAgl0N=Z<#%A11{Y12zh(4*hPTHdw8RwkVswJkQqhr{Qj#67&S-PtP zcOFESH!SKoxGJzHcT|b;X!)Doh*g9=H9ghG1oWZ8;Tloy(OrEb31t@HOd`4%*Hi`d$pZd(I9!CDvX6+i5Yf(J-&(ws zO@z~k=t3e|NJM8Ts88NhddjPb=yM|4L_}MOXm7c1yIhn-gflT@Tcn^aq2S*BW?U@B z4H&W-$|<@Cc_niR5nZC7E}HluE*_??BchLEZxGECRfN2Pcoh-NS5Ozt%)`|SG46`J zK{QfC5#B^ZOBK|kNX*CeIuX4~MEhcI5KRUa#?V3$ zM98b0R}j&U6x5{*bl_vgi&<~jN@{qti}1%pv>Y#vku3{xJ#D>WShR|8G``jg71X8d ztUweIi}6J4jS&KkBD?^-Uyp*i#KHm$Pg#iDVs8v-I+fMudLmk70t~`rwEguF-g{8A z^n^X={Vq^Ym)NVB9e@p2`xent@;NHg$!MsK}7dg z9iuXGBBTiaiimz@4^}-tC8FD6Z-hJvF(;gYW%1bo$~Ic#a-7oOdE$O+!)%f63Hd?Z zWdbZD{LJo-jvXKJIG49HuAya;or^?;>hBA%@lyU4OYTTuZ#2^uUN<`aX zmbBYBcRWS?k5~P-?#t=D9|xNb4iP(_h|VRV(+b#DGx4!~NI_i+NnRr4D2ryYI@ZOJsU1P;7HS63Ld2#__PG2rioz;+s2xlY8q6iGZr;~kG z`kZrLhpMx4j?ePF&zJX}&r{AtSl@*OIn;bT42bP0R7$eW$}|P>hT2y`x!2oBMC+Y% zs^>D^d!9Qk#^^$O@5poHU&9$ZbIO{PDQgklr#u?wIIREPbk6lFs83?N_g(PXS%QTg zt?)eh!940_snMEqqhbN_F!RrB`Iq-xFE3Em9HX$Rv+*vxsGu%|AS)5_ zyyaS@H@umM);Q-5E2vA`VD9*$_kKM*SQjXqGhU1N;a?QgMLk)FunVUmw87E-tDSQn zmGQyhqw=KpeiOC`v~8Mmo&FW`Z=(TOM9i$fMzj`q#q&*m0m>@CYMgUC2m3e7b%*rS z?Av!@xq^DAr`?{ASM4rUbdyJj=!qD_R}HpSp7Y-GKIhL7Ql^1RF)V#cNqtn(PER;p z*_UodEWO`IpjZ5NtWavY^Iw90#O7j5Z66<&~^MS`l8P zAUo~}2+&eMC&W3o1M|etLUcGGVpNej7H_J>p|Hv>b{l=IZ@@(ui=GM1>mgCz}Jd58Cjlz|l3<8&;82 zgjc{}D-;`?bFT+1zf?HqIH-MXzO3Ud}i0JQZ}h{u)2#h%lmkq8$E)}tCWW7-?2>}YY$?c=r&bpKK3 z#&0CT5WV0b8-@nuwLq(-n%o)08A*g^1+p)EKBNnF>^SFmIQo2mW|$Rl0Wdfti7>?Q zU>9uQjet1kK7cQbG_zuT?jwmXL@&s(p_<&nVrET%d%rN&_8v)u(kach!=%VifOGDX z0Et~Rr%%Y0dLo<|An}MT#^^)(nT&~bNLo*X(hc!x%r=Eu!MRQ8xuJ;=NH@ef;qr{+ zNIx4BA&_i{Beo){nk_^mnMlV(i2cEFg5__tV@i6#vw26bf@5RcFRT@z^p|LpaCt_0 z-W9gOh^{vbDZ-u3x&4CW87<(PJ1D$BV;eu4Hl=e|<}!^a!nXU4J^Xb1RKRpPZS*IoYZk9rc1b! z2tp^)VvAt82MuF5osxOVz4xQ-85A4ued&89X*z|6h>}BJBOkg1+jSs@bp#gSH^Su^ zhEXF5$%(df-a{jMY)dO+I&I&_NQsFsV@!lbT1o*SbPAg1NuyySTOmLll}V;y3ras3Ki9BrPOo3kKkS9+MwY_0ghF1k0Tx zSeB6uvjy96M@-KwlnCbvmStqa#e(f9`7Q=0ln7^d@1w9dF9X=ym7GFED?*Ob`%YsRF=^`^8{VbB;P|7)Y?r*et|Ak))vOdNIt8mdO->-Lf)#dfNte@?|X!|CQO9F z<^NE)JR>~|g$;P47vVnP{h39!ajzFVo67_%D7jy)hI$r+l3yZ@%6H1z#Q2VR!fpuB z^@5~8gv=GfQt$mRZIhHQW@SB3qTVMed9w!VppH zd|>nk0m?P1@ZMh%U@l0p4n?Vf2=@i*4R5g=hT%Zl-x8n#J7U})tk5V`*?Y28`4&D| zaczKdjZ-WQWYF9jbNe?E;by^lRCAU0ep;Ziqs4pAp0HO3y8oyS@z&BvgeL>_h8!w& zJ3LJc0npaB6Va#u?d^pPsMXQzMc)vp!e`swp(s}c^!*9-j;W!U2)73`VZF|KA7Yg* zD!li+Dt$wsduBtYZIL$p|C40M_oh%?YT>%w;k}o#U5^g$J-esfi77w`{v7)=poAja zD446FmNSUxmv-DKdoT~Y18p3FV>T2$MbgEOSM#n&12~**MZogQ$}%FlDB!)lR_^9a zPErxR6*xl4Ztyh;T#Tq>WtHJ8{mUgg0tv}~6H#BG8?z1O&!0c+AE*5y{v}WpO*LbPXr=Xt$w3d8hX6;zjV-AU zjgMJ?BhkvRjW?Y9-(258-we~X# zZl2>D@4`x-Q3vn+kBR7R^jKNKf?GsH-T0kjAjn2Se^xjxdtqobVD~NT-(3>;M)@PM z+Rz z+~q`cpZ9)(g1Q6+ZLlgQ0Zv4ZS1Z5@-58qIe%v*^E9z)RgnSZuwW1s1#fM*c@1+fj z)Bv-rf66JL=VbJN&4#uUVdz}(3wp+ep^a(x{MdT2tN*s}-gjY`&Em`Z zpz}p-*j*f~W0%Q2_5jupj2hC$jy8jbfcIlQpqYLgR$lL%JD|Cu-g{o;Sd2V@V{pNQSwuwLwF)*>VfWA4lPm*&I}hSF^BxJAoqDpot^Vo&Zu zp~a0(%;DNeVa@{lvccaEB7i$u%X=vi6296$&Eps6NIw=6IJ#r2^xP4{P2S_Y7+2oC zUM>7~oY}^|=ivTN6Z{;1=pH1ZU2T+ASR_Tr+*n{Y?LoXyA0tP?r_Q-d(};WT$HCfh z7Df-}1T44gqP+3u0xpn9IKiGoxp!9 ztCr)dF1GmZ!AACU{J%D)phrwI{N;t1cU!m*-ZFZiY85pK>WMIK$7S9>yo-p=DnSEJ zQYxG*;ZyHhusD-Mh(}ZQfYnUI-ZJ-yhlAdAVDh#(;D`DcdJUr(>`iFM%X8-UVcOIx zJTh1rVE(HL39k{(Gw?`2?@}W@yP70vIRS@-EFAczL5R_&#FH}o;jin%)*}{*?IWyq z4q!_s%a7{0n6^Y|@+|4P^%;a1bq1b-v9)Fe^JoqNtib!BKMU*x7X;}WX3NA!17!g= z(ldmGngdt`?akQ)`ceg+sN#h9>g>m6tvN6kgCT4stZs7=CMk zCyP`BKa1ZWWUrdTcvcbzq&pCQTzYR=Eh|y{b$!TOxm55np2bK#9{>4&p=Zn+em{Si zlW~fwDx=9-Z(u zzR>R|s7u+X^nP!*d0)NAGaF!!T%xf)s69*#hx>EL6WCgt(0p z+GOW4Zb$fIVHfak%ZU z=pwAJ6W2PNOIy%}fo)Ow9OA1z0~mrq3=>!4z;50wf!WrpMSON;5n+YBxL!x^Sn=?j zs-t?D^96>SefS7zq8~=T_X};Z--sTua8~vsW^-wX64eJ zeDf#*!T(Wcll@kR|2=`SGNa5QtY#0cO5XII8c}Kuhx(#MbVZD*{R)S;|6X8|y%QE0 z{cr>?1hI&)mWqMzCj5DKl_1Zij`XRdNp6+DEWoTkI7HjSi*km7#sG34p?VAYea}s7$ z)rM4OV8yiu*Q6Sod#bX(uh`*N+vFdrGEDlcN%o?$vK{-ocL_vm@ketN+yPV%<4uIg z`7%=BWKAD7*MBaM@@)dPNgfb#189lpJRS`dY=+U71U(pt9=upp@pvzRU@^zz7z+;gJU8zG|?~YJA1Q+ zGpHs)LmNkgt@eL{9R0yC5gHn)SnW%sVhe7yZz43bvKK;>u-fO77k3M}01U%KXlP~^ zPUlN(u6Gg9--d8uFcBKs`G|;~5VYD~g%dJjmrdZw^69h=fi40SDly$jiNDxEL~fK>U}XB z+;R)}HW3;{gjY&`SK|ScC+z6{*LnSZ6QNO529dKe?@Ut_t{|f8^7zFjLZiqGU^73f zWT(xzArBV-6QNObjw0aukkEbHhT&;um?{A~ed%X2Cr6li~0^KEclp!$fG58BPlOiqQQ{Yc=hgiO?uJFA3(cKNqL- zPfPQUOoT=ZJR@keKZl6!NTV$ip%DW^u-fkvy4N2d(M_V07bZd@76!3(@rls={wS%{ zzKPI?iLa3wP10(AX|o?P5gM_Gq| zJbCdxgnpvlUz!LF;}k5E)j6EJ*oghf5qnw_p<#T-I~iN;zd=O5Z`Q+(vIk%o-(xKf z>o(nFZ^Da2^lFkHw4Tr~hQqz#h8Ejp2*cV$0wn7R4WlkBq^F5!T%%3)It=1O|9feg z)p|n1sLwZ8P)TaF&wGuJrWGKW2o0klgK+q6X0<U g)Cb`5mL4MdKSJ%wlKT!u!~g&Q07*qoM6N<$f-!XhRsaA1 literal 0 HcmV?d00001 diff --git a/Resources/Images/SearchIconActive.png b/Resources/Images/SearchIconActive.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf16435c82593ac563450ee8acb3caf154b6a7f GIT binary patch literal 5853 zcmX|FcTf|`*G)trN=xWn2}nnzNH5Qj(2Fzyse*Knjvy^afIujQ4pJUSZ&E`QA}w^J zgCHG2U+97$0>3!l%=gFc&fGos?%AEYvwLpr6Fqh6J8X9V006b72HcP|uKjHwa#F9{ zF!>q)U@FptKQ<1`+0DJ(k~h^vxRx_0PJ=_LGc~<*9!d9pR*P?bTwUs-Jt}D`SR5UU z@wq=|hM;#Xp{tOltKk-SgD!YkoFgPC<0=q`j*iyIpQ+maofEPmw{p7x^6Fh^pjP}8JrVkMuler;8Mr=0O=!vnaj5hc#$yx9pJ%3B<-cabPT#G zd-fr-mEQzRr44QF=5LPE^0J8CIf_Oo!IT;Ez>k%A^JRuqe(F^~IV|tXUsbu_`Os(Q zzKwq_TZJAUZAT59%E`Tr1Ihg0Yulolp9Z-NZ3E61=GtP9Ug1Vxu@#I!Imm690SaVh zi%2DNyjYUK(eo>iEgjMVplC>}Db`+qw{RgjfkbLsE3(tg(Pu|;H24NU4Y?Nu^840a zKRP#oDzX2^Q_(wk3M0kH2h!!jJ>l3-V zAbarOJXiMbe|q~XKwbAde_*yocQ*;LfVmrIg-PBVB5_^{L8|)>_;llWqyCVKX$mO$ zEId#M*$%9FiBF$4qsChuQE?!u&|dzK6+v=$P0ld#l>3vt2KYCim{>rwq%Eca>4897 zoCj{|yF>PbA2?Maa6RUkCICe!B!O~K<4dL-SZAnm18dCIe>g%CF90JFkS-iH2vZ;w*+6M8d3Y{ z!PV8B!{c0oH&<|OeMsV7x>cLT$gKkwzo`} z0ALbbHS>5NX7TFkCAI?|cn?YM+pKKi2+R}+Qrzb)TnLipE6fwQ%d|z8T;uIDNG{ zu(|KaezVZK8f{$$aH-aYhU2?xyH%8nOA8Bf#g&zARU+?YBwoMmk^Hp{SjspN+anT3 zew$Vt&)X-7PQQ2x-3%QTOE|qSwPcA#zuYAfbKLNGxK(ApfPgDpW#xZDSwXWhnK4do za#mxFsTSsl%s!vSE|JGohl=~f=pC*|ToX0Kn`Sl?2C}^ASXID=VeF6X^5Rav6dzol z_Q<)3eD=Jvx69+nQ{Yoocn5Lu$!S~ud>zqd>^76l*f-}_e49q?N>u(o{7;uP9*4{f zxcr#lMrrzdt?z(L1!TBkY*=dhM0Q6|>@4h-dVRIeU2jyWIri!HP7&@f59;hr7jQ!I z+=-8q?iJ2a&(krolAYR~XK^>^$=F7kKeI~YdS1l4Kma5{3+u?;#mOL}WL`R;HT`;( zL@Vt1J0HULR#(^0Lpm*3?596opMH(rGNXmInN^r0dgKuM^Hk&kPhkTTXCs70 zZ(Q}miSz^nmr-|tN30rxMbhL}aKxYB-qYcs!Q^;3CWLl)GJ68rs|y{`ma|4wfE%BI zdEEx0lrI80Sq2*&LOffLYVb2vYE@VK^21>8#0s>@<~46Sfb#IYMyPJ50GJKDhNF{W zi=6r+1Pw4DPlB>??Asy>)$Krt0~KBB>3{uDU(waE1xOF9vs&jPelI; zzko~-AYb(87VaKV%Bbz-8kEm9?r5Td1-}@Kn!}}(3>ODs@Ns~wq~Uc+O~OhF$R0j2 z{?ozPxF0Sr+LW66IQKQw(p195%~u4{cj}?T1YJL_AWHoGX| z6%+=SApYnaRksX@y#Be2u>&n+9e$6-zGB6|~vfW}K zxiEdg?cFozk|NCffl7Q`Ou+OAGtk`t6)~7ivKlT#6O8nXoEc*mrxttaKe1ssgLIGxhwW)R&rh%EsR_Gm`EmsRNC^;4UeLJXW z1#XJi>zY!S6DaN0cqtt(*j*0)*%p2A*${-p{sg1NMayR=y1VnlUIxdr2aAxYD$3OaIZ} zVjCsC>M=YIGlsXc+PxFMp_c2OSt6ULY82$i2zgmC|7|1ALvEq4HR0@;(0C0LHY(BI zW#m3Qm|n_2@a-|qvx;#?Ho59{2*1+P19BI3-3ZhkJc_^yN0d)+W9gqM8eMUVDvb*T zJaja8_Q(w7CV=}@I&kU#Mg5r7HB9FHC#a=v-JuHme_d0Gl8glRVun0gj8{E5SAftxF$6e%FZOnNzf|%+XkkBWmK>r-a z@1LuTxL{^W`A_O@b32Pcv~3=IPz{PLYyIheRM55FGNy+>-4(_)qwOs8l%2;=nhXiM z2Ru%tnoF%b_VRy@W?bmHOvhOAZ|6FCLmYKt6gK5`*J-Xr$sb*m5pWiK#XLUX??LmDGDs5>YU7r z!n;uEW2Rr}?G%ObdnBqA7Kd{UI$J{%Xt|w>t@IPY-GBw3UaiOH4XBvwYZo}mRA{zh zXKV=Y^1!Q6NCz*TLxBZXc(19GwH1a1L-{XRf=fmte!|K`x|AmU)?FGxV6r0X{x^?lkjGlGjiM7e-X-+)>Fde91GSh{i~`6H8tlwaWM_qL|`9kjL-|M#}SYM`=) z1>rB+gZT1{YuAS3O>IK`3v6cGVRH&pXXm28)iH3l40O?e1qQNUhpc`uVzO0wHdqmo znCPO^otC=?;&z@@7vSYac^+vGBLNUWy$Qu?^jmGWYy3Y1CW`}3${6&c_OE%nH*Q=f0Arf z=D^(~DM0$BL=HyZ9LTZ5lQ%T-z{XPDT?7XR`}$E}oTRNb1Ft!g*m*R&1jgpAK5K1{ z$gYUGu-CzUUbMWdO?oLmc!Gl@bP|h-Swv$~Meh%{EmwMmrdMUWXPm$FqmL>~|LIR` zex%$l8S)7|JOVAA|cQ7=tIr4KT)hb4l836UO2FUA7#42scwD|c ziKw@dT~eKN9Y>jC-U{z4gYdsiK8bj1+|`Cll9W;gxVz%$mNFQhtTyV73aa<(T<$DC zFW`m^90tbseyTo*Ju^PKy)O#?8FB0VW@C!g(J8RjUX8+;DN{J(bg-mjj*fe?+Nx3H znWDln`RdcFWbj0Ac0vsJ_aJcY!4d1f3!PA3UW@L=+rV$M;$Lts409p;Z zFN9cuDT#G%R58H)hOM@_Qi!fkO$ojofYTE{`G^At~q)ykx&n3MM?Jg3v(J_x+FEdj&;A{>g*bXm3Hj zZHZ6Ok7E`mhvk|#80f88mrT|#Jyo4^+Prfkun^x2d7Qa^M|HAjY^ndx%SMi-abwqU zL8hGRZzU+266MHmms9)q4W=dIJmmW#Y$i<+V>g+jszTJ+w{+#it@sVawFuH3QkImv8S(7Or= z-v9Q-@0A6%h82dWB;Uj7ug?H*Z-+ezOk%Mz<6Cw!BHoo#ZI&wPAx_#!w+O51n`?YF zB9q`hBC^7tdcNws)}@)VHh9>S)B(`SFCLA1r96&WBs(hyS{QDZ9*O*@*0Tn4yY|{2 zX?~|(Kd^dM6*Z9aWtbU1m^6f`#TutiIh+(?HQdC#Lzj#Tss+sAjW&uu|EAm6-tnrA z50bRFL4$zC$_~B*`gqhivUbrvjYOPNx62?^??)A+&>lO)|9a{Dv|XD<|C^vQN7$UyXe0SMqT_-(w$0uC&Nx2eZ+*|1gPCPBPjd}0pvhqyB z%3X=yxf`1=^&=k@sX4kp#o|)iQI(Y5HKXDr*)HVdD0MNj-gTc)M*eQ#c|w${kq3Q` zN43>nn(pgxTBesQ))HL2G?Sj2^E7vfRaCZ-9E)YO6=&JYW<|baZZF+-4^THLee2iW zQGTJIewoHVr}#8Us8PmdX^BthBuVV(gUIWO%fiyh;3tWL3n#mGNL7e6S8utCbl%EI zl)jd_V9CcNN_ zop+k41coTw7Z?w9=ly8S8&=l!RaTSAR|cI#`aX&|HZq5%`t)S0Z$`U>0i0u0!i%7% z=OU(`!#en)OoZ~G)5z^CuZqcdS^XO8e&NO$IzddWLS%dFWQD@C?^n3#G)FE;v8Zk& zht?3I_MbRcx>%x4NX4MH7K2L0RC#YhbrF6+*8I*5b(Uf-mi6PcWtMn8*yvCh86n5X-+Qjhd zd!ftF`iHcH>NJfUtkwd6t=|d8FUKfJZVAU3BD@T!XHYzJ{_k0gh_o89_t4mibp4QDQ}Pw3J* zWK9_MUuL$xV>_2=zY6sekx)%9@f4o#=P&L`%yL~p?3g*Ufed(Nt_qVYot@+Bydd)F zS$x`vgyEAby0wD5s81xwHkanNqBPfhql*t^a(oY9rJ#enT~P6#LSc;(*`~jQO9i~X z{z*x7=M<4F)T#(&E->Piow-`jI(;cXv(l%BPcT29Bg8NdVSWGzJ14==jc4+Q8G`NIGVH5 z^DU9+u>tiP>NWB)dk+cvlyTOqDc^@9MRM2(6QsSWgLRQ>VBdY({X<7$sMg~Aqf?7! zg}4*?O5udr3Z-YhW?7;xc;aEAxzOS#d_7MJpGCR#QlD&@eR(;qry28EP@xpSQaMd1 zsAsTl%nILx`Wdc*`iw7limIb|#7GS9jAR&Jq@@1-?U5DA9@jxyx*SilvXU~n$;Pqd z{P*Zm=zM#>>PXI99e%ugi}|dXU|#PzrPZWDj*n2fM3M}UfS=3O;xo$pY9(R{1 z`RFu-r;N0Oe>hPgrBeg)9dEeKESA|+!?C4JvKsT*TX}0T-e@;pc#d9ato8wna4wSL zdObqbzo}Bp7xqE2k_2Ij))yCZ^R`q5ddVUs*)AzaFmsl?k2JTy*&OeZiqh9x9Ivn> zpsS?5&x};I>owV79P0`(unojns_a{QPsg+TbcL)o<%NwK8*{HQGA3NS3W+NS#t(1=vF252Jm;59JYH~#~2vlYAm literal 0 HcmV?d00001 diff --git a/Resources/Images/SearchIconHover.png b/Resources/Images/SearchIconHover.png new file mode 100644 index 0000000000000000000000000000000000000000..4cc047812991e8014c287391b69d47b52ff30424 GIT binary patch literal 5870 zcmV z3A7f~k;nhVotj;?JK*aQ|oT_^J2pRW=m zT`xJd&&UB^M7UEI(QTx_rbgI?ugaX^^)8}(O1Jq&Ba~T$y8{RSi~z8m0(-K6ULFVW zMf?fCbO5&jm|g6y`AgYE*bl&P0EYqCUV%M%vn^kz0C*0-^#JYyFt6O*E*G68=?#7L zt^x22fD1ekU{~mCe?I%*I$qs13P90CxEFwFyu3e3fjwF{oac-*O#Qm=&d^R#MMx`% zQvo~(AXH$FR`v(*0DzkT?CQHSv{FP7jtB5KfbS}>r6_y{z#NZ<>Z&P{2oK;T`X4K> zrKp_3bH<~6cZ4R2AVPX`Uqw+%05()$Ptl>csYw8C;MHWqG1^6VG{3ozQeaEjIFx;R zjPH(cjaCt^!EfqG{N^_DV;{b@9KvttLlxNK3x}}Jhx+aa7ibgVnE>tq zu$2N^d}B)hGx#1Df@l%pRxG|V6xiZBbPr~7rimed1`+Pg0vw{i7Gnkbun-5DKk~=5 z2#?^9vn?IlVXVyN03NDJqcU>Br3fzpaIM`~^-xWT%YAo3p12U<)&OQzWse*B8N>H% zYr9w3BQ3)1IO}0=;dYJo*_*R6zHGkC7D*9)l@m_wCKi`30k|8$4!%1gOQb~j6~1|! z3AJbB$0od=XhO^g2@z5N_8k^{T`D%8IZRK`*QN_rXsyDB-xmgg|{`Yw@2QE zo+g&)ZTa6EUrDt;=L1*(;4Qx1;h$LyU^#%^{P%SLtPfy40AJ+&(7x=GEgbeGD`#PBCb<9AxUF(a~Xaa{D!VDh-mJ3ERQa-J{c-&ZsOtf^UCd1Mb=-! zS<0hfXb5;IfXk9JUZlXDh6#hH7l0pfxZk>pO}#&_XP~TK!Yqe5XDb#UeObm)W(h&usI~DT&83nwzE`1bZIh%i)nGk53?e!NW`d$K7As8Tc;Uj0 zI;6>BI69GsNLpCV`ENUvRe;qbX-q58JMvujV?CDrGdMxx%UK=mehF#S?qEfodV_{% zK8UXxUQInQ45I10-LkO~(?)XTgew);qLy~PggYp6E|p@~(Ki8B!(YYzyh5pkEBH%o z)S(>_{#4nG9-O4{8wzZ33j2T}Sbna^tg{u^pq5sLqhpjDN%j$d%T(Zy=wAe5* zN#h5L?z-N)^t&ouskLIZP?x4SkC3eA!X|*0Z_c3K|jN2@;0XfTCdzPSW^)9kpM2 zIJudG_zy>Y_5M&GG$6u#xP-k6&a$nD{7KSyE`XmnYMxfxy+0hbClQVkvZE_cI$k*N zjf=R>stdmFyYssf;b(cMA`Q*|j!e?{BL{7J3C|Ddh5L60{XfX}GcCFk;h}b{*U3 zElCn-n+Svixe3ONYa>&gS$uAlA<1?*EY38xSNAxxoj~;AJSPfp98Q* z5gd~>wM4k5unmvj8?D+nc^`^&| zEtt>sp2K=3M(aEQV4;xzwiDj3c1lHry9yfd7;gd_1%m6)NLIhw-IwR1BEsDS+jNmI zYGlJ)K|c=g<(^eUxQk$$j|;YCWQ*i9;%mO#yNU=Uk1>8quq`87o)+}yj=tQxPM!cu zoi(wL_neFZlBDrp0lY0_`TCAe0u!T?Gbf~Yw)`$FJ87HsbY$Ne?h zNAey@KAX-U5pF5i_@9;a-)No%fQHIWr~mF zJ(PSlok1e}ykO(+D(k<|Jnsnl%!|;qPT`J9-asQCOoSO@A~c#sax^13N^=H@a23JE zmnrMN(L9nxpE`WFKP19c1>3Xoh6N`{<3)l!SgU~}-3w1AAB!zQlExpD-JyojES-|) zl!rmIy4|5-69$p=5V7>f<#h@V5haJdMn3cs^y_=Ri0~u9_C6!nmXQyVUqUYqjqS80 zXQN3hxFcgaZS&P=65ChK82b_$&0-?Vm`)4GHJZc%{xhaiIK4U?R+zPAgYnG>Mf%$(T;z;!Bc6rHp)#EWRXJRLU8ZEs8gU3|Ko1B3qm( zC}9wN0l<1f*1rYd&C)rs29*eP$%4F+g@%o6ku1n7S!mcP6%k67rk5snPv{lhpusx#%!XVlT!0v*+GZ6~6e}r&*M)MpdT-?cf5k4!tKQlTE zA}fQa6b`ERzMyrqU;Rv>oRk5zCfky4B8tjCNU%MlIcV{0BSD{e_Z5T!5faA>w`VlY z3BtZI5i-(_*d+`ii=a_P7(`!p5Nz*Nq)dfcBAmlxb4gAY@43+v()Z+V03P-6uGHIr zohfX`pfHH`7H-q1Ll{H@0Sp!P&n#c>M?DeVCu|3Cu7eYQjM|* zz*b7#69!S}DDm3M`5?Lx;U67+4KECXXblJL8lRjWd9eduJS>>cs*di0a;G{lmI}-M zhl6&F6`TfO3kSY%@vNz#I}zUK$jFn!AnNC+ZEp#Kh-$)~>Zt$T4n@&}2>o42Z{>8^KjX zWB30*jfn7C!Lus5@DdI2GI{3ZbuOB0=T4vu5F2CDrmXRbQl)~QQ+8y z!oxni+;Kl#z5vwGj0mrBd}!l8=P;b-X0!ljM@(g3y5dEf3x1l814fK)=I3b+@jC&# zy5oHS!;&7s4PqCk&#sl-k%ZVGylJeW5-9IJ5v?njg{BK!F` zSaE;p*tbgoT$ZG53_02bKN4@&KzqgX|==FvKAo?0UzQ863r~)fb
|~=ZG%}b` z;`whwNUXZiIRpDStX*p8rXNRJd3z+D)YA7eywtuxs6C@tQ2m=*JzALh6q5T-f8at< z&kD9_6bE`qU+1_IoD-xsfvIHTfd$&kH)I*&i{ z8(fA%odnN> z)Ek|=#6pzF$E20gTR7mmv}j9_IkSfcz{rovi?ESWmh*V@B2h?dH4c5;MW8JwXv|Zi zq0m=2$Ip=AM)r9#Q-Zm?-LzD|LJDiU6TmhCZHc2bS(M(ehXJ56?#mU}Q&yIg01q1eym9Ojk^`GYp{uPu71{i2uBdx)3GDfDf!0(-P@J%^kfz{RR0 zp_S5u1T$r*3i&0f??w-lrJf~)zK!Pj&Z$0D@!~YGvFw8*YJ{9gvXozrQe_QKC~#%J z+zUNPGmoze`N*~rJY?CfWc{r0CmwR9{36oCBKALxR<;$e>l{Ajpm3%3%O=8VX7fc$ z=R5K6ta5Fn$IPan0P07oF70Q)Q%OiVv~`ZqCi~?q{wV@&Wk#7rSj|&>UB>^dPZv>Y z4TpNZi|C42qw`f>$$V5`lYJFlsCk#mDHZ}>MA(&=_^K?YZ%`%3fxHjpf+x7*cSUq> zs_<6(yvUgsvjjHTsaVu+ITdS;MCaPYR}psQP2T*S#{Yda-d7vQ!rUc?6Ok6O=qiGv zFU5x$x1otAd9h}?KzYM8_)9pj2#a(k!rG7+?&qt*=3FDQFPF04jE7g1QunATe_ttw z|8~{2fWN-;x$@jA3i>I{eY|HmO<0MBG`Q34h7y8r60%_kCo`+cyf6_OuAzYMQ9N&x;M*KoqbBph zL}+MWDQ8|t#lNa5wQnLcv_OTXzA3o5P6^H@Yv{X)(9pypp0`PDu3rG)NZ0*05gOX~ zD<3{1vAIt3!m9!7@6rb*LPI0-d1(5MfNiu=`fHb^_DzI_R_3t~B}(nD%C(9HxbTIE z(9q0W9x6&~uJ_^$5AiAtCPG6y_i`~OsZ#q?+J93?pO^@ZB0zJtF+%5(Y{Ds5Vx{&? zgho-A#D$+F=)+$`ky866LZe8W$CdOX*pZK3$UXDIL}(O^v4Vx3se1X;yaT>XghtVz zdErs~m69Ofisrcme47Z3B0?*r-w`ae|2+Vg=Jm@>gho+W%4cmzmD)cJz)$k{#U?_d z$h^m;_Ma8%!&!M$0Wc97MdvLJ_+B8?k1Kh2ni(cSqYO~S#b6d>-gNNl?0#9bV)TM~ zg^AGc1q%57E+0)U!BLHt+BXpzKCy(u%_K|hkL~G)OoWDSEaG{aRH^;(0J=Q{(nM(Z z2nBi%6)d$+MXC<0_X8$E!&hGB3g2%F^nVquRa3LnzKPKAnRz@%lq$7V6 z`-=hW%hlJK#zbfs-9vkOw3%No^Mc|~2LX7nB|ifAKU1KRl_1iY2><{907*qoM6N<$ Ef&vL2Bme*a literal 0 HcmV?d00001 diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 07a7ac45f6..fc7a6b59b6 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2384,6 +2384,8 @@ "Preset_4": "Preset 4", "Preset_5": "Preset 5", "Standard": "Standard", + "FFA": "Free For All", + "HidenSeekTOHE": "Hide And Seek", "GameMode": "Game Mode", "PressTabToNextPage": "Press Tab or Number for Next Page...", "RoleSummaryText": "Role Summary:", From 61112ee85fd6ed928f942b38f0766e714dfc2eec Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:24:57 +0200 Subject: [PATCH 101/778] remove some unused stuff --- Patches/GameOptionsMenuPatch.cs | 6 ------ Patches/GameSettingMenuPatch.cs | 1 - 2 files changed, 7 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index babc4e4920..5fc5ce797b 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -52,8 +52,6 @@ private static void InitializePostfix() [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings)), HarmonyPrefix] private static bool CreateSettingsPrefix(GameOptionsMenu __instance) { - - Logger.Info("GameOptionsMenu Create Setgginsssds HiddenBySearch = []; public static bool ShouldReveal = false; From af0edad8df3c75dc78db1964bc045ec6550526e6 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:28:21 +0200 Subject: [PATCH 102/778] more remove --- Patches/GameSettingMenuPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 5199c1e774..f8609471d5 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -250,11 +250,11 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) passiveButton.OnClick = new(); passiveButton.OnClick.AddListener( (Action)(() => { - SearchForOptions(__instance, TextField); + SearchForOptions(TextField); })); - static void SearchForOptions(GameSettingMenu __instance, FreeChatInputField textarea) + static void SearchForOptions(FreeChatInputField textarea) { if (ModGameOptionsMenu.TabIndex < 3) return; From bc3e2e1cced0efff9f0e2238eddd0a918f070653 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:44:05 +0200 Subject: [PATCH 103/778] unneccesary assigment of value --- Patches/GameSettingMenuPatch.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index f8609471d5..f5a9b8f275 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -179,8 +179,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) Minus.OnClick.AddListener( (Action)(() => { if (PresetBehaviour == null) __instance.ChangeTab(3, false); - StringOption PresetBeh = PresetBehaviour; - PresetBeh.Decrease(); + PresetBehaviour.Decrease(); })); Minus.activeTextColor = new Color(255f, 255f, 255f); Minus.inactiveTextColor = new Color(255f, 255f, 255f); @@ -212,9 +211,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) plus.OnClick.AddListener( (Action)(() => { if (PresetBehaviour == null) __instance.ChangeTab(3, false); - StringOption PresetBeh = PresetBehaviour; - - PresetBeh.Increase(); + PresetBehaviour.Increase(); })); plus.activeTextColor = new Color(255f, 255f, 255f); plus.inactiveTextColor = new Color(255f, 255f, 255f); From ee0d1e0c0aa72e2408bc91fd8b37d9dc360005aa Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:56:02 +0200 Subject: [PATCH 104/778] change name --- Patches/GameSettingMenuPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index f5a9b8f275..637c55402f 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -251,12 +251,12 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) })); - static void SearchForOptions(FreeChatInputField textarea) + static void SearchForOptions(FreeChatInputField textField) { if (ModGameOptionsMenu.TabIndex < 3) return; HiddenBySearch.Do(x => x.SetHidden(false)); - string text = textarea.textArea.text.Trim().ToLower(); + string text = textField.textArea.text.Trim().ToLower(); if (text == "loonie") text = "dictator"; Logger.Info($"Current text: {text}", "SearchBar Text "); var Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) From 88679c8749b13ea80e0431d4b21cef1b2a5236c2 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 20:56:16 +0200 Subject: [PATCH 105/778] something --- Patches/GameSettingMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 637c55402f..16782030f5 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -260,7 +260,7 @@ static void SearchForOptions(FreeChatInputField textField) if (text == "loonie") text = "dictator"; Logger.Info($"Current text: {text}", "SearchBar Text "); var Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) - && !GetString($"{x.Name}").ToLower().StartsWith(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); + && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; Result.Do(x => x.SetHidden(true)); From 734ad703df110a58d5f72f1210f50acc31b18e3a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:09:02 +0200 Subject: [PATCH 106/778] fix massive bug (idk wtf how it happens ngl) --- Patches/GameOptionsMenuPatch.cs | 4 +++- Patches/GameSettingMenuPatch.cs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 5fc5ce797b..a4d17c7569 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,9 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); + var enabled = (!GameSettingMenuPatch.SearchWinners.Any() || GameSettingMenuPatch.SearchWinners.Contains(option)) + && !option.IsHiddenOn(Options.CurrentGameMode) + && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); if (option is TextOptionItem) { diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 16782030f5..48a86d5ed2 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -100,6 +100,7 @@ public static void StartPostfix(GameSettingMenu __instance) if (ShouldReveal) { HiddenBySearch.Do(x => x.SetHidden(false)); + SearchWinners.Clear(); HiddenBySearch.Clear(); } @@ -136,6 +137,7 @@ private static void SetDefaultButton(GameSettingMenu __instance) public static StringOption PresetBehaviour; public static FreeChatInputField InputField; public static List HiddenBySearch = []; + public static List SearchWinners = []; public static bool ShouldReveal = false; private static void SetupAdittionalButtons(GameSettingMenu __instance) @@ -256,6 +258,7 @@ static void SearchForOptions(FreeChatInputField textField) if (ModGameOptionsMenu.TabIndex < 3) return; HiddenBySearch.Do(x => x.SetHidden(false)); + SearchWinners.Clear(); string text = textField.textArea.text.Trim().ToLower(); if (text == "loonie") text = "dictator"; Logger.Info($"Current text: {text}", "SearchBar Text "); @@ -263,6 +266,8 @@ static void SearchForOptions(FreeChatInputField textField) && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; Result.Do(x => x.SetHidden(true)); + SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); + SearchWinners.Do(x => Logger.Info($"Searching for {x.Name}", "Searching For test")); ShouldReveal = false; GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); From 7a61286bbf05cca8a2e465251c83457284f6f15d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:13:09 +0200 Subject: [PATCH 107/778] oops- --- Patches/GameOptionsMenuPatch.cs | 2 +- Patches/GameSettingMenuPatch.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index a4d17c7569..8a4454b5d0 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = (!GameSettingMenuPatch.SearchWinners.Any() || GameSettingMenuPatch.SearchWinners.Contains(option)) + var enabled = (!GameSettingMenuPatch.SearchWinners[modTab].Any() || GameSettingMenuPatch.SearchWinners[modTab].Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 48a86d5ed2..efc0140031 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -137,7 +137,7 @@ private static void SetDefaultButton(GameSettingMenu __instance) public static StringOption PresetBehaviour; public static FreeChatInputField InputField; public static List HiddenBySearch = []; - public static List SearchWinners = []; + public static Dictionary> SearchWinners = []; public static bool ShouldReveal = false; private static void SetupAdittionalButtons(GameSettingMenu __instance) @@ -266,8 +266,8 @@ static void SearchForOptions(FreeChatInputField textField) && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; Result.Do(x => x.SetHidden(true)); - SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - SearchWinners.Do(x => Logger.Info($"Searching for {x.Name}", "Searching For test")); + SearchWinners[(TabGroup)(ModGameOptionsMenu.TabIndex)] = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); + SearchWinners[(TabGroup)(ModGameOptionsMenu.TabIndex)].Do(x => Logger.Info($"Searching for {x.Name}", "Searching For test")); ShouldReveal = false; GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); From dccaa4a70a6828d1d0629ac3761a97eaffac047d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:15:32 +0200 Subject: [PATCH 108/778] =?UTF-8?q?Call=20me=20mr.=20einstein=20?= =?UTF-8?q?=F0=9F=92=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Patches/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 8a4454b5d0..05aa7a3799 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = (!GameSettingMenuPatch.SearchWinners[modTab].Any() || GameSettingMenuPatch.SearchWinners[modTab].Contains(option)) + var enabled = (!GameSettingMenuPatch.SearchWinners.TryGetValue(modTab, out var dinners) && !dinners.Any() || dinners.Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); From 301315af8ded8e493a2096d5379d74ad835ae635 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:16:13 +0200 Subject: [PATCH 109/778] nvm --- Patches/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 05aa7a3799..f4cb107c4f 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = (!GameSettingMenuPatch.SearchWinners.TryGetValue(modTab, out var dinners) && !dinners.Any() || dinners.Contains(option)) + var enabled = (!GameSettingMenuPatch.SearchWinners.TryGetValue(modTab, out var dinners) || !dinners.Any() || dinners.Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); From ded06331a0cc3b5eb2bb25ca4a11219802e13098 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:17:47 +0200 Subject: [PATCH 110/778] okay wtf compiler --- Patches/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index f4cb107c4f..7ac8b7276d 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = (!GameSettingMenuPatch.SearchWinners.TryGetValue(modTab, out var dinners) || !dinners.Any() || dinners.Contains(option)) + var enabled = ((GameSettingMenuPatch.SearchWinners.TryGetValue(modTab, out var dinners) ? !dinners.Any() : true) || dinners.Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); From 67b24e73b3f359ba2599421bcddb7e4f22be3665 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:22:33 +0200 Subject: [PATCH 111/778] okay nvm I'm jsut gonna fix this shit later --- Patches/GameOptionsMenuPatch.cs | 2 +- Patches/GameSettingMenuPatch.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 7ac8b7276d..23e253cc5c 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = ((GameSettingMenuPatch.SearchWinners.TryGetValue(modTab, out var dinners) ? !dinners.Any() : true) || dinners.Contains(option)) + var enabled = ((!GameSettingMenuPatch.SearchWinners.Any() || GameSettingMenuPatch.SearchWinners.Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index efc0140031..ebe391b5ba 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -137,7 +137,8 @@ private static void SetDefaultButton(GameSettingMenu __instance) public static StringOption PresetBehaviour; public static FreeChatInputField InputField; public static List HiddenBySearch = []; - public static Dictionary> SearchWinners = []; + public static List SearchWinners = []; + public static bool ShouldReveal = false; private static void SetupAdittionalButtons(GameSettingMenu __instance) @@ -266,8 +267,9 @@ static void SearchForOptions(FreeChatInputField textField) && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; Result.Do(x => x.SetHidden(true)); - SearchWinners[(TabGroup)(ModGameOptionsMenu.TabIndex)] = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - SearchWinners[(TabGroup)(ModGameOptionsMenu.TabIndex)].Do(x => Logger.Info($"Searching for {x.Name}", "Searching For test")); + SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); + SearchWinners.Do(x => Logger.Info($"Searching for {x.Name}", "Searching For test")); + ShouldReveal = false; GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); From 706db429a6224688ef3c831a1eef4f9699215fc5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:26:05 +0200 Subject: [PATCH 112/778] holy shit --- Patches/GameOptionsMenuPatch.cs | 3 +-- Patches/GameSettingMenuPatch.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 23e253cc5c..2c704fe4a4 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,8 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = ((!GameSettingMenuPatch.SearchWinners.Any() || GameSettingMenuPatch.SearchWinners.Contains(option)) - && !option.IsHiddenOn(Options.CurrentGameMode) + var enabled = (!GameSettingMenuPatch.SearchWinners.Any() || GameSettingMenuPatch.SearchWinners.Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); if (option is TextOptionItem) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index ebe391b5ba..778edfd59c 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -273,7 +273,7 @@ static void SearchForOptions(FreeChatInputField textField) ShouldReveal = false; GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); - _ = new LateTask(() => ShouldReveal = true, 0.28f); + _ = new LateTask(() => ShouldReveal = true, 0.38f); } } From 0f5c8194c88b267b119a97a7fda3a7c32601424e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:31:14 +0200 Subject: [PATCH 113/778] okay I fix this stupid shit later, my head is about to explode --- Patches/GameOptionsMenuPatch.cs | 3 ++- Patches/GameSettingMenuPatch.cs | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 2c704fe4a4..33743d20aa 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,7 +75,8 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = (!GameSettingMenuPatch.SearchWinners.Any() || GameSettingMenuPatch.SearchWinners.Contains(option)) && !option.IsHiddenOn(Options.CurrentGameMode) + var enabled = (!GameSettingMenuPatch.SearchWinners.Any() || (GameSettingMenuPatch.SearchWinners.Contains(option) || GameSettingMenuPatch.SearchWinners.Contains(option.Parent))) + && !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); if (option is TextOptionItem) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 778edfd59c..80d171f8eb 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -305,6 +305,11 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ } } + if (ShouldReveal) // Un-hiding options here would do nothing + { + SearchWinners.Clear(); + } + if (tabNum < 3) return true; var tabGroupId = (TabGroup)(tabNum - 3); From 5fa7e0b46fb25596885adbd576403e7c5c461bfa Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:42:29 +0200 Subject: [PATCH 114/778] okay jesus christ, finally --- Modules/OptionItem/OptionItem.cs | 2 +- Patches/GameOptionsMenuPatch.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index 84c67cef15..a997bd09ac 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -181,7 +181,7 @@ public virtual string GetString() // Deprecated IsHidden function public virtual bool IsHiddenOn(CustomGameMode mode) { - return IsHidden || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); + return IsHidden || (GameSettingMenuPatch.SearchWinners.Any() && (!GameSettingMenuPatch.SearchWinners.Contains(this) && !GameSettingMenuPatch.SearchWinners.Contains(this.Parent))) || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); } public string ApplyFormat(string value) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 33743d20aa..37a5f7ffe4 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -75,8 +75,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = (!GameSettingMenuPatch.SearchWinners.Any() || (GameSettingMenuPatch.SearchWinners.Contains(option) || GameSettingMenuPatch.SearchWinners.Contains(option.Parent))) - && !option.IsHiddenOn(Options.CurrentGameMode) + var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); if (option is TextOptionItem) From 904e0b493e97c3780494d2bd0b0ea53c3d08d20d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:01:27 +0200 Subject: [PATCH 115/778] fix more bugs --- Modules/OptionItem/OptionItem.cs | 21 ++++++++++++++++++++- Patches/GameSettingMenuPatch.cs | 13 ++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index a997bd09ac..bbb4923e3d 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -181,9 +181,28 @@ public virtual string GetString() // Deprecated IsHidden function public virtual bool IsHiddenOn(CustomGameMode mode) { - return IsHidden || (GameSettingMenuPatch.SearchWinners.Any() && (!GameSettingMenuPatch.SearchWinners.Contains(this) && !GameSettingMenuPatch.SearchWinners.Contains(this.Parent))) || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); + return IsHidden || CheckSearchHidden() || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); } + public bool CheckSearchHidden() + { + if (!GameSettingMenuPatch.SearchWinners.Any()) + return false; + + var LastParent = this.Id; + + for (var i = 0; i < 5; i++) + { + if (AllOptions.First(x => x.Id == LastParent).Parent == null) break; + LastParent = AllOptions.First(x => x.Id == LastParent).Parent.Id; + } + + if (!GameSettingMenuPatch.SearchWinners.Contains(AllOptions.First(x => x.Id == LastParent))) + return true; + + + return false; + } public string ApplyFormat(string value) { if (ValueFormat == OptionFormat.None) return value; diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 80d171f8eb..601856dd22 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -1,6 +1,7 @@ using System; using TMPro; using UnityEngine; +using UnityEngine.TextCore; using UnityEngine.UI; using static TOHE.Translator; using static UnityEngine.RemoteConfigSettingsHelper; @@ -55,7 +56,7 @@ public static void StartPostfix(GameSettingMenu __instance) TabGroup.Addons => "#ff9ace", _ => "#ffffff", }; - label.fontStyle = FontStyles.UpperCase; + label.fontStyle = TMPro.FontStyles.UpperCase; label.text = $"{GetString("TabGroup." + tab)}"; _ = ColorUtility.TryParseHtmlString(htmlcolor, out Color tabColor); @@ -115,7 +116,7 @@ private static void SetDefaultButton(GameSettingMenu __instance) var textLabel = gameSettingButton.GetComponentInChildren(); textLabel.DestroyTranslator(); - textLabel.fontStyle = FontStyles.UpperCase; + textLabel.fontStyle = TMPro.FontStyles.UpperCase; textLabel.text = GetString("TabVanilla.GameSettings"); //gameSettingButton.activeTextColor = gameSettingButton.inactiveTextColor = Color.black; //gameSettingButton.selectedTextColor = Color.blue; @@ -221,7 +222,6 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) plus.disabledTextColor = new Color(255f, 255f, 255f); plus.selectedTextColor = new Color(255f, 255f, 255f); - plus.transform.localPosition = new Vector3(1.62f, 1.87f, 1f); var GameSettingsLabel = __instance.GameSettingsButton.transform.parent.parent.FindChild("GameSettingsLabel").GetComponent(); @@ -233,7 +233,8 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) TextField.transform.localScale = new Vector3(0.3f, 0.59f, 1); TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); InputField = TextField; - TextField.textArea.outputText.transform.localScale = new Vector3(3f, 2f, 1f); + TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); + TextField.textArea.outputText.font = PLuLabel.font; var button = TextField.transform.FindChild("ChatSendButton"); Object.Destroy(button.FindChild("Normal").FindChild("Icon").GetComponent()); @@ -305,8 +306,10 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ } } - if (ShouldReveal) // Un-hiding options here would do nothing + if (ShouldReveal) { + HiddenBySearch.Do(x => x.SetHidden(false)); + HiddenBySearch.Clear(); SearchWinners.Clear(); } From c046b29a566a503770a8f5fb0ffa24962b8dd71b Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:35:05 +0200 Subject: [PATCH 116/778] okay only 1 bug remaining, but holy shit is it hard to find --- Patches/GameSettingMenuPatch.cs | 13 ++++++++----- Resources/Lang/en_US.json | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 601856dd22..6919fd42a4 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -262,16 +262,19 @@ static void SearchForOptions(FreeChatInputField textField) HiddenBySearch.Do(x => x.SetHidden(false)); SearchWinners.Clear(); string text = textField.textArea.text.Trim().ToLower(); - if (text == "loonie") text = "dictator"; - Logger.Info($"Current text: {text}", "SearchBar Text "); var Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; - Result.Do(x => x.SetHidden(true)); SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - SearchWinners.Do(x => Logger.Info($"Searching for {x.Name}", "Searching For test")); - + if (!SearchWinners.Any()) + { + HiddenBySearch.Clear(); + Logger.SendInGame(GetString("SearchNoResult")); // okay so showpopup nor this will overlay the menu, but I use this just for the sound lol + return; + } + Result.Do(x => x.SetHidden(true)); + ShouldReveal = false; GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); _ = new LateTask(() => ShouldReveal = true, 0.38f); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index fc7a6b59b6..55ef29bba3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2377,6 +2377,7 @@ "Chance90": "90%", "Chance95": "95%", "Chance100": "100%", + "SearchNoResult": "No Results.", "Preset": "Preset", "Preset_1": "Preset 1", "Preset_2": "Preset 2", From d45d0d8f54d481cf8ee68d30c2d5c23d2847295a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:37:58 +0200 Subject: [PATCH 117/778] remove using --- Patches/GameSettingMenuPatch.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 6919fd42a4..894a84b255 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -1,10 +1,8 @@ using System; using TMPro; using UnityEngine; -using UnityEngine.TextCore; using UnityEngine.UI; using static TOHE.Translator; -using static UnityEngine.RemoteConfigSettingsHelper; using Object = UnityEngine.Object; namespace TOHE; @@ -56,7 +54,7 @@ public static void StartPostfix(GameSettingMenu __instance) TabGroup.Addons => "#ff9ace", _ => "#ffffff", }; - label.fontStyle = TMPro.FontStyles.UpperCase; + label.fontStyle = FontStyles.UpperCase; label.text = $"{GetString("TabGroup." + tab)}"; _ = ColorUtility.TryParseHtmlString(htmlcolor, out Color tabColor); @@ -116,7 +114,7 @@ private static void SetDefaultButton(GameSettingMenu __instance) var textLabel = gameSettingButton.GetComponentInChildren(); textLabel.DestroyTranslator(); - textLabel.fontStyle = TMPro.FontStyles.UpperCase; + textLabel.fontStyle = FontStyles.UpperCase; textLabel.text = GetString("TabVanilla.GameSettings"); //gameSettingButton.activeTextColor = gameSettingButton.inactiveTextColor = Color.black; //gameSettingButton.selectedTextColor = Color.blue; From 8e13ecc0da867cf404d7be7bf22d33343d07eb27 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:41:42 +0200 Subject: [PATCH 118/778] unneccesary lol --- Patches/GameSettingMenuPatch.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 894a84b255..6a2767b19a 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -233,6 +233,8 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) InputField = TextField; TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); TextField.textArea.outputText.font = PLuLabel.font; + TextField.charCountText.gameObject.SetActive(false); + var button = TextField.transform.FindChild("ChatSendButton"); Object.Destroy(button.FindChild("Normal").FindChild("Icon").GetComponent()); @@ -413,14 +415,6 @@ public static bool Prefix(FreeChatInputField __instance) Vector2 size = __instance.Background.size; size.y = Math.Max(0.62f, __instance.textArea.TextHeight + 0.2f); __instance.Background.size = size; - int length = __instance.textArea.text.Length; - ((TMP_Text)__instance.charCountText).text = string.Format("{0}/100", (object)length); - if (length < 75) - ((Graphic)__instance.charCountText).color = Color.black; - else if (length < 100) - ((Graphic)__instance.charCountText).color = new Color(1f, 1f, 0.0f, 1f); - else - ((Graphic)__instance.charCountText).color = Color.red; return false; } return true; From 1ecca2436ffb0b85a266de5fb42522b716394b9a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:32:21 +0200 Subject: [PATCH 119/778] some changes --- Patches/GameSettingMenuPatch.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 6a2767b19a..c30e5598c7 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -230,10 +230,12 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) var TextField = GameObject.Instantiate(FreeChatField, ParentLeftPanel.parent); TextField.transform.localScale = new Vector3(0.3f, 0.59f, 1); TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); - InputField = TextField; TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); TextField.textArea.outputText.font = PLuLabel.font; - TextField.charCountText.gameObject.SetActive(false); + TextField.textArea.outputText.color = Color.white; + + InputField = TextField; + var button = TextField.transform.FindChild("ChatSendButton"); From 9d275797107388ec2bdbb38055818279c786b18b Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:34:39 +0200 Subject: [PATCH 120/778] bruh --- Patches/GameSettingMenuPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index c30e5598c7..1611fc3769 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -232,7 +232,6 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); TextField.textArea.outputText.font = PLuLabel.font; - TextField.textArea.outputText.color = Color.white; InputField = TextField; From 70ffe76bb8555bfd690f5fbada3c5383d9e818e4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:00:45 +0200 Subject: [PATCH 121/778] nothing --- Patches/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 37a5f7ffe4..29579c9eaa 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -278,7 +278,7 @@ public static void ReOpenSettings(bool IsPresset, int index = 4) hostButtons.transform.FindChild("Edit").GetComponent().ReceiveClickDown(); }, 0.1f, "Click Edit Button"); - // Change tab to "Mod Settings" + // Change tab to Original Tab if (!IsPresset) { _ = new LateTask(() => From a14ac088b67ba64f1e706f038ab1d414ad84acab Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:02:21 +0200 Subject: [PATCH 122/778] fix error in online games (thanks gurge) --- Patches/GameSettingMenuPatch.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 1611fc3769..fe45a011b0 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -144,9 +144,12 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) { var ParentLeftPanel = __instance.GamePresetsButton.transform.parent; - var labeltag = GameObject.Find("Privacy (Not Interactive)"); + var cocztam = DestroyableSingleton.Instance; + + var labeltag = GameObject.Find("ModeValue"); var preset = Object.Instantiate(labeltag, ParentLeftPanel); - preset.transform.localPosition = new Vector3(-4.6f, -3.75f, -2.0f); + + preset.transform.localPosition = new Vector3(-3.33f, -0.45f, -2f); preset.transform.localScale = new Vector3(0.65f, 0.63f, 1f); var SpriteRenderer = preset.GetComponentInChildren(); SpriteRenderer.color = Color.white; @@ -188,7 +191,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) Minus.disabledTextColor = new Color(255f, 255f, 255f); Minus.selectedTextColor = new Color(255f, 255f, 255f); - Minus.transform.localPosition = new Vector3(0.01f, 1.87f, 1f); + Minus.transform.localPosition = new Vector3(-2f, -3.37f, -4f); Minus.inactiveSprites.GetComponent().sprite = TempMinus.GetComponentInChildren().sprite; Minus.activeSprites.GetComponent().sprite = TempMinus.GetComponentInChildren().sprite; Minus.selectedSprites.GetComponent().sprite = TempMinus.GetComponentInChildren().sprite; @@ -220,7 +223,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) plus.disabledTextColor = new Color(255f, 255f, 255f); plus.selectedTextColor = new Color(255f, 255f, 255f); - plus.transform.localPosition = new Vector3(1.62f, 1.87f, 1f); + plus.transform.localPosition = new Vector3(-0.4f, -3.37f, -4f); var GameSettingsLabel = __instance.GameSettingsButton.transform.parent.parent.FindChild("GameSettingsLabel").GetComponent(); GameSettingsLabel.DestroyTranslator(); From de695339da92874ec36ff601a80d3b55a53d460f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:44:33 +0200 Subject: [PATCH 123/778] skidadle EHR code --- Modules/Extensions/CollectionExtensions.cs | 40 ++++++++++++++++++++++ Patches/GameOptionsMenuPatch.cs | 38 +++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs index e0cd9bd172..4d73e45bb9 100644 --- a/Modules/Extensions/CollectionExtensions.cs +++ b/Modules/Extensions/CollectionExtensions.cs @@ -87,6 +87,46 @@ public static HashSet FilterDuplicates(this IEnumerable + /// Determines whether a collection contains any elements that satisfy a predicate and returns the first element that satisfies the predicate + ///
+ /// The collection to search + /// The predicate to check for each element + /// The first element that satisfies the predicate, or the default value of if no elements satisfy the predicate + /// The type of the elements in the collection + /// true if the collection contains any elements that satisfy the predicate, false otherwise + public static bool Find(this IEnumerable collection, Func predicate, out T element) + { + if (collection is List list) + { + for (int i = 0; i < list.Count; i++) + { + T item = list[i]; + if (predicate(item)) + { + element = item; + return true; + } + } + + element = default; + return false; + } + + foreach (T item in collection) + { + if (predicate(item)) + { + element = item; + return true; + } + } + + element = default; + return false; + } + /// /// Return the first byte of a HashSet(Byte) /// diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 29579c9eaa..8416a9b6bf 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -194,6 +194,7 @@ float CalculateScrollBarYBoundsMax() return -num - 1.65f; } } + private static void OptionBehaviourSetSizeAndPosition(OptionBehaviour optionBehaviour, OptionItem option, OptionTypes type) { Vector3 positionOffset = new(0f, 0f, 0f); @@ -583,10 +584,11 @@ private static bool InitializePrefix(StringOption __instance) { var item = OptionItem.AllOptions[index]; var name = item.GetName(); + var name1 = name; var language = DestroyableSingleton.Instance.currentLanguage.languageID; //Logger.Info($" Language: {language}", "StringOption.Initialize"); - if (EnumHelper.GetAllValues().Any(x => GetString($"{x}") == name.RemoveHtmlTags())) + if (EnumHelper.GetAllValues().Find(x => GetString($"{x}") == name1.RemoveHtmlTags(), out var role)) { name = $"{name}"; __instance.TitleText.fontWeight = FontWeight.Black; @@ -595,12 +597,28 @@ private static bool InitializePrefix(StringOption __instance) SupportedLangs.Russian or SupportedLangs.Japanese or SupportedLangs.SChinese or SupportedLangs.TChinese => 0.15f, _ => 0.35f, }; + + SetupHelpIcon(role, __instance); } __instance.TitleText.text = name; return false; } return true; } + private static void SetupHelpIcon(CustomRoles role, StringOption option) + { + var template = option.transform.FindChild("MinusButton (1)"); + var icon = Object.Instantiate(template, template.parent, true); + icon.name = $"{role}HelpIcon"; + var text = icon.FindChild("Plus_TMP").GetComponent(); + text.text = "?"; + text.color = Color.white; + icon.FindChild("InactiveSprite").GetComponent().color = Color.black; + icon.FindChild("ActiveSprite").GetComponent().color = Color.gray; + icon.localPosition += new Vector3(-0.8f, 0f, 0f); + icon.SetAsLastSibling(); + } + [HarmonyPatch(nameof(StringOption.UpdateValue)), HarmonyPrefix] private static bool UpdateValuePrefix(StringOption __instance) { @@ -671,6 +689,24 @@ public static bool IncreasePrefix(StringOption __instance) [HarmonyPatch(nameof(StringOption.Decrease)), HarmonyPrefix] public static bool DecreasePrefix(StringOption __instance) { + //Credit For SetupHelpIcon to EHR https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/GameOptionsMenuPatch.cs + + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index) && !__instance.transform.FindChild("MinusButton (1)").GetComponent().activeSprites.activeSelf) + { + var item = OptionItem.AllOptions[index]; + var name = item.GetName(); + if (Enum.GetValues().Find(x => GetString($"{x}") == name.RemoveHtmlTags(), out var role)) + { + var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); + var str = Translator.GetString($"{roleName}InfoLong"); + int Lenght = str.Length > 360 ? 360 : str.Length; + var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; + var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); + var info = $"{ColorRole}: {infoLong}"; + GameSettingMenu.Instance.MenuDescriptionText.text = info; + return false; + } + } if (__instance.Value == 0) { __instance.Value = __instance.Values.Length - 1; From d80fb939a8ac6f6189238d7627e8c1d10f1b2600 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:08:19 +0200 Subject: [PATCH 124/778] some changes --- Patches/GameOptionsMenuPatch.cs | 28 ++++++++++++++-------------- Patches/GameSettingMenuPatch.cs | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 8416a9b6bf..56c044d2e0 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -54,10 +54,8 @@ private static bool CreateSettingsPrefix(GameOptionsMenu __instance) { Instance ??= __instance; // When is vanilla tab, run vanilla code - if (ModGameOptionsMenu.TabIndex < 3) - { - return true; - } + if (ModGameOptionsMenu.TabIndex < 3) return true; + __instance.scrollBar.SetYBoundsMax(CalculateScrollBarYBoundsMax()); __instance.StartCoroutine(CoRoutine().WrapToIl2Cpp()); return false; @@ -265,7 +263,7 @@ private static void OptionBehaviourSetSizeAndPosition(OptionBehaviour optionBeha break; } } - public static void ReOpenSettings(bool IsPresset, int index = 4) + public static void ReOpenSettings(int index = 4) { //Close setting menu GameSettingMenu.Instance.Close(); @@ -279,15 +277,17 @@ public static void ReOpenSettings(bool IsPresset, int index = 4) hostButtons.transform.FindChild("Edit").GetComponent().ReceiveClickDown(); }, 0.1f, "Click Edit Button"); + + if (index > 3) + return; + // Change tab to Original Tab - if (!IsPresset) + _ = new LateTask(() => { - _ = new LateTask(() => - { - if (!GameStates.IsLobby || GameSettingMenu.Instance == null) return; - GameSettingMenu.Instance.ChangeTab(index, Controller.currentTouchType == Controller.TouchType.Joystick); - }, 0.28f, "Change Tab"); - } + if (!GameStates.IsLobby || GameSettingMenu.Instance == null) return; + GameSettingMenu.Instance.ChangeTab(index, Controller.currentTouchType == Controller.TouchType.Joystick); + }, 0.28f, "Change Tab"); + } [HarmonyPatch(nameof(GameOptionsMenu.ValueChanged)), HarmonyPrefix] private static bool ValueChangedPrefix(GameOptionsMenu __instance, OptionBehaviour option) @@ -631,7 +631,7 @@ private static bool UpdateValuePrefix(StringOption __instance) item.SetValue(__instance.GetInt()); if (item is PresetOptionItem) { - GameOptionsMenuPatch.ReOpenSettings(true); + GameOptionsMenuPatch.ReOpenSettings(1); } else if (item is StringOptionItem && item.Name == "GameMode") { @@ -640,7 +640,7 @@ private static bool UpdateValuePrefix(StringOption __instance) else if (__instance.GetInt() != 2 && GameStates.IsHideNSeek) item.SetValue(2); - GameOptionsMenuPatch.ReOpenSettings(false); + GameOptionsMenuPatch.ReOpenSettings(); } return false; diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index fe45a011b0..bb6e5b4191 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -280,7 +280,7 @@ static void SearchForOptions(FreeChatInputField textField) Result.Do(x => x.SetHidden(true)); ShouldReveal = false; - GameOptionsMenuPatch.ReOpenSettings(false, ModGameOptionsMenu.TabIndex); + GameOptionsMenuPatch.ReOpenSettings(ModGameOptionsMenu.TabIndex); _ = new LateTask(() => ShouldReveal = true, 0.38f); } } From 6d4d194848543210cd548f541afa6e21382eb24e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:47:36 +0200 Subject: [PATCH 125/778] ishidden improvement --- Modules/OptionItem/OptionItem.cs | 15 ++++----------- Patches/GameOptionsMenuPatch.cs | 2 +- Patches/GameSettingMenuPatch.cs | 11 ++++------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index bbb4923e3d..f976541331 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -181,27 +181,20 @@ public virtual string GetString() // Deprecated IsHidden function public virtual bool IsHiddenOn(CustomGameMode mode) { - return IsHidden || CheckSearchHidden() || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); + return CheckHidden() || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); } - - public bool CheckSearchHidden() + private bool CheckHidden() { - if (!GameSettingMenuPatch.SearchWinners.Any()) - return false; - var LastParent = this.Id; + for (var i = 0; i < 5; i++) { if (AllOptions.First(x => x.Id == LastParent).Parent == null) break; LastParent = AllOptions.First(x => x.Id == LastParent).Parent.Id; } - if (!GameSettingMenuPatch.SearchWinners.Contains(AllOptions.First(x => x.Id == LastParent))) - return true; - - - return false; + return this.IsHidden || this.Parent?.IsHidden == true || AllOptions.First(x => x.Id == LastParent).IsHidden; } public string ApplyFormat(string value) { diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index e4a2e515ed..156c1fcbcd 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -277,7 +277,7 @@ public static void ReOpenSettings(int index = 4) }, 0.1f, "Click Edit Button"); - if (index > 3) + if (index < 3) return; // Change tab to Original Tab diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index bb6e5b4191..4a4b67716f 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -99,7 +99,6 @@ public static void StartPostfix(GameSettingMenu __instance) if (ShouldReveal) { HiddenBySearch.Do(x => x.SetHidden(false)); - SearchWinners.Clear(); HiddenBySearch.Clear(); } @@ -136,7 +135,7 @@ private static void SetDefaultButton(GameSettingMenu __instance) public static StringOption PresetBehaviour; public static FreeChatInputField InputField; public static List HiddenBySearch = []; - public static List SearchWinners = []; + // public static List SearchWinners = []; public static bool ShouldReveal = false; @@ -264,12 +263,11 @@ static void SearchForOptions(FreeChatInputField textField) if (ModGameOptionsMenu.TabIndex < 3) return; HiddenBySearch.Do(x => x.SetHidden(false)); - SearchWinners.Clear(); string text = textField.textArea.text.Trim().ToLower(); var Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; - SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); + var SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); if (!SearchWinners.Any()) { HiddenBySearch.Clear(); @@ -278,10 +276,10 @@ static void SearchForOptions(FreeChatInputField textField) } Result.Do(x => x.SetHidden(true)); - + ShouldReveal = false; GameOptionsMenuPatch.ReOpenSettings(ModGameOptionsMenu.TabIndex); - _ = new LateTask(() => ShouldReveal = true, 0.38f); + _ = new LateTask(() => ShouldReveal = true, 0.38f, "ShouldReveal: TRUE"); } } @@ -317,7 +315,6 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ { HiddenBySearch.Do(x => x.SetHidden(false)); HiddenBySearch.Clear(); - SearchWinners.Clear(); } if (tabNum < 3) return true; From 3cb5c011016d6263fc8c55ca29dca3f097fd4b00 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:31:47 +0200 Subject: [PATCH 126/778] fix for dark theme --- Patches/GameSettingMenuPatch.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 4a4b67716f..9d7db0aa95 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -421,7 +421,21 @@ public static bool Prefix(FreeChatInputField __instance) return true; } } - +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +public static class FixDarkThemeForSearchBar +{ + public static void Postfix() + { + if (!GameSettingMenu.Instance || !Main.DarkTheme.Value) return; + var field = GameSettingMenuPatch.InputField; + if (field != null) + { + field.background.color = new Color32(40, 40, 40, byte.MaxValue); + field.textArea.compoText.Color(Color.white); + field.textArea.outputText.color = Color.white; + } + } +} [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] public class RpcSyncSettingsPatch { From 822ec79b0558667216a6af42545c4052a080eb48 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:56:20 +0200 Subject: [PATCH 127/778] fix bug --- Patches/GameOptionsMenuPatch.cs | 2 +- Patches/GameSettingMenuPatch.cs | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 156c1fcbcd..7dd5425282 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -300,7 +300,7 @@ private static bool ValueChangedPrefix(GameOptionsMenu __instance, OptionBehavio } return false; } - private static void ReCreateSettings(GameOptionsMenu __instance) + public static void ReCreateSettings(GameOptionsMenu __instance) { if (ModGameOptionsMenu.TabIndex < 3) return; var modTab = (TabGroup)(ModGameOptionsMenu.TabIndex - 3); diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 9d7db0aa95..941c27ff7d 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -96,11 +96,8 @@ public static void StartPostfix(GameSettingMenu __instance) } } - if (ShouldReveal) - { HiddenBySearch.Do(x => x.SetHidden(false)); HiddenBySearch.Clear(); - } SetupAdittionalButtons(__instance); } @@ -135,9 +132,6 @@ private static void SetDefaultButton(GameSettingMenu __instance) public static StringOption PresetBehaviour; public static FreeChatInputField InputField; public static List HiddenBySearch = []; - // public static List SearchWinners = []; - - public static bool ShouldReveal = false; private static void SetupAdittionalButtons(GameSettingMenu __instance) { @@ -254,11 +248,11 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) passiveButton.OnClick = new(); passiveButton.OnClick.AddListener( (Action)(() => { - SearchForOptions(TextField); + SearchForOptions(__instance, TextField); })); - static void SearchForOptions(FreeChatInputField textField) + static void SearchForOptions(GameSettingMenu __instance, FreeChatInputField textField) { if (ModGameOptionsMenu.TabIndex < 3) return; @@ -277,15 +271,21 @@ static void SearchForOptions(FreeChatInputField textField) Result.Do(x => x.SetHidden(true)); - ShouldReveal = false; - GameOptionsMenuPatch.ReOpenSettings(ModGameOptionsMenu.TabIndex); - _ = new LateTask(() => ShouldReveal = true, 0.38f, "ShouldReveal: TRUE"); + GameOptionsMenuPatch.ReCreateSettings(GameOptionsMenuPatch.Instance); + textField.Clear(); } } [HarmonyPatch(nameof(GameSettingMenu.ChangeTab)), HarmonyPrefix] public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [HarmonyArgument(1)] bool previewOnly) { + if (HiddenBySearch.Any()) + { + HiddenBySearch.Do(x => x.SetHidden(false)); + GameOptionsMenuPatch.ReCreateSettings(GameOptionsMenuPatch.Instance); + HiddenBySearch.Clear(); + } + ModGameOptionsMenu.TabIndex = tabNum; GameOptionsMenu settingsTab; @@ -311,12 +311,6 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ } } - if (ShouldReveal) - { - HiddenBySearch.Do(x => x.SetHidden(false)); - HiddenBySearch.Clear(); - } - if (tabNum < 3) return true; var tabGroupId = (TabGroup)(tabNum - 3); From c59efce8289de5b67a5ca740cce77c4ed6a4246b Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:14:27 +0200 Subject: [PATCH 128/778] fix for russian language --- Patches/GameSettingMenuPatch.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 941c27ff7d..0cba2c360e 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -154,7 +154,12 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) PLabel.DestroyTranslator(); PLabel.text = GetString($"Preset_{OptionItem.CurrentPreset + 1}"); //PLabel.font = PLuLabel.font; - PLabel.fontSizeMax = 2.45f; PLabel.fontSizeMin = 2.45f; + float size = DestroyableSingleton.Instance.currentLanguage.languageID switch + { + SupportedLangs.Russian => 1.45f, + _ => 2.45f, + }; + (PLabel.fontSizeMax, PLabel.fontSizeMin) = (size, size); var TempMinus = GameObject.Find("MinusButton").gameObject; var GMinus = GameObject.Instantiate(__instance.GamePresetsButton.gameObject, preset.transform); @@ -224,7 +229,13 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) var FreeChatField = DestroyableSingleton.Instance.freeChatField; var TextField = GameObject.Instantiate(FreeChatField, ParentLeftPanel.parent); - TextField.transform.localScale = new Vector3(0.3f, 0.59f, 1); + float Ysize = DestroyableSingleton.Instance.currentLanguage.languageID switch + { + SupportedLangs.Russian => 0.69f, //nice + _ => 0.59f, + }; + + TextField.transform.localScale = new Vector3(0.3f, Ysize, 1); TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); TextField.textArea.outputText.font = PLuLabel.font; @@ -282,11 +293,13 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ if (HiddenBySearch.Any()) { HiddenBySearch.Do(x => x.SetHidden(false)); + GameOptionsMenuPatch.ReCreateSettings(GameOptionsMenuPatch.Instance); + HiddenBySearch.Clear(); } - ModGameOptionsMenu.TabIndex = tabNum; + if (!previewOnly || tabNum != 1) ModGameOptionsMenu.TabIndex = tabNum; GameOptionsMenu settingsTab; PassiveButton button; From 22698b0f5dacbf9c9ac50ce54f68ca408f4b6d3a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:42:53 +0200 Subject: [PATCH 129/778] fix russian again --- Patches/GameSettingMenuPatch.cs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 0cba2c360e..b88bb60992 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -229,13 +229,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) var FreeChatField = DestroyableSingleton.Instance.freeChatField; var TextField = GameObject.Instantiate(FreeChatField, ParentLeftPanel.parent); - float Ysize = DestroyableSingleton.Instance.currentLanguage.languageID switch - { - SupportedLangs.Russian => 0.69f, //nice - _ => 0.59f, - }; - - TextField.transform.localScale = new Vector3(0.3f, Ysize, 1); + TextField.transform.localScale = new Vector3(0.3f, 0.59f, 1); TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); TextField.textArea.outputText.font = PLuLabel.font; @@ -250,9 +244,17 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) Object.Destroy(button.FindChild("Disabled").FindChild("Icon").GetComponent()); Object.Destroy(button.transform.FindChild("Text").GetComponent()); - button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconActive.png", 100f); - button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconHover.png", 100f); - button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIcon.png", 100f); + button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconActive.png", 100f); + button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconHover.png", 100f); + button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIcon.png", 100f); + + if (DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.Russian) + { + Vector3 FixedScale = new(0.7f, 1f, 1f); + button.FindChild("Normal").FindChild("Background").transform.localScale = FixedScale; + button.FindChild("Hover").FindChild("Background").transform.localScale = FixedScale; + button.FindChild("Disabled").FindChild("Background").transform.localScale = FixedScale; + } PassiveButton passiveButton = button.GetComponent(); From 500f68fcc15c5b0f706259638582f7a99ce6c9fe Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:11:37 +0200 Subject: [PATCH 130/778] fix null error --- Patches/GameSettingMenuPatch.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index b88bb60992..df4687f1cf 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -275,7 +275,7 @@ static void SearchForOptions(GameSettingMenu __instance, FreeChatInputField text && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; var SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - if (!SearchWinners.Any()) + if (!SearchWinners.Any() || !ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out var settingsTab)) { HiddenBySearch.Clear(); Logger.SendInGame(GetString("SearchNoResult")); // okay so showpopup nor this will overlay the menu, but I use this just for the sound lol @@ -284,7 +284,7 @@ static void SearchForOptions(GameSettingMenu __instance, FreeChatInputField text Result.Do(x => x.SetHidden(true)); - GameOptionsMenuPatch.ReCreateSettings(GameOptionsMenuPatch.Instance); + GameOptionsMenuPatch.ReCreateSettings(settingsTab); textField.Clear(); } } @@ -295,8 +295,8 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ if (HiddenBySearch.Any()) { HiddenBySearch.Do(x => x.SetHidden(false)); - - GameOptionsMenuPatch.ReCreateSettings(GameOptionsMenuPatch.Instance); + if (ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out var GameSettingsTab)) + GameOptionsMenuPatch.ReCreateSettings(GameSettingsTab); HiddenBySearch.Clear(); } From 8ea2c44122275294990025932cc86a45389c24b6 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:26:32 +0200 Subject: [PATCH 131/778] forgot to remove lmao --- Patches/GameSettingMenuPatch.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index df4687f1cf..58cb0fc35d 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -137,12 +137,10 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) { var ParentLeftPanel = __instance.GamePresetsButton.transform.parent; - var cocztam = DestroyableSingleton.Instance; - var labeltag = GameObject.Find("ModeValue"); var preset = Object.Instantiate(labeltag, ParentLeftPanel); - preset.transform.localPosition = new Vector3(-3.33f, -0.45f, -2f); + preset.transform.localScale = new Vector3(0.65f, 0.63f, 1f); var SpriteRenderer = preset.GetComponentInChildren(); SpriteRenderer.color = Color.white; From fdd57e3785edd7b29ba268b06e3ff1428ebd4983 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 26 Jul 2024 22:47:32 +0200 Subject: [PATCH 132/778] accidentaly removed this, opps- --- Patches/ChatBubblePatch.cs | 21 +++++++++++++++++++++ Patches/GameSettingMenuPatch.cs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 9dbf168390..a87996c6d7 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -1,3 +1,4 @@ +using TMPro; using UnityEngine; namespace TOHE.Patches; @@ -28,4 +29,24 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe __instance.TextArea.color = Color.white; } } +} + +//Thanks https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Fixes/CursorPosPatch.cs + +/// +/// "Fixes" an issue where empty TextBoxes have wrong cursor positions. +/// +[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] +internal static class CursorPosPatch +{ + public static bool Prefix(TextMeshPro self, ref Vector2 __result) + { + if (self.textInfo == null || self.textInfo.lineCount == 0 || self.textInfo.lineInfo[0].characterCount <= 0) + { + __result = self.GetTextInfo(" ").lineInfo.First().lineExtents.max; + return false; + } + + return true; + } } \ No newline at end of file diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 58cb0fc35d..b7c5889b3d 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -139,7 +139,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) var labeltag = GameObject.Find("ModeValue"); var preset = Object.Instantiate(labeltag, ParentLeftPanel); - + preset.transform.localPosition = new Vector3(-3.33f, -0.45f, -2f); preset.transform.localScale = new Vector3(0.65f, 0.63f, 1f); var SpriteRenderer = preset.GetComponentInChildren(); From 6ad111ab4569893cbee6c4c7dedf61c9491182e3 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:37:06 +0000 Subject: [PATCH 133/778] New Role DoubleAgent --- Modules/CustomRolesHelper.cs | 3 +- Patches/MeetingHudPatch.cs | 7 + Resources/Lang/en_US.json | 20 ++ Roles/Crewmate/Bastion.cs | 7 +- Roles/Impostor/DoubleAgent.cs | 371 ++++++++++++++++++++++++++++++++++ Roles/Neutral/Agitater.cs | 23 ++- main.cs | 1 + 7 files changed, 422 insertions(+), 10 deletions(-) create mode 100644 Roles/Impostor/DoubleAgent.cs diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 137c1bad30..05a7a33027 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -869,7 +869,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.BountyHunter) || pc.Is(CustomRoles.Lightning) || pc.Is(CustomRoles.Hangman) - || pc.Is(CustomRoles.TicketsStealer)) + || pc.Is(CustomRoles.TicketsStealer) + || pc.Is(CustomRoles.DoubleAgent)) return false; if (!pc.GetCustomRole().IsImpostor()) return false; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 1a628dbe9e..df8c9581bd 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -671,6 +671,13 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } break; + case CustomRoles.DoubleAgent: + if (!DoubleAgent.OnVotes(voter, target)) + { + __instance.RpcClearVote(voter.GetClientId()); + return false; + } + break; } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6dcf743679..5c297118a4 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -56,7 +56,11 @@ "Role_Sheriff": "Sheriff", "Role_Knight": "Knight", "Role_Deputy": "Deputy", + "Role_AdmiredImpostor": "Admired Impostor", + "Role_Trickster": "Trickster", + "Role_Traitor": "Traitor", "Role_NoChange": "Don't change the role", + "Role_Random": "Random role from list", "CrewmatesCanGuess": "Crewmates can guess", "ImpostorsCanGuess": "Impostors can guess", @@ -376,6 +380,7 @@ "Tired": "Tired", "Statue": "Statue", "DollMaster": "Dollmaster", + "DoubleAgent": "Double Agent", "BracketAddons": "Add Brackets To Add-ons", "EngineerTOHEInfo": "Use the vents to catch the Impostors", "ScientistTOHEInfo": "Access portable vitals from anywhere", @@ -672,6 +677,7 @@ "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", "RainbowInfo": "Colorful melodies! You don't even know your own color.", "DollMasterInfo": "Take control of players actions!", + "DoubleAgentInfo": "Plant bombs on players in meetings", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", @@ -968,6 +974,7 @@ "GhastlyInfoLong": "(Crewmates [Ghost]):\nAs the Ghastly, possess an unsuspecting person, after that choose a target for them, now they'll only be able to use their kill (or kill ability) on target until you possess someone else or possess time runs out.", "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", + "DoubleAgentInfoLong": "(Impostor):\nAs the Double Agent, you do not have access to the kill button but. You can vote someone in a meeting to pass a bomb onto them, this can only be done one player at a time, once the meeting has finished the bomb will activate and explode in a set amount of time.\nNote when you pass the bomb onto someone in a meeting you will be able to vote afterwards.\n\nAdditionally depending on settings the Double Agent can diffuse Bastion and Agitator bombs when venting.\n\nThe Double Agent can change roles when they are the Last Imposter, depending on the settings the role can be a Admired Impostor, Trickster, Traitor, or stay as the Double Agent.", "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", @@ -1306,6 +1313,19 @@ "DollMasterPossessionDuration": "Possession Duration", "DollMasterCanKillAsMainBody": "Can kill as the main body", "DollMasterTargetDiesAfterPossession": "Target dies after possession", + + "DoubleAgentCanDiffuseBombs": "Double Agent can diffuse bombs from other roles", + "DoubleAgentClearBombOnMeetingCall": "Diffuse active bomb on meeting call", + "DoubleAgentCanUseAbilityInCalledMeeting": "If diffused can use ability in called meeting", + "DoubleAgentBombExplosionTimer": "Explosion time", + "DoubleAgentExplosionRadius": "Explosion radius", + "DoubleAgent_DiffusedAgitaterBomb": "Agitator bomb successfully diffused", + "DoubleAgent_DiffusedBastionBomb": "Bastion bomb successfully diffused", + "DoubleAgent_BombExplodesIn": "Bomb Explodes In: {0}s", + "DoubleAgent_BombExploded": "Bomb has exploited!", + "DoubleAgentChangeRoleTo": "Change role on last Imposter", + "DoubleAgentRoleChange": "You have become a: ", + "MastermindCD": "Manipulate Cooldown", "MastermindTimeLimit": "Time limit to kill someone", "MastermindDelay": "Manipulation notification delay", diff --git a/Roles/Crewmate/Bastion.cs b/Roles/Crewmate/Bastion.cs index 759b6bb264..5350d811bd 100644 --- a/Roles/Crewmate/Bastion.cs +++ b/Roles/Crewmate/Bastion.cs @@ -24,7 +24,7 @@ internal class Bastion : RoleBase private static OptionItem BastionAbilityUseGainWithEachTaskCompleted; private static OptionItem BastionMaxBombs; - private static readonly HashSet BombedVents = []; + public static readonly HashSet BombedVents = []; public override void SetupCustomOption() { @@ -85,6 +85,11 @@ public override bool OnCoEnterVentOthers(PlayerPhysics physics, int ventId) Logger.Info("Crewmate enter in bombed vent, bombed is cancel", "Bastion.OnCoEnterVentOther"); return false; } + else if (pc.Is(CustomRoles.DoubleAgent)) + { + Logger.Info("DoubleAgent enter in bombed vent, bombed is cancel", "Bastion.OnCoEnterVentOther"); + return false; + } else { _ = new LateTask(() => diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs new file mode 100644 index 0000000000..8fcfe6ef15 --- /dev/null +++ b/Roles/Impostor/DoubleAgent.cs @@ -0,0 +1,371 @@ +using UnityEngine; +using static TOHE.Options; +using static TOHE.Translator; +using TOHE.Roles.Core; +using TOHE.Roles.Crewmate; +using TOHE.Modules; +using TOHE.Roles.Neutral; +using Hazel; +using InnerNet; +using System; + +namespace TOHE.Roles.Impostor; +internal class DoubleAgent : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 28600; + private static readonly List playerIdList = []; + public static bool HasEnabled => playerIdList.Any(); + public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorSupport; + //==================================================================\\ + private static readonly List createdButtonsList = []; + private static readonly List CurrentBombedPlayers = []; + private static float CurrentBombedTime = float.MaxValue; + public static bool BombIsActive = false; + public static bool CanBombInMeeting = true; + public static bool StartedWithMoreThanOneImp = false; + + public static OptionItem DoubleAgentCanDiffuseBombs; + private static OptionItem ClearBombedOnMeetingCall; + private static OptionItem CanUseAbilityInCalledMeeting; + private static OptionItem BombExplosionTimer; + private static OptionItem ExplosionRadius; + private static OptionItem ChangeRoleToOnLast; + + private enum ChangeRolesSelectOnLast + { + Role_NoChange, + Role_Random, + Role_AdmiredImpostor, // Team Crewmate + Role_Traitor, // Team Neutral + Role_Trickster, // Team Impostor as Crewmate + } + public static readonly CustomRoles[] CRoleChangeRoles = + [ + 0, // NoChange + 0, // Random + CustomRoles.ImpostorTOHE, // Team Crewmate + CustomRoles.Traitor, // Team Neutral + CustomRoles.Trickster, // Team Impostor as Crewmate + ]; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.DoubleAgent); + DoubleAgentCanDiffuseBombs = BooleanOptionItem.Create(Id + 10, "DoubleAgentCanDiffuseBombs", true, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); + ClearBombedOnMeetingCall = BooleanOptionItem.Create(Id + 11, "DoubleAgentClearBombOnMeetingCall", true, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); + CanUseAbilityInCalledMeeting = BooleanOptionItem.Create(Id + 12, "DoubleAgentCanUseAbilityInCalledMeeting", false, TabGroup.ImpostorRoles, false).SetParent(ClearBombedOnMeetingCall); + BombExplosionTimer = FloatOptionItem.Create(Id + 13, "DoubleAgentBombExplosionTimer", new(10f, 60f, 2.5f), 20f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]) + .SetValueFormat(OptionFormat.Seconds); + ExplosionRadius = FloatOptionItem.Create(Id + 14, "DoubleAgentExplosionRadius", new(0.5f, 2f, 0.1f), 1.0f, TabGroup.ImpostorRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]) + .SetValueFormat(OptionFormat.Multiplier); + ChangeRoleToOnLast = StringOptionItem.Create(Id + 15, "DoubleAgentChangeRoleTo", EnumHelper.GetAllNames(), 1, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); + } + public override void Init() + { + ClearBomb(); + playerIdList.Clear(); + CurrentBombedPlayers.Clear(); + CurrentBombedTime = float.MaxValue; + BombIsActive = false; + StartedWithMoreThanOneImp = false; + CanBombInMeeting = true; + } + + public override void Add(byte playerId) + { + playerIdList.Add(playerId); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); + if (Main.AllAlivePlayerControls.Count(player => player.Is(Custom_Team.Impostor)) > 1) + StartedWithMoreThanOneImp = true; + } + + public static void ClearBomb() + { + CurrentBombedPlayers.Clear(); + CurrentBombedTime = 999f; + BombIsActive = false; + } + + // On vent diffuse Bastion & Agitator Bomb if DoubleAgentCanDiffuseBombs is enabled. + // Dev Note: Add role check for OnCoEnterVentOthers and make BombedVents public in Bastion.cs. + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (DoubleAgentCanDiffuseBombs.GetBool()) + { + if (pc.PlayerId == Agitater.CurrentBombedPlayer) + { + Agitater.ResetBomb(); + PlaySoundForAll("Boom"); + _ = new LateTask(() => + { + if (pc.inVent) pc.MyPhysics.RpcBootFromVent(vent.Id); + pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_DiffusedAgitaterBomb"))); + }, 0.8f, "Boot Player from vent: " + vent.Id); + return; + } + + if (Bastion.BombedVents.Contains(vent.Id)) + { + Bastion.BombedVents.Remove(vent.Id); + _ = new LateTask(() => + { + if (pc.inVent) pc.MyPhysics.RpcBootFromVent(vent.Id); + pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_DiffusedBastionBomb"))); + }, 0.5f, "Boot Player from vent: " + vent.Id); + } + } + } + + public override bool CanUseKillButton(PlayerControl pc) => false; + + // Use vote for Non-Modded! + // Plant bomb on first vote that isn't another imposter or self. + // Dev Note: Add this to CastVotePatch in MeetingHudPatch.cs like it is for keeper. + public static bool OnVotes(PlayerControl voter, PlayerControl target) + { + if (voter.IsModClient()) return true; + if (!CanBombInMeeting) return true; + + if (!BombIsActive) + { + if (target.GetCustomRole().GetCustomRoleTeam() == Custom_Team.Impostor) return false; + if (voter == target) return false; + + CurrentBombedTime = 999f; + CurrentBombedPlayers.Add(target.PlayerId); + BombIsActive = true; + return false; + } + return true; + } + + // Clear active bombed players on meeting call if ClearBombedOnMeetingCall is enabled. + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + + if (BombIsActive && ClearBombedOnMeetingCall.GetBool()) + { + ClearBomb(); + if (ClearBombedOnMeetingCall.GetBool() && !CanUseAbilityInCalledMeeting.GetBool()) CanBombInMeeting = false; + } + else + CurrentBombedTime = 999f; + } + + // If bomb is active set timer after meeting. + public override void AfterMeetingTasks() + { + CurrentBombedTime = BombExplosionTimer.GetFloat() + 1f; + CanBombInMeeting = true; + } + + // If enabled and if DoubleAgent is last Impostor become set role. + public override void OnFixedUpdate(PlayerControl pc) + { + if (ChangeRoleToOnLast.GetValue() != 0 && StartedWithMoreThanOneImp && GameStates.IsInTask && !GameStates.IsMeeting && !GameStates.IsExilling) + { + if (pc.Is(CustomRoles.DoubleAgent) && Main.AllAlivePlayerControls.Count(player => player.Is(Custom_Team.Impostor)) < 2) + { + var Role = CRoleChangeRoles[ChangeRoleToOnLast.GetValue()]; + if (ChangeRoleToOnLast.GetValue() == 1) // Random + Role = CRoleChangeRoles[UnityEngine.Random.RandomRangeInt(2, CRoleChangeRoles.Length)]; + + // If role is not on Impostor team remove all Impostor addons if any. + if (!Role.IsImpostorTeam()) + { + foreach (CustomRoles allAddons in pc.GetCustomSubRoles()) + { + if (allAddons.IsImpOnlyAddon()) + { + pc.GetCustomSubRoles()?.Remove(allAddons); + } + } + } + // If Role is ImpostorTOHE aka Admired Impostor opt give Admired Addon if player dose not already have it. + if (Role == CustomRoles.ImpostorTOHE && !pc.GetCustomSubRoles().Contains(CustomRoles.Admired)) + pc.GetCustomSubRoles()?.Add(CustomRoles.Admired); + + Init(); + pc.RpcSetCustomRole(Role); + pc.GetRoleClass()?.Add(pc.PlayerId); + pc.MarkDirtySettings(); + + string RoleName = Utils.ColorString(Utils.GetRoleColor(pc.GetCustomRole()), Utils.GetRoleName(pc.GetCustomRole())); + if (Role == CustomRoles.ImpostorTOHE) + RoleName = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Admired), $"{GetString("Admired")} {GetString("ImpostorTOHE")}"); + pc.Notify(Utils.ColorString(Utils.GetRoleColor(pc.GetCustomRole()), GetString("DoubleAgentRoleChange") + RoleName)); + } + } + + if (CurrentBombedPlayers.Any(playerId => Utils.GetPlayerById(playerId) == null)) // If playerId is a null Player clear bomb. + ClearBomb(); + } + + // Active bomb timer update and check. + private void OnFixedUpdateOthers(PlayerControl player) + { + if (!CurrentBombedPlayers.Contains(player.PlayerId)) return; + + if (!player.IsAlive()) // If Player is dead clear bomb. + ClearBomb(); + + if (BombIsActive && (GameStates.IsInTask && GameStates.IsInGame) && !(GameStates.IsMeeting && GameStates.IsExilling)) + { + var OldCurrentBombedTime = (int)CurrentBombedTime; + + CurrentBombedTime -= Time.deltaTime; + + if (OldCurrentBombedTime > (int)CurrentBombedTime && CurrentBombedTime < (BombExplosionTimer.GetFloat() + 1)) + SendRPC(); + + if (CurrentBombedTime < 1) + BoomBoom(player); + } + } + + // Set timer on Double Agent for Non-Modded Clients. + public override void OnFixedUpdateLowLoad(PlayerControl pc) + { + if (BombIsActive) + { + if (!pc.IsModClient()) + { + string Duration = Utils.ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); + if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Item1 != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); + } + } + } + + // Players go bye bye ¯\_(ツ)_/¯ + private void BoomBoom(PlayerControl player) + { + if (player.inVent) player.MyPhysics.RpcBootFromVent(GetPlayerVentId(player)); + + foreach (PlayerControl target in Main.AllAlivePlayerControls) // Get players in radius of bomb that are not in a vent. + { + if (CheckForPlayersInRadius(player, target) <= ExplosionRadius.GetFloat()) + { + if (player.inVent) return; + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + target.RpcMurderPlayer(target); + } + } + + PlaySoundForAll("Boom"); + ClearBomb(); + + _Player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); + } + + private static float CheckForPlayersInRadius(PlayerControl player, PlayerControl target) => Vector2.Distance(player.GetCustomPosition(), target.GetCustomPosition()); + + // Play specific sound for all players. + private static void PlaySoundForAll(string Sound) + { + foreach (PlayerControl player in Main.AllPlayerControls) + { + player.RPCPlayCustomSound(Sound); + } + } + + // Get vent Id that the player is in. + private static int GetPlayerVentId(PlayerControl pc) + { + if (!(ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Ventilation, out var systemType) && + systemType.TryCast() is VentilationSystem ventilationSystem)) + return 0; + + return ventilationSystem.PlayersInsideVents.TryGetValue(pc.PlayerId, out var playerIdVentId) ? playerIdVentId : 0; + } + + // Set bomb mark on player. + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + { + if (seen == null ) return string.Empty; + if (CurrentBombedPlayers.Contains(seen.PlayerId)) return Utils.ColorString(Color.red, "Ⓑ"); // L Rizz :) + return string.Empty; + } + + + // Set timer for Double Agent Modded Clients. + public override string GetLowerText(PlayerControl player, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + { + if (player == null) return string.Empty; + if (CurrentBombedTime > 0 && CurrentBombedTime < BombExplosionTimer.GetFloat() + 1) return Utils.ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); + return string.Empty; + } + + // Send bomb timer to Modded Clients when active. + private void SendRPC() + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.None, -1); + writer.WriteNetObject(_Player); + writer.Write((int)CurrentBombedTime); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + // Receive and set bomb timer from Host when active. + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + CurrentBombedTime = reader.ReadInt32(); + } + + // Use button for Modded! + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.DoubleAgent) && PlayerControl.LocalPlayer.IsAlive() && CanBombInMeeting && !BombIsActive) + CreatePlantBombButton(__instance); + } + } + public static void CreatePlantBombButton(MeetingHud __instance) + { + foreach (var pva in __instance.playerStates) + { + var pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + if (pc.GetCustomRole().GetCustomRoleTeam() == Custom_Team.Impostor || PlayerControl.LocalPlayer == pc) continue; + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = UnityEngine.Object.Instantiate(template, pva.transform); + targetBox.name = "PlantBombButton"; + targetBox.transform.localPosition = new Vector3(-0.35f, 0.03f, -1.31f); + createdButtonsList.Add(targetBox); + SpriteRenderer renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("DoubleAgentPocketBomb"); + PassiveButton button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => DestroyButtons(targetBox))); + button.OnClick.AddListener((Action)(() => PlantBombOnClick(pva.TargetPlayerId /*, __instance*/))); + button.OnClick.AddListener((Action)(() => CustomSoundsManager.Play("Line"))); + } + } + + private static void PlantBombOnClick(byte targetId /*, MeetingHud __instance*/) + { + if (BombIsActive) return; + + CurrentBombedTime = 999f; + CurrentBombedPlayers.Add(targetId); + BombIsActive = true; + } + + private static void DestroyButtons(GameObject pressedButton) + { + foreach (var button in createdButtonsList.Where(button => button != pressedButton)) + UnityEngine.Object.Destroy(button); + createdButtonsList.Clear(); + + pressedButton.GetComponent().enabled = false; + Transform highlightTransform = pressedButton.transform.Find("ControllerHighlight"); + GameObject highlightObject = highlightTransform?.gameObject; + highlightObject?.SetActive(false); + } +} + +// FieryFlower was here ඞ \ No newline at end of file diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index 138226afbb..0b77b171e4 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -24,11 +24,11 @@ internal class Agitater : RoleBase private static OptionItem AgitaterAutoReportBait; private static OptionItem HasImpostorVision; - public byte CurrentBombedPlayer = byte.MaxValue; - public byte LastBombedPlayer = byte.MaxValue; - public bool AgitaterHasBombed = false; - public long? CurrentBombedPlayerTime = new(); - public long? AgitaterBombedTime = new(); + public static byte CurrentBombedPlayer = byte.MaxValue; + public static byte LastBombedPlayer = byte.MaxValue; + public static bool AgitaterHasBombed = false; + public static long? CurrentBombedPlayerTime = new(); + public static long? AgitaterBombedTime = new(); public override void SetupCustomOption() @@ -62,13 +62,20 @@ public override void Add(byte playerId) Main.ResetCamPlayerList.Add(playerId); } - public void ResetBomb() + public static void ResetBomb() { - CurrentBombedPlayer = byte.MaxValue; + CurrentBombedPlayer = 254; CurrentBombedPlayerTime = new(); LastBombedPlayer = byte.MaxValue; AgitaterHasBombed = false; - SendRPC(CurrentBombedPlayer, LastBombedPlayer); + } + public override void OnFixedUpdate(PlayerControl pc) + { + if (CurrentBombedPlayer == 254) + { + SendRPC(CurrentBombedPlayer, LastBombedPlayer); + CurrentBombedPlayer = byte.MaxValue; + } } public override bool CanUseKillButton(PlayerControl pc) => true; public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = AgiTaterBombCooldown.GetFloat(); diff --git a/main.cs b/main.cs index b94b800bdd..d4d57108c3 100644 --- a/main.cs +++ b/main.cs @@ -578,6 +578,7 @@ public enum CustomRoles Devourer, Disperser, DollMaster, + DoubleAgent, Eraser, Escapist, EvilGuesser, From 92f4438a955d2d73fd502c166262f25108947103 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:31:32 +0200 Subject: [PATCH 134/778] add enter command, AND STOP FUCKING ME UP API ISTG --- Patches/ControlPatch.cs | 10 ++++++++-- Patches/GameSettingMenuPatch.cs | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 77ae1b16b5..2005bf9d5d 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -212,8 +212,14 @@ public static void Postfix(/*ControllerManager __instance*/) } } - // Forse start/end meeting - if (GetKeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) + if (GetKeysDown(KeyCode.Return) && GameSettingMenuPatch.Instance != null && GameSettingMenuPatch.Instance.isActiveAndEnabled == true) + { + Logger.Info("Serarch for options thingy", "Triggered yay!!"); + GameSettingMenuPatch._SearchForOptions?.Invoke(); + } + + // Forse start/end meeting + if (GetKeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) { if (GameStates.IsHideNSeek) return; diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index b7c5889b3d..8294c7f53b 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -21,11 +21,14 @@ public class GameSettingMenuPatch static Dictionary ModSettingsButtons = []; static Dictionary ModSettingsTabs = []; + public static GameSettingMenu Instance; [HarmonyPatch(nameof(GameSettingMenu.Start)), HarmonyPrefix] [HarmonyPriority(Priority.First)] public static void StartPostfix(GameSettingMenu __instance) { + Instance = __instance; + TabGroup[] ExludeList = Options.CurrentGameMode switch { CustomGameMode.HidenSeekTOHE => Enum.GetValues().Skip(3).ToArray(), @@ -132,6 +135,7 @@ private static void SetDefaultButton(GameSettingMenu __instance) public static StringOption PresetBehaviour; public static FreeChatInputField InputField; public static List HiddenBySearch = []; + public static Action _SearchForOptions; private static void SetupAdittionalButtons(GameSettingMenu __instance) { @@ -259,11 +263,17 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) passiveButton.OnClick = new(); passiveButton.OnClick.AddListener( (Action)(() => { - SearchForOptions(__instance, TextField); + SearchForOptions(TextField); })); + _SearchForOptions = (() => { + if (TextField.textArea.text == string.Empty) + return; + + passiveButton.ReceiveClickDown(); + }); - static void SearchForOptions(GameSettingMenu __instance, FreeChatInputField textField) + static void SearchForOptions(FreeChatInputField textField) { if (ModGameOptionsMenu.TabIndex < 3) return; From 9a09df1de4bd28cb5e18ba09ca42cabb4e3b6b96 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:32:59 +0200 Subject: [PATCH 135/778] forgot remove --- Patches/ControlPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 2005bf9d5d..c8f28fbafd 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -214,7 +214,6 @@ public static void Postfix(/*ControllerManager __instance*/) if (GetKeysDown(KeyCode.Return) && GameSettingMenuPatch.Instance != null && GameSettingMenuPatch.Instance.isActiveAndEnabled == true) { - Logger.Info("Serarch for options thingy", "Triggered yay!!"); GameSettingMenuPatch._SearchForOptions?.Invoke(); } From f5dde65c790b649cb9986759ca8b2777ec6fc0ef Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:55:09 +0200 Subject: [PATCH 136/778] fix bug --- Patches/GameSettingMenuPatch.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 8294c7f53b..c6453a0046 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -77,10 +77,6 @@ public static void StartPostfix(GameSettingMenu __instance) ModSettingsButtons.Add(tab, button); } - ModGameOptionsMenu.OptionList = new(); - ModGameOptionsMenu.BehaviourList = new(); - ModGameOptionsMenu.CategoryHeaderList = new(); - ModSettingsTabs = []; foreach (var tab in EnumHelper.GetAllValues()) { @@ -399,6 +395,9 @@ private static bool OnEnablePrefix(GameSettingMenu __instance) TemplateGameSettingsButton = Object.Instantiate(__instance.GameSettingsButton, __instance.GameSettingsButton.transform.parent); TemplateGameSettingsButton.gameObject.SetActive(false); } + ModGameOptionsMenu.OptionList = new(); + ModGameOptionsMenu.BehaviourList = new(); + ModGameOptionsMenu.CategoryHeaderList = new(); SetDefaultButton(__instance); From 6e3c2445cde89494b37fea8e0ff96907e861c0e5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:37:50 -0400 Subject: [PATCH 137/778] things --- Patches/ChatCommandPatch.cs | 4 ++++ Roles/Neutral/Baker.cs | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index a35fd5257e..919757d6aa 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2079,6 +2079,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can var allAlivePlayers = Main.AllAlivePlayerControls; int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); var sub = new StringBuilder(); @@ -2087,6 +2088,9 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can if (Options.ShowMadmatesInLeftCommand.GetBool()) sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); Utils.SendMessage(sub.ToString(), player.PlayerId); diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 88ee88cf01..e4ac0e87cd 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -285,7 +285,7 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } } CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Starved, [.. deathList]); - BreadList.Clear(); + BreadList[baker.PlayerId].Clear(); StarvedNonBreaded = true; } } @@ -324,7 +324,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr { Baker.FamineList[killer.PlayerId].Add(target.PlayerId); Baker.SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); + NotifyRoles(SpecifySeer: killer); killer.Notify(GetString("FamineStarved")); Logger.Info(target.GetRealName() + $" has been starved", "Famine"); } @@ -346,12 +346,12 @@ public override void OnReportDeadBody(PlayerControl sylveon, NetworkedPlayerInfo { foreach (var tar in pc.Value) { - var target = Utils.GetPlayerById(tar); - var killer = Utils.GetPlayerById(pc.Key); + var target = GetPlayerById(tar); + var killer = GetPlayerById(pc.Key); if (killer == null || target == null) continue; target.RpcExileV2(); target.SetRealKiller(killer); - Main.PlayerStates[tar].deathReason = PlayerState.DeathReason.Starved; + tar.SetDeathReason(PlayerState.DeathReason.Starved); Main.PlayerStates[tar].SetDead(); MurderPlayerPatch.AfterPlayerDeathTasks(killer, target, true); Logger.Info($"{killer.GetRealName()} has starved {target.GetRealName()}", "Famine"); From c0df81f4dc302f581b7aa2a9cea3cd015264f3e9 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:01:02 -0400 Subject: [PATCH 138/778] bers and baker to copycat --- Roles/(Ghosts)/Crewmate/Hawk.cs | 2 +- Roles/(Ghosts)/Impostor/Bloodmoon.cs | 2 +- Roles/Crewmate/CopyCat.cs | 16 ++++++++++++++++ Roles/Neutral/Baker.cs | 1 + 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Roles/(Ghosts)/Crewmate/Hawk.cs b/Roles/(Ghosts)/Crewmate/Hawk.cs index 501337a389..042d5ac154 100644 --- a/Roles/(Ghosts)/Crewmate/Hawk.cs +++ b/Roles/(Ghosts)/Crewmate/Hawk.cs @@ -109,7 +109,7 @@ private bool CheckRetriConflicts(PlayerControl target) return target != null && Main.AllAlivePlayerControls.Length >= MinimumPlayersAliveToKill.GetInt() && AbilityLimit > 0 && rnd.Next(100) >= KillerChanceMiss[target.PlayerId] - && !target.Is(CustomRoles.Pestilence) + && !target.IsNeutralApocalypse() && !target.Is(CustomRoles.Jinx) && !target.Is(CustomRoles.CursedWolf) && (!target.Is(CustomRoles.NiceMini) || Mini.Age > 18); diff --git a/Roles/(Ghosts)/Impostor/Bloodmoon.cs b/Roles/(Ghosts)/Impostor/Bloodmoon.cs index 8ea554029d..fdc15f4074 100644 --- a/Roles/(Ghosts)/Impostor/Bloodmoon.cs +++ b/Roles/(Ghosts)/Impostor/Bloodmoon.cs @@ -63,7 +63,7 @@ public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) if (AbilityLimit > 0 && !target.Is(CustomRoles.Jinx) && !target.Is(CustomRoles.CursedWolf) - && !target.Is(CustomRoles.Pestilence) + && !target.IsNeutralApocalypse() && killer.RpcCheckAndMurder(target, true) && !PlayerDie.ContainsKey(target.PlayerId)) { diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 651c7b7eb9..abcf0f27bc 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -1,4 +1,5 @@ using TOHE.Roles.Core; +using TOHE.Roles.Neutral; using static TOHE.Options; using static TOHE.Translator; @@ -133,6 +134,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr break; case CustomRoles.Arrogance: case CustomRoles.Juggernaut: + case CustomRoles.Berserker: role = CustomRoles.Reverie; break; //case CustomRoles.EvilGuesser: @@ -151,6 +153,20 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr case CustomRoles.Pursuer: role = CustomRoles.Deceiver; break; + case CustomRoles.Baker: + switch (Baker.CurrentBread()) + { + case 0: + role = CustomRoles.Overseer; + break; + case 1: + role = CustomRoles.Deputy; + break; + case 2: + role = CustomRoles.Crusader; // medic would make more sense but medic cant be used + break; + } + break; } } if (role.IsCrewmate()) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index e4ac0e87cd..ca91a1ee01 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -79,6 +79,7 @@ private static (int, int) BreadedPlayerCount(byte playerId) } return (breaded, all); } + public static byte CurrentBread() => BreadID; public static void SendRPC(PlayerControl player, PlayerControl target) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); From 5642695ae6c4636ff2a7aa82fa0cc0a7c8da3fbc Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:02:53 -0400 Subject: [PATCH 139/778] update colors and stuff --- Resources/Lang/en_US.json | 7497 +++++++++++++++++++------------------ Resources/roleColor.json | 4 +- Roles/Neutral/Baker.cs | 7 +- 3 files changed, 3757 insertions(+), 3751 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 608fcca342..52b2bd1052 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1,3750 +1,3751 @@ { - "LanguageID": "0", - "HostText": "Host", - "HostColor": "#902efd", - "IconColor": "#4bf4ff", - "Icon": "♥", - "NameColor": "#ffc0cb", - - "HideHostText": "Hide 'Host♥' Text", - "HideAllTagsAndText": "Hide All Tags (for «AutoMuteUs»)", - - "SupportUs": "Support Us", - "update": "Update", - "GitHub": "GitHub", - "Discord": "Discord", - "Website": "Website", - "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", - - "HostIconInMeeting": "HOST: {0}", - - "SubText.Crewmate": "Find and exile the Impostors", - "SubText.Impostor": "Sabotage and kill everyone", - "SubText.Neutral": "Work alone to achieve your victory", - "SubText.Apocalypse": "Become unstoppable with your team", - "SubText.Madmate": "Help the Impostors", - - "TypeImpostor": "Impostors", - "TypeCrewmate": "Crewmates", - "TypeNeutral": "Neutrals", - "TypeAddon": "Add-ons", - "GuesserMode": "Guesser Mode", - - "TeamImpostor": "Impostor", - "TeamNeutral": "Neutral", - "TeamCrewmate": "Crewmate", - "TeamMadmate": "Madmate", - - "YouAreCrewmate": "You are a Crewmate", - "YouAreImpostor": "You are an Impostor", - "YouAreNeutral": "You are a Neutral", - "YouAreMadmate": "You are a Madmate", - - - "Role_Crewmate": "Crewmate", - "Role_Jester": "Jester", - "Role_Opportunist": "Opportunist", - "Role_Celebrity": "Celebrity", - "Role_Bodyguard": "Bodyguard", - "Role_Dictator": "Dictator", - "Role_Mayor": "Mayor", - "Role_Doctor": "Doctor", - "Role_Maverick": "Maverick", - "Role_Pursuer": "Pursuer", - "Role_Follower": "Follower", - "Role_Amnesiac": "Amnesiac", - "Role_Imitator": "Imitator", - "Role_Sheriff": "Sheriff", - "Role_Knight": "Knight", - "Role_Deputy": "Deputy", - "Role_NoChange": "Don't change the role", - - "CrewmatesCanGuess": "Crewmates can guess", - "ImpostorsCanGuess": "Impostors can guess", - "NeutralKillersCanGuess": "Neutral Killers can guess", - "NeutralApocalypseCanGuess": "Neutral Apocalypse can guess", - "PassiveNeutralsCanGuess": "Passive Neutrals can guess", - - "CanGuessAddons": "Can Guess Add-ons", - "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", - "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", - "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", - "GuessImmune": "Sorry, but target is immune to being guessed!", - - - "GM": "Game Master", - "Sunnyboy": "Sunnyboy", - "Bard": "Bard", - "Crewmate": "Crewmate", - "CrewmateTOHE": "Crewmate", - "Engineer": "Engineer", - "EngineerTOHE": "Engineer", - "Scientist": "Scientist", - "ScientistTOHE": "Scientist", - "Noisemaker": "Noisemaker", - "NoisemakerTOHE": "Noisemaker", - "Tracker": "Tracker", - "TrackerTOHE": "Tracker", - "GuardianAngel": "Guardian Angel", - "GuardianAngelTOHE": "Guardian Angel", - "Impostor": "Impostor", - "ImpostorTOHE": "Impostor", - "Shapeshifter": "Shapeshifter", - "ShapeshifterTOHE": "Shapeshifter", - "Phantom": "Phantom", - "PhantomTOHE": "Phantom", - - "BountyHunter": "Bounty Hunter", - "Fireworker": "Fireworker", - "Mercenary": "Mercenary", - "ShapeMaster": "Shapemaster", - "Vampire": "Vampire", - "Warlock": "Warlock", - "Ninja": "Ninja", - "Zombie": "Zombie", - "Anonymous": "Anonymous", - "Miner": "Miner", - "KillingMachine": "Killing Machine", - "Escapist": "Escapist", - "Witch": "Witch", - "Nemesis": "Nemesis", - "Bloodmoon": "Bloodmoon", - "Puppeteer": "Puppeteer", - "Mastermind": "Mastermind", - "TimeThief": "Time Thief", - "Sniper": "Sniper", - "Undertaker": "Undertaker", - "RiftMaker": "Rift Maker", - "EvilTracker": "Evil Tracker", - "EvilHacker": "Evil Hacker", - "EvilGuesser": "Evil Guesser", - "AntiAdminer": "Anti Adminer", - "Arrogance": "Arrogance", - "Bomber": "Bomber", - "Scavenger": "Scavenger", - "Trapster": "Trapster", - "Gangster": "Gangster", - "Cleaner": "Cleaner", - "Lightning": "Lightning", - "Greedy": "Greedy", - "CursedWolf": "Cursed Wolf", - "SoulCatcher": "Soul Catcher", - "QuickShooter": "Quick Shooter", - "Camouflager": "Camouflager", - "Eraser": "Eraser", - "Butcher": "Butcher", - "Hangman": "Hangman", - "Swooper": "Swooper", - "Crewpostor": "Crewpostor", - "Wildling": "Wildling", - "Trickster": "Trickster", - "Vindicator": "Vindicator", - "Parasite": "Parasite", - "Disperser": "Disperser", - "Inhibitor": "Inhibitor", - "Saboteur": "Saboteur", - "Councillor": "Councillor", - "Dazzler": "Dazzler", - "Deathpact": "Deathpact", - "Devourer": "Devourer", - "Consigliere": "Consigliere", - "Morphling": "Morphling", - "Twister": "Twister", - "Lurker": "Lurker", - "Visionary": "Visionary", - "Refugee": "Refugee", - "Underdog": "Underdog", - "Ludopath": "Ludopath", - "Godfather": "Godfather", - "Chronomancer": "Chronomancer", - "Pitfall": "Pitfall", - "EvilMini": "Evil Mini", - "Blackmailer": "Blackmailer", - "Instigator": "Instigator", - "LazyGuy": "Lazy Guy", - "SuperStar": "Super Star", - "Celebrity": "Celebrity", - "Cleanser": "Cleanser", - "Keeper": "Keeper", - "Knight": "Knight", - "Mayor": "Mayor", - "Psychic": "Psychic", - "Mechanic": "Mechanic", - "Sheriff": "Sheriff", - "Vigilante": "Vigilante", - "Jailer": "Jailer", - "CopyCat": "Copycat", - "Snitch": "Snitch", - "Marshall": "Marshall", - "Doctor": "Doctor", - "Dictator": "Dictator", - "Detective": "Detective", - "NiceGuesser": "Nice Guesser", - "GuessMaster": "Guess Master", - "Transporter": "Transporter", - "TimeManager": "Time Manager", - "Veteran": "Veteran", - "Bastion": "Bastion", - "Bodyguard": "Bodyguard", - "Deceiver": "Deceiver", - "Grenadier": "Grenadier", - "Medic": "Medic", - "FortuneTeller": "Fortune Teller", - "Judge": "Judge", - "Mortician": "Mortician", - "Medium": "Medium", - "Pacifist": "Pacifist", - "Observer": "Observer", - "Monarch": "Monarch", - "Overseer": "Overseer", - "Coroner": "Coroner", - "Merchant": "Merchant", - "President": "President", - "Hawk": "Hawk", - "Retributionist": "Retributionist", - "Deputy": "Deputy", - "Investigator": "Investigator", - "Guardian": "Guardian", - "Addict": "Addict", - "Mole": "Mole", - "Alchemist": "Alchemist", - "Tracefinder": "Tracefinder", - "Oracle": "Oracle", - "Spiritualist": "Spiritualist", - "Chameleon": "Chameleon", - "Inspector": "Inspector", - "Captain": "Captain", - "Admirer": "Admirer", - "TimeMaster": "Time Master", - "Crusader": "Crusader", - "Reverie": "Reverie", - "Lookout": "Lookout", - "Telecommunication": "Telecommunication", - "Lighter": "Lighter", - "TaskManager": "Task Manager", - "Witness": "Witness", - "Swapper": "Swapper", - "NiceMini": "Nice Mini", - "Mini": "Mini", - "Spy": "Spy", - "Randomizer": "Randomizer", - "Enigma": "Enigma", - "Jester": "Jester", - "Arsonist": "Arsonist", - "Pyromaniac": "Pyromaniac", - "Kamikaze": "Kamikaze", - "Huntsman": "Huntsman", - "Terrorist": "Terrorist", - "Executioner": "Executioner", - "Lawyer": "Lawyer", - "Opportunist": "Opportunist", - "Vector": "Vector", - "Jackal": "Jackal", - "God": "God", - "Innocent": "Innocent", - "Stealth": "Stealth", - "Penguin": "Penguin", - "Pelican": "Pelican", - "PlagueDoctor": "Plague Scientist", - "Revolutionist": "Revolutionist", - "Hater": "Hater", - "Demon": "Demon", - "Stalker": "Stalker", - "Workaholic": "Workaholic", - "Solsticer": "Solsticer", - "Collector": "Collector", - "Provocateur": "Provocateur", - "BloodKnight": "Blood Knight", - "Apocalypse": "Apocalypse", - "PlagueBearer": "Plaguebearer", - "Pestilence": "Pestilence", - "SoulCollector": "Soul Collector", - "Death": "Death", - "Baker": "Baker", - "Famine": "Famine", - "Berserker": "Berserker", - "War": "War", - "Glitch": "Glitch", - "Sidekick": "Sidekick", - "Follower": "Follower", - "Cultist": "Cultist", - "SerialKiller": "Serial Killer", - "Juggernaut": "Juggernaut", - "Infectious": "Infectious", - "Virus": "Virus", - "Pursuer": "Pursuer", - "Specter": "Specter", - "Pirate": "Pirate", - "Agitater": "Agitator", - "Maverick": "Maverick", - "CursedSoul": "Cursed Soul", - "Pickpocket": "Pickpocket", - "Traitor": "Traitor", - "Vulture": "Vulture", - "Taskinator": "Taskinator", - "Benefactor": "Benefactor", - "Medusa": "Medusa", - "Spiritcaller": "Spiritcaller", - "Amnesiac": "Amnesiac", - "Imitator": "Imitator", - "Bandit": "Bandit", - "Doppelganger": "Doppelganger", - "PunchingBag": "Punching Bag", - "Doomsayer": "Doomsayer", - "Shroud": "Shroud", - "Werewolf": "Werewolf", - "Shaman": "Shaman", - "Seeker": "Seeker", - "Pixie": "Pixie", - "Occultist": "Occultist", - "SchrodingersCat": "Schrodingers Cat", - "Romantic": "Romantic", - "VengefulRomantic": "Vengeful Romantic", - "RuthlessRomantic": "Ruthless Romantic", - "Poisoner": "Poisoner", - "HexMaster": "Hex Master", - "Wraith": "Wraith", - "Jinx": "Jinx", - "PotionMaster": "Potion Master", - "Necromancer": "Necromancer", - "Warden": "Warden", - "Minion": "Minion", - "Ghastly": "Ghastly", - "LastImpostor": "Last Impostor", - "Overclocked": "Overclocked", - "Lovers": "Lovers", - "Madmate": "Madmate", - "Watcher": "Watcher", - "Flash": "Flash", - "Torch": "Torch", - "Seer": "Seer", - "Tiebreaker": "Tiebreaker", - "Oblivious": "Oblivious", - "Bewilder": "Bewilder", - "Workhorse": "Workhorse", - "Fool": "Fool", - "Avanger": "Avenger", - "Youtuber": "YouTuber", - "Egoist": "Egoist", - "TicketsStealer": "Stealer", - "Paranoia": "Paranoia", - "Mimic": "Mimic", - "Guesser": "Guesser", - "Necroview": "Necroview", - "Reach": "Reach", - "Charmed": "Charmed", - "Cleansed": "Cleansed", - "Bait": "Bait", - "Trapper": "Beartrap", - "Infected": "Infected", - "Onbound": "Onbound", - "Rebound": "Rebound", - "Mundane": "Mundane", - "Knighted": "Knighted", - "Unreportable": "Disregarded", - "Contagious": "Contagious", - "Lucky": "Lucky", - "Unlucky": "Unlucky", - "VoidBallot": "Void Ballot", - "Aware": "Aware", - "Fragile": "Fragile", - "DoubleShot": "Double Shot", - "Rascal": "Rascal", - "Soulless": "Soulless", - "Gravestone": "Gravestone", - "Lazy": "Lazy", - "Autopsy": "Autopsy", - "Loyal": "Loyal", - "EvilSpirit": "Evil Spirit", - "Recruit": "Recruit", - "Admired": "Admired", - "Glow": "Glow", - "Radar": "Radar", - "Diseased": "Diseased", - "Antidote": "Antidote", - "Stubborn": "Stubborn", - "Swift": "Swift", - "Ghoul": "Ghoul", - "Bloodthirst": "Bloodthirst", - "Mare": "Mare", - "Burst": "Burst", - "Sleuth": "Sleuth", - "Clumsy": "Clumsy", - "Nimble": "Nimble", - "Circumvent": "Circumvent", - "Cyber": "Cyber", - "Hurried": "Hurried", - "Oiiai": "OIIAI", - "Influenced": "Influenced", - "Silent": "Silent", - "Susceptible": "Susceptible", - "Tricky": "Tricky", - "Rainbow": "Rainbow", - "Tired": "Tired", - "Statue": "Statue", - "DollMaster": "Dollmaster", - "BracketAddons": "Add Brackets To Add-ons", - "EngineerTOHEInfo": "Use the vents to catch the Impostors", - "ScientistTOHEInfo": "Access portable vitals from anywhere", - "NoisemakerTOHEInfo": "Send out an alert when killed", - "TrackerTOHEInfo": "Track a players with your map", - "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", - "PhantomTOHEInfo": "Turn invisible", - "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", - "ImpostorTOHEInfo": "Kill and sabotage", - "CrewmateTOHEInfo": "Search for the Impostors", - "BountyHunterInfo": "Eliminate your target", - "FireworkerInfo": "Go out with a BANG", - "MercenaryInfo": "Keep killing, else you suicide", - "ShapeMasterInfo": "Swiftly kill with no shift cooldown", - "VampireInfo": "Your kills are delayed", - "WarlockInfo": "Curse crewmates then shift to make them kill", - "NinjaInfo": "Mark a target, then shift to kill", - "ZombieInfo": "You are very slow", - "AnonymousInfo": "Force a player to report a body", - "MinerInfo": "Warp to your last used vent by shifting", - "KillingMachineInfo": "You can ONLY kill, but low cooldown", - "EscapistInfo": "Shift to mark places and warp back to them", - "WitchInfo": "Spell crewmates to kill them in meetings", - "NemesisInfo": "Kill when you're the last Impostor", - "BeforeNemesisInfo": "You can't kill yet", - "AfterNemesisInfo": "Now start killing", - "BloodmoonInfo": "Seek havoc upon the crewmates", - "PuppeteerInfo": "Make players kill for you", - "MastermindInfo": "Make others kill for you", - "TimeThiefInfo": "Lower meeting time by killing", - "SniperInfo": "Snipe players from a distance by shifting", - "UndertakerInfo": "Teleport dead body to a marked location", - "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", - "EvilTrackerInfo": "Track players by shifting", - "EvilHackerInfo": "Hack systems", - "AntiAdminerInfo": "Know when players are near devices", - "ArroganceInfo": "With each kill you make, your cooldown decreases", - "BomberInfo": "Shapeshift to explode", - "TrapsterInfo": "Trap your kills", - "ScavengerInfo": "Your kills are unreportable", - "EvilGuesserInfo": "Guess crew roles in meetings to kill", - "GangsterInfo": "Convert players to your side", - "CleanerInfo": "Report bodies to make them unreportable", - "LightningInfo": "Convert players to Quantum Ghosts", - "GreedyInfo": "Your kill cooldown shifts", - "CursedWolfInfo": "You survive a few kill attempts", - "SoulCatcherInfo": "You swap places with your shift target", - "QuickShooterInfo": "Store ammo to offset kill cooldown", - "CamouflagerInfo": "Camouflage everyone for easy kills", - "EraserInfo": "Erase the role of your vote target", - "ButcherInfo": "Enjoy my beautiful work", - "HangmanInfo": "I will decide when your life will end", - "SwooperInfo": "Turn invisible temporarily", - "CrewpostorInfo": "Kill by completing tasks", - "WildlingInfo": "Kill with strength and disguise", - "TricksterInfo": "Kill and trick the crew", - "VindicatorInfo": "Use your extra votes to kill everyone", - "ParasiteInfo": "Help the Impostors kill the crew", - "DisperserInfo": "Teleport everyone to random vents", - "InhibitorInfo": "You cannot kill during sabotages", - "SaboteurInfo": "You can only kill during sabotages", - "CouncillorInfo": "Kill off crewmates during meetings", - "DazzlerInfo": "Reduce the vision of the crew", - "DeathpactInfo": "Assign players to a death pact", - "DevourerInfo": "Consume the skin of the crew", - "ConsigliereInfo": "Discover the roles of other players", - "MorphlingInfo": "You can only kill while shapeshifted", - "TwisterInfo": "Swap all player positions", - "LurkerInfo": "Reduce your kill cooldown by venting", - "ConvictInfo": "Your target died, now help the Impostors", - "VisionaryInfo": "You see the alignments of the living", - "RefugeeInfo": "Help the Impostors kill off the crew", - "UnderdogInfo": "Start killing on a low player count", - "LudopathInfo": "Your kill cooldown is random", - "GodfatherInfo": "Convert players to Refugees by voting", - "ChronomancerInfo": "Kill in bursts", - "PitfallInfo": "Setup traps around the map", - "EvilMiniInfo": "No one can hurt you until you grow up", - "BlackmailerInfo": "Silence other players", - "InstigatorInfo": "Sow discord among the crewmates", - "LazyGuyInfo": "You're too lazy", - "SuperStarInfo": "Everyone knows you", - "CleanserInfo": "Erase All Add-ons of your vote target", - "KeeperInfo": "Reject the Eject, Keeper Protect!", - "MayorInfo": "Your vote counts multiple times", - "PsychicInfo": "One of the red names are evil", - "MechanicInfo": "Vent around and fix sabotages", - "SheriffInfo": "Shoot the Impostors", - "VigilanteInfo": "Not the hero we deserved but the hero we needed", - "JailerInfo": "Jail suspicious players", - "CopyCatInfo": "Use kill button to copy target's role", - "SnitchInfo": "Finish your tasks to find the Impostors", - "MarshallInfo": "Finish your tasks to prove your innocence", - "DoctorInfo": "Know how each player died", - "DictatorInfo": "Exile a player based on your own judgment", - "DetectiveInfo": "Gain extra info from your body reports", - "UndercoverInfo": "Impostors see you as their partner", - "KnightInfo": "You can kill 1 player", - "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", - "GuessMasterInfo": "Whispers heard, every guessed word.", - "TransporterInfo": "Do tasks to swap two players' locations", - "TimeManagerInfo": "Increase meeting time by doing tasks", - "VeteranInfo": "Alert to kill anyone who interacts with you", - "BastionInfo": "Bomb vents", - "BodyguardInfo": "Prevent nearby kills", - "DeceiverInfo": "Try to fool the players", - "GrenadierInfo": "Reduce Impostors' vision by venting", - "MedicInfo": "Cast a shield onto a player", - "FortuneTellerInfo": "Get clues to people's roles", - "JudgeInfo": "Silence in the courtroom!", - "MorticianInfo": "Locate dead bodies", - "MediumInfo": "Talk with ghosts", - "ObserverInfo": "You can see all shield-animations", - "PacifistInfo": "Vent to reset kill cooldowns", - "MonarchInfo": "Give your crew extra voting power!", - "StealthInfo": "Killing Blinds Everyone in the Room", - "PenguinInfo": "Drag your victims", - "OverseerInfo": "Reveal roles of other players", - "CoronerInfo": "Find corpses and their killers", - "PresidentInfo": "You are in charge of the meeting", - "MerchantInfo": "Sell add-ons and bribe killers", - "RetributionistInfo": "Help the crew after you die", - "HawkInfo": "Seek murdering the bad guys!", - "DeputyInfo": "Handcuff killers to increase their cooldowns", - "InvestigatorInfo": "Find potential evils", - "GuardianInfo": "Complete your tasks to become immortal", - "AddictInfo": "Vent to become invulnerable, or you'll die", - "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", - "AlchemistInfo": "Brew potions by completing tasks", - "TracefinderInfo": "Sense the location of dead bodies", - "OracleInfo": "Vote a player to see their alignment", - "SpiritualistInfo": "Be guided by the ghostly life", - "ChameleonInfo": "Vent to disguise into your surroundings", - "InspectorInfo": "Validate the alignments of two players", - "CaptainInfo": "Sail with the Captain, lest add-ons be abandoned.", - "AdmirerInfo": "Choose a player to side with you", - "TimeMasterInfo": "Rewind time!", - "CrusaderInfo": "Kill a player's attacker", - "ReverieInfo": "With each kill, your cooldown decreases", - "LookoutInfo": "See through disguises", - "TelecommunicationInfo": "Track device usage", - "LighterInfo": "Catch killers with your enhanced vision", - "TaskManagerInfo": "See the total tasks completed in real-time", - "WitnessInfo": "Find out if someone killed recently", - "GhastlyInfo": "Control somebody!", - "SwapperInfo": "Swap the votes of two players", - "NiceMiniInfo": "No one can hurt you until you grow up.", - "ArsonistInfo": "Douse everyone and ignite", - "PyromaniacInfo": "Douse and kill everyone", - "HuntsmanInfo": "Kill your targets for a low cooldown", - "SpyInfo": "You know who interacts with you", - "RandomizerInfo": "You're going to be someone's burden when you die?", - "EnigmaInfo": "Get Clues about Killers", - "JesterInfo": "Get voted out", - "OpportunistInfo": "Stay alive until the end", - "TerroristInfo": "Finish your tasks, THEN die", - "ExecutionerInfo": "Get your target voted out", - "LawyerInfo": "Help your target win!", - "VectorInfo": "Jump in! Jump out!", - "JackalInfo": "Murder everyone", - "GodInfo": "Everything is under your control", - "InnocentInfo": "Get someone ejected by making them kill you", - "PelicanInfo": "Eat all players", - "RevolutionistInfo": "Recruit players to win with you", - "HaterInfo": "Kill Lovers and Neptunes", - "DemonInfo": "Consume blood volumes", - "StalkerInfo": "Descend into the darkness, release fear!", - "WorkaholicInfo": "Finish all tasks to win solo!", - "SolsticerInfo": "Speed run all your tasks!", - "CollectorInfo": "Collect votes from players", - "ProvocateurInfo": "Victory with help target", - "BloodKnightInfo": "Killing gives you a temporary shield", - "PlagueBearerInfo": "Plague everyone to turn into Pestilence", - "PestilenceInfo": "Obliterate everyone!", - "SoulCollectorInfo": "Predict deaths to collect souls", - "DeathInfo": "Enact Armageddon", - "BakerInfo": "Feed Players Bread to become Famine", - "FamineInfo": "Starve Everyone", - "BerserkerInfo": "Kill to increase your level", - "WarInfo": "Destroy everything", - "GlitchInfo": "Hack and kill everyone", - "SidekickInfo": "Help the Jackal kill everyone", - "FollowerInfo": "Follow a player and help them", - "CultistInfo": "Charm everyone", - "SerialKillerInfo": "Kill off everyone to win!", - "JuggernautInfo": "With each kill, your cooldown decreases", - "InfectiousInfo": "Infect everyone", - "VirusInfo": "Kill and infect everyone", - "PursuerInfo": "Protect yourself and live to the end!", - "PlagueDoctorInfo": "Spread the infection!", - "SpecterInfo": "Get killed and finish your tasks to win!", - "PirateInfo": "Successfully plunder players to win", - "AgitaterInfo": "Pass a Bomb onto others", - "MaverickInfo": "Kill and survive to the end", - "CursedSoulInfo": "Snatch souls and steal the win", - "PickpocketInfo": "Steal votes from your kills", - "TraitorInfo": "Eliminate the Impostors, then win", - "VultureInfo": "Eat bodies by reporting to win", - "TaskinatorInfo": "Silent tasks, deadly blasts", - "BenefactorInfo": "Task complete, shield elite!", - "MedusaInfo": "Stone bodies by reporting them", - "SpiritcallerInfo": "Turn Players into Evil Spirits", - "AmnesiacInfo": "Remember the role of a dead body", - "ImitatorInfo": "Imitate a player's role", - "BanditInfo": "Rob a player's add-on", - "DoppelgangerInfo": "Steal your target's identity", - "PunchingBagInfo": "Get attacked a few times to win!", - "KamikazeInfo": "Kill players with a suicidal mission", - "DoomsayerInfo": "Successfully guess players to win", - "ShroudInfo": "Shroud players to make them kill", - "WerewolfInfo": "Kill crewmates in groups", - "ShamanInfo": "Deflect all the attacks on Voodoo doll", - "SeekerInfo": "Play Hide and Seek with your target", - "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", - "OccultistInfo": "Kill and curse your enemies", - "SchrodingersCatInfo": "The cat is both alive and dead until observed.", - "RomanticInfo": "Protect your partner to win together", - "VengefulRomanticInfo": "Revenge your partner to win together", - "RuthlessRomanticInfo": "Kill everyone to win with your partner", - "PoisonerInfo": "Kill everyone with delayed kills", - "HexMasterInfo": "Hex players to kill them in meetings", - "WraithInfo": "Vent to go invisible temporarily", - "JinxInfo": "Reflect attacks onto your attackers", - "PotionMasterInfo": "Use your potions to your advantage", - "NecromancerInfo": "Kill your killer to defy death", - "WardenInfo": "(Ghost) Alert about danger", - "MinionInfo": "(Ghost) Blind enemies", - "LoversInfo": "Stay alive and win together", - "MadmateInfo": "Help the Impostors", - "WatcherInfo": "You see all the colors of votes", - "LastImpostorInfo": "Lower kill cooldown", - "OverclockedInfo": "Lower cooldown", - "FlashInfo": "You're faster", - "TorchInfo": "You have enhanced vision!", - "SeerInfo": "You are alerted when somebody has died", - "TiebreakerInfo": "Break tied votes", - "ObliviousInfo": "You can't report bodies", - "BewilderInfo": "A twist of vision, a web of confusion", - "WorkhorseInfo": "Be the first to complete all tasks and get more", - "FoolInfo": "You can't fix sabotages", - "AvangerInfo": "You take someone with you upon death", - "YoutuberInfo": "Get killed first to win", - "CelebrityInfo": "Everyone knows when you die", - "EgoistInfo": "Win on your own", - "TicketsStealerInfo": "Gain votes with kills", - "ParanoiaInfo": "You're dead and alive simultaneously", - "MimicInfo": "Reveal killed players' roles to impostors upon death", - "GuesserInfo": "Guess roles of players in meetings to kill", - "NecroviewInfo": "See the team of the dead", - "ReachInfo": "You have a longer kill range", - "BaitInfo": "Your killer self-reports your body", - "TrapperInfo": "Freeze your killer for a few seconds", - "OnboundInfo": "You can't be guessed", - "ReboundInfo": "Guess me right, and face your plight!", - "MundaneInfo": "Tasks all done, guessing's begun.", - "UnreportableInfo": "Your body can't be reported", - "LuckyInfo": "Dodge attackers", - "DoubleShotInfo": "You have an extra life when guessing", - "RascalInfo": "You appear evil in some cases", - "SoullessInfo": "You have no soul", - "GravestoneInfo": "Your role is revealed when you die", - "LazyInfo": "You're too lazy", - "AutopsyInfo": "You see how others died", - "LoyalInfo": "You cannot be recruited", - "EvilSpiritInfo": "You are an evil Spirit", - "RecruitInfo": "Help the Jackal", - "AdmiredInfo": "The Admirer chose you as their love", - "GlowInfo": "You glow in the dark", - "RadarInfo": "Arrow's hue, closest to you!", - "DiseasedInfo": "Increase the cooldown of the player who interacts with you", - "AntidoteInfo": "Decrease the cooldown of the player who interacts with you", - "StubbornInfo": "Protect your role and add-ons", - "SwiftInfo": "Your kills don't cause a lunge", - "UnluckyInfo": "Doing things has a chance to kill you", - "VoidBallotInfo": "Your vote count is 0", - "AwareInfo": "Know who revealed your role", - "FragileInfo": "Die instantly if someone uses the kill button on you", - "GhoulInfo": "Kill your killer after dying", - "BloodthirstInfo": "Become bloodthirsty and kill", - "MareInfo": "Kill in the darkness", - "BurstInfo": "Make your killer burst!", - "SleuthInfo": "Gain info from dead bodies", - "ClumsyInfo": "You have a chance to miss your kill", - "NimbleInfo": "You can vent!", - "CircumventInfo": "You can no longer vent", - "OiiaiInfo": "OIIAIOIIIAI", - "CyberInfo": "You're popular!", - "HurriedInfo": "God, I got too much stuff!", - "InfluencedInfo": "You lack decisiveness!", - "SilentInfo": "Vote like a Ghost!", - "SusceptibleInfo": "Death-reason lotto!", - "TrickyInfo": "Tricky slays, in mysterious ways.", - "TiredInfo": "Labor makes you rest Zzz..", - "StatueInfo": "You're still as a rock nearby people", - "GMInfo": "Spectate the chaos!", - "NotAssignedInfo": "No assigned role", - "SunnyboyInfo": "Shine, shine my sunshine!", - "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", - "RainbowInfo": "Colorful melodies! You don't even know your own color.", - "DollMasterInfo": "Take control of players actions!", - "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", - "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", - "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", - "TrackerTOHEInfoLong": "(Crewmates):\nAs the Tracker, press your tracker button on a player to track their location via the map for a limited amount of time.", - "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", - "PhantomTOHEInfoLong": "(Impostors):\nAs the Phantom, you can press your vanish button to go invisible to escape a kill. You can click your appear button if you want to become visible before the timer runs out or not.\nNote: You will make a smoke cloud whenever you go invisible and become visible. So make sure you are in a safe area where no one will see you.", - "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", - "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", - "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", - "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased. The Target swaps after a certain amount of time.", - "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworks up to the maximum amount the host sets.\nWhen you are the last Impostor and all Fireworks have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworks, it's considered an Impostor victory.", - "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline, as shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", - "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", - "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. This means that even if a meeting is called first, your target still dies. However, if you bite a Bait, you kill normally and report the body. Depending on the settings, you can use double trigger (bite players - single click, kill normally - double click).", - "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", - "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown but moves very slowly and has very little vision. Zombie can not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", - "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark a target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", - "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", - "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", - "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown with tiny vision. However, you cannot vent, sabotage, report, nor call emergency meetings.\n\nNote: You will bypass any shields, killing bait and beartrap won't take any effect", - "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport; be careful).", - "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", - "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", - "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", - "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", - "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", - "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", - "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player, you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting, your marked location will reset.\n\nAfter every teleported kill, you will freeze for a configurable amount of time.", - "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker, you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out, therefore destroying all the rifts.\n\nNote: Up to two rifts can be placed at a time; if you try to place a third, it removes the first one.", - "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other players, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", - "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", - "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", - "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", - "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", - "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", - "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", - "TrapsterInfoLong": "(Impostors):\nThe Trapster has a unique method of killing. By initiating a body report, the Trapster can eliminate the player attempting to report the body the Trapster killed.\nNote: If Trapster kills the Bait, the Trapster will die immediately.", - "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the Host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", - "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned-up body cannot be reported (including bait).", - "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they encounter to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", - "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always odd.", - "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the max of times you can counterattack)", - "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", - "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the kill cooldown; he will kill even if it's still on cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", - "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shapeshifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", - "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA crew target can only be erased once (include Oiiai)", - "ButcherInfoLong": "(Impostors):\nThe Butcher's kills, including passive ones, leave multiple dead bodies on targets, which can be a bit confusing when reporting. Here's the rule: the killed target must repeatedly display the animation of being killed, which cannot be skipped, and they cannot participate in the meeting normally during this period. And if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", - "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", - "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", - "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you finish a task.", - "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but cannot vent.\nWhen you kill, you temporarily become immune to attacks.", - "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear as a crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", - "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", - "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", - "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", - "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", - "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not teleport after they shapeshift and players who are in the vent will not teleport.", - "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Lights or Reactor), you cannot kill.", - "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Comms or O2), then you can kill.", - "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nDepending on the settings, Councillor will suicide when he judge his teammates.\nConverted Councillor can judge freely.", - "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", - "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, You shapeshift to mark your targets for a deathpact.\nIf you have enough players marked for a death pact, they must meet within a specific period; if they fail to do so, they die.\nIf a marked player dies before the death pact becomes complete, the pact is withdrawn.", - "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to change the appearance of the target of the shapeshift permanently. Additionally, when each player's appearance changes, you will have your kill cooldown reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", - "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshifted.", - "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shapeshifting to swap the position of all players randomly. The swap happens twice, once when you start your shapeshift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone, and players in vents will not teleport.", - "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown resets to its original value.", - "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following information will be displayed on the players:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in the range of the infected player becomes infected themselves.\nInfection progress is cumulative and does not reset with distance or after meetings.", - "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", - "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", - "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", - "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", - "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round, if someone kills the target, the killer will turn into a Refugee.", - "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you have a charge bar which indicates when the slaughter is ready. When it is at 100% the next time you kill someone, you go into slaughter mode, meaning you can kill indefinitely until your bar runs out of charge. Otherwise, you have a normal KCD.", - "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized quickly, and their vision will be affected.", - "EvilMiniInfoLong": "(Impostors):\nAs the Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which gets drastically shortened as you grow up.", - "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", - "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate gets voted out in a meeting, if you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The Host determines the number of additional players dying.", - "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task. In addition, the Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for Anonymous, being marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", - "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only die when the Murderer is alone with the Super Star (regular kills only). In addition, the Guessers can't guess the Super Star. ", - "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", - "CleanserInfoLong": "(Crewmates):\nAs The Cleanser, you can vote to erase the add-ons of any target at the meeting. This erasure takes effect after the meeting ends. Depending on the settings, the cleansed player may never receive add-ons again.", - "KeeperInfoLong": "(Crewmates):\nAs keeper, you can vote for someone to protect them from being ejected. You can only do this a configurable number of times.", - "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. Depending on the settings, players can't see your extra votes, you can vent to call a meeting at any time, or you can have yourself revealed as Mayor upon task completion.", - "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting; at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", - "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, and Communications using only one side. You can fix Lights by flicking only one switch. Opening a door will open all doors in the map.", - "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", - "VigilanteInfoLong": "(Crewmates):\nAs the Vigilante, you are tasked with eliminating potential threats to the Crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster cannot convert Vigilante into madmate.", - "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or get voted (the vote count will be 0). The Jailer may choose to execute the prisoner by voting for them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote: Jailed players cannot be guessed or judged, and jailed players can only guess Jailer.", - "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see the Impostor's names displayed in red on the meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", - "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the Crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", - "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, the Doctor can access vitals wherever you are while he still has battery left.", - "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes for someone, the meeting will end on the spot, and the player they voted for will be ejected from the meeting. The moment the Dictator votes someone out, the Dictator will also die.", - "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the Host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", - "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", - "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", - "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role the guesser tried to guess, and you will also be notified in case of a misguess.", - "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill anyone but only do it once the whole game.", - "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", - "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", - "VeteranInfoLong": "(Crewmates):\nAs the Veteran, you can enter the alert state by venting. If a player tries to kill the Veteran in the alert state, the Veteran will kill the murderer instead. Veteran will see a shield animation on their body and a text above their head as a reminder when they enter and exit the alert state.", - "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though; crewmates can also be killed with the bombs.", - "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy the target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil with a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to Copycat after every meeting.", - "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate, and the murderer is an Impostor, the Bodyguard will not activate the skill.", - "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the killing ability has the counterfeit, he will commit suicide when he tries to kill someone next time.", - "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", - "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game. Depending on the settings, the target's shield can or cannot deactivate when the Medic dies. The Medic can also see if someone is trying to break the target's shield.\nDepending on the Host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", - "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote: If the setting to give random active players as a hint is on, you cannot check the same player multiple times.", - "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the Host). If it is wrong, the Judge commits suicide.\nCommand for judgment: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", - "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body, they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", - "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after someone reports a dead body. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question, which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", - "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. The shied animations typically indicate a role ability, so look out for this.", - "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has multiple votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exiled.", - "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", - "OverseerInfoLong": "(Crewmates):\nAs The Overseer, you have minimal vision, but you can use your kill button to reveal the role of a nearby player. A 「○」 will be displayed next to the revealed target after you use the kill button on them, and you will also be scanning them (only you can see this). Stay near the target for a defined time to reveal his role; if you move too far away, the reveal will cancel.", - "CoronerInfoLong": "(Crewmates):\nAs a Coroner, you can't report corpses; instead, after trying to report the corpse, you will see an arrow leading you to the killer. If someone calls a meeting, the arrows disappear. Depending on the settings, players can't report the body you found.", - "PresidentInfoLong": "(Crewmates):\nThe President has two abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President, and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", - "MerchantInfoLong": "(Crewmates):\nAs a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can prevent the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The money used is lost and not available for additional bribes.", - "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", - "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk, you can kill a limited amount of players decided by the host, though there's a chance you miss, slicing someone multiple times increases the chances.", - "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", - "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when someone calls a meeting.", - "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon task completion. Guessers can't even guess you in meetings.", - "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires, you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is 0 seconds, you still have a short time to vent.\nIf you don't make it, you die; if you make it, the suicide timer is reset.\nAlso after you vent, no one can interact with you for a defined period.\nAfter; the period is over, and you are immobilized for another defined period, and cannot report any bodies.", - "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you exit the vent, you will spawn near a random vent in the map (Except the one you used).", - "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", - "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double-click to kill normally. When you die, all marked also die, with death reason Targeted.", - "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by the Host.", - "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", - "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", - "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", - "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message if they are on the same team or a denial message if they are not on the same team.\n\nAll neutrals and converted players are counted in the same team. Trickster counts as Crew, and Rascal counts as Impostor.\nChecking command: /cmp [player id 1] [player id 2].", - "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non-crew role. Crewmates can see ☆besides Captain's name.\n\nIf anyone betrays the Captain's trust by voting Captain out, they will lose an add-on.", - "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", - "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", - "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", - "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", - "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", - "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", - "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", - "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real-time.", - "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings).", - "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", - "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, your survival is crucial. You can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses. This unique role adds a new dynamic to the game, where your survival is not just for your benefit but for the entire Crew's success.", - "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the kill button interaction is blocked, the player's cooldown will reset to 10s'", - "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player.", - "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the kill button on the player and following them for a few seconds. When the dousing starts, and it's successful, a shield animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", - "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting. You may have to report the body to receive a clue, depending on the settings. The more tasks you complete, the more precise the clues get.", - "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", - "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your kill cooldown goes down permanently by the set amount. However, if you kill someone who is not one of your targets, your kill cooldown permanently increases by the set amount. A colored name indicates your targets.", - "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini has two roles. A Nice or Evil Mini is chosen.\n\nUse'/r nice mini' and '/r evil mini' respectively for more details.", - "JesterInfoLong": "(Neutrals):\nIf the Jester gets voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", - "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", - "ExecutionerInfoLong": "(Neutrals):\nThe Executioner is a role with an execution target, indicated by a diamond symbol「♦」next to their name. If the execution target is killed, the Executioner's role will change to either Crewmate, Jester, or Opportunist, depending on the game settings. However, if the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", - "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", - "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", - "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", - "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill people normally (i.e., don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", - "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you steal the win, i.e., everyone else loses, and you win.", - "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target gets voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", - "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", - "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by the Host, to kill players (though they are still recruited). When the required number of players are recruited (displayed next to your name), you must vent within the specified time to win the game immediately with all your recruits. If you do not vent in time, you lose and die.", - "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, depending on the settings, you can only kill Lovers and other recruiting roles and add-ons. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", - "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", - "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause a Lights sabotage (if Lights sabotage is already active, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the Host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker wins alone.", - "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on the Host's settings, you can only win if you are alive and or revealed to everyone at the beginning (these settings are rarely both on).", - "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting finishes, your tasks reset, and you need to start all over again.\nVotes on the Solsticer will be directly canceled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in-game.", - "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required votes, the game ends, and you win alone, even if you voted a Jester or Executioner's target out.", - "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", - "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", - "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", - "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", - "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", - "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone at the meeting after you transform.", - "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after meeting)", - "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", - "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", - "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", - "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain the majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", - "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive.", - "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", - "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you can outnumber the Crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", - "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", - "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, survive to the end of the game.", - "SpecterInfoLong": "(Neutrals):\nAs the Specter, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", - "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both the Pirate and the target choose the same number, the Pirate wins.\nAdditionally, if the Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command: /duel X (where X can be 0, 1, or 2)\n\nYou win after winning a certain number of duels set by the Host.\n\nNote: The kill would not count towards pirate victory if the target did not participate in the duel.", - "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", - "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", - "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", - "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", - "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", - "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", - "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", - "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", - "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", - "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", - "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", - "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", - "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button one time to steal a player's addon and twice to kill. You may instantly steal the addon or after the meeting starts, depending on the settings. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable addons on the target or the target is stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can vent is on, Nimble will become unstealable.", - "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", - "PunchingBagInfoLong": "(Neutrals):\nAs the Punching Bag, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", - "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings), then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themselves after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", - "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", - "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.\nNote: If the killer cannot kill the chosen target, murder is canceled, but if the killer rechecks the Shaman, the killer will kill the Shaman.", - "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If the seeker tags the wrong player, a point is deducted, and if the seeker tags the correct player, a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target.\n\nThe seeker needs to collect a certain number of points set by the Host to win.", - "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark up to x amount of targets each round by using the kill button on them. You must have one of the marked targets ejected when the meeting starts. If unsuccessful, you will commit suicide, except if you didn't mark any targets or all the targets are dead. The selected targets reset to 0 after the meeting ends. If you succeed, you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the Host.", - "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use its kill button on you, the interaction is not blocked, and you will die.", - "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield that protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote: If your role changes, your win condition will be changed accordingly", - "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", - "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", - "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", - "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", - "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", - "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three different potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons default to killing.", - "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally when someone tries to kill you, you will block the kill, and you will teleport to a random vent. You will have a limited time to kill your killer. If you succeed in doing so, you live. If the time runs out before you kill your killer, you die permanently. If you try to kill someone else other than your killer, you will die.", - "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", - "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nThis feature is only assigned to roles with a kill button.", - "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when they are the last ones standing, and their victory is shared. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the Host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", - "MadmateInfoLong": "(Add-ons):\nOnly Crewmates can become Madmate. Madmate's task is to help the Impostors win the game. Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors, and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone, including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone\nPacifist => Their ability only works on Crewmates.", - "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", - "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the Host)", - "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", - "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", - "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreakers choose different tie targets simultaneously, the skills of the Tiebreaker will not take effect.", - "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. The Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still report automatically, and Oblivious can still be used as a scapegoat for Anonymous.", - "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder has died, the murderer's vision may become the same as the Bewilder's, depending on the settings.", - "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, and Workhorse will give the player extra tasks. The Host sets the number of additional tasks.", - "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", - "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out, and irregular kills will not count), the Avenger will revenge a random player.", - "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to die in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc., will not trigger the skills of the YouTuber.", - "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", - "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the Host sets the vote number, and the decimal is rounded down).\nAlso, extra votes from the Stealer are hidden during the meeting depending on the options.", - "ParanoiaInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Paranoia, you will be considered as two players in the game to determine when the game ends due to killers having the majority. Additionally, this grants you an extra vote, depending on options.", - "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called. This message will include information on roles which the Mimic killed.", - "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess the roles of players in meetings to kill them.\nGuessing the incorrect role kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", - "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. Unlike everyone else, you have the longest kill range possible in the game.", - "BaitInfoLong": "(Add-ons):\nWhen the Bait dies, the murderer who killed the Bait will self-report the Bait's body. However, this won't happen when a Scavenger, Cleaner, Swooper, Wraith, Medusa, or Killing Machine kills the Bait. The report may have a delay according to the Host's settings.", - "TrapperInfoLong": "(Add-ons):\nWhen Beartrap dies, Beartrap immobilizes killer for a configurable amount of time.", - "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", - "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", - "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", - "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", - "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", - "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess once you complete all of your tasks.", - "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", - "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse will be unreportable.", - "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", - "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on, there is a probability for you to evade the kill; the Host sets the specific probability. The killer will see the shield animation when the evasion takes effect, but you will not know anything.", - "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", - "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", - "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul steals your soul, you get this add-on.\n\nYou are not counted as alive.", - "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", - "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", - "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", - "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", - "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", - "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", - "GlowInfoLong": "(Add-ons):\nDuring lights out, you and players nearby you will receive a vision boost.", - "RadarInfoLong": "(Add-ons):\nAs Radar, you have arrows pointing towards the closest person at all times.", - "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be increased by a configurable amount of time.", - "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be decreased by a configurable amount of time.", - "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add-on, Eraser can't erase your role, Cleanser can't cleanse you, Bandit can't steal from you, and Monarch can't knight you.\nAdditionally, you can't gain any new add-ons from the Merchant.", - "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.\nNote: Swift also ignores Bait", - "UnluckyInfoLong": "(Add-ons):\nAs Unlucky, when you complete tasks, kill, venting, or open door, you have a chance to die.", - "VoidBallotInfoLong": "(Add-ons):\nHolder of this add-on will have 0 vote count.", - "AwareInfoLong": "(Add-ons):\nAs the Aware, you get a notification in the next meeting if a revealing role had interacted with you.", - "FragileInfoLong": "(Add-ons):\nAs Fragile, you will instantly die if someone tries to use the kill button on you (even if the role cannot directly kill).", - "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on task completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nThis is only assigned to crewmates, and not crewmates with no tasks or are task-based.", - "BloodthirstInfoLong": "(Add-ons):\nAs the Bloodthirst, doing tasks allows you to become bloodthirsty and kill players.\nWhen you finish a task, the next player you come in contact with dies.\n\nYour Bloodthirst remains after a meeting.\nUpon making a kill, your Bloodthirst clears till the next task you complete.\nBloodthirsts do not stack.\n\nOnly assigned to crewmates with tasks.", - "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", - "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", - "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", - "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset, and the target remains untouched.\n\nOnly assigned to killers.", - "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", - "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", - "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced, then the vote result won't shift\nCollector cannot become influenced.", - "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", - "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", - "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", - "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", - "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", - "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", - "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", - "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", - "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy.", - "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence does not affect the game, and all players know who the Game Master is. The Game Master role will be assigned to the Host, who will automatically become a ghost at the start of the game.", - "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. The game will not end when you are alive due to killers gaining the majority.\nAdditionally, you have access to portable vitals.", - "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown will be permanently halved.", - "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", - "GhastlyInfoLong": "(Crewmates [Ghost]):\nAs the Ghastly, possess an unsuspecting person, after that choose a target for them, now they'll only be able to use their kill (or kill ability) on target until you possess someone else or possess time runs out.", - "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", - "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", - "ShowTextOverlay": "Text Overlay", - "Overlay.GuesserMode": "Guesser Mode", - "Overlay.NoGameEnd": "No Game End", - "Overlay.DebugMode": "Debug Mode", - "Overlay.LowLoadMode": "Low Load Mode", - "Overlay.AllowConsole": "Console", - "DisableShieldAnimations": "Disable Unnecessary Shield Animations", - "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", - "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", - "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", - "AbilityUseLimit": "Initial Ability Use Limit", - "ShowArrows": "Has Arrows pointing toward bodies", - "ArrowDelayMin": "Minimum Arrow show-up delay", - "ArrowDelayMax": "Maximum Arrow show-up delay", - "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", - "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", - - "AbilityCD": "Ability Cooldown", - "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", - "ShowSpecificRole": "Know specific roles on Task Completion", - "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", - "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", - "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", - "DisableMeeting": "Disable Meetings", - "DisableCloseDoor": "Disable Doors Sabotage", - "DisableSabotage": "Disable Sabotages", - "NoGameEnd": "No Game End", - "AllowConsole": "BepInEx Console", - "DebugMode": "Debug Mode", - "SyncButtonMode": "Limit Meeting Times", - "RandomMapsMode": "Random Maps Mode", - "SyncedButtonCount": "Max Number of Emergency Meetings per Game", - "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", - "HHFailureKCDIncrease": "Kill cooldown increase on killing others", - "HHNumOfTargets": "Number of targets", - "Targets": "Targets: ", - "HHMaxKCD": "Maximum kill cooldown", - "HHMinKCD": "Minimum kill cooldown", - "AllAliveMeeting": "Meeting When No One is Dead", - "AllAliveMeetingTime": "Meeting Time When No One is Dead", - "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", - "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", - "AdditionalEmergencyCooldownTime": "Additional Cooldown", - "LadderDeath": "Fall From Ladders", - "LadderDeathChance": "Fall To Death Chance", - "EveryoneCanSeeDeathReason": "Everyone Can See Death Reason", - "DisableSwipeCardTask": "Disable Swipe Card Task", - "DisableSubmitScanTask": "Disable Submit Scan Task", - "DisableUnlockSafeTask": "Disable Unlock Safe Task", - "DisableUploadDataTask": "Disable Upload Data Task", - "DisableStartReactorTask": "Disable Start Reactor Task", - "DisableResetBreakerTask": "Disable Reset Breakers Task", - "DisableShortTasks": "Disable Short Tasks", - "DisableCleanVent": "Disable Clean Vent Task", - "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", - "DisableChartCourse": "Disable Chart Course Task", - "DisableStabilizeSteering": "Disable Stabilize Steering Task", - "DisableCleanO2Filter": "Disable Clean O2 Filter Task", - "DisableUnlockManifolds": "Disable Unlock Manifolds Task", - "DisablePrimeShields": "Disable Prime Shields Task", - "DisableMeasureWeather": "Disable Measure Weather", - "DisableBuyBeverage": "Disable Buy Beverage", - "DisableAssembleArtifact": "Disable Assemble Artifact Task", - "DisableSortSamples": "Disable Sort Samples Task", - "DisableProcessData": "Disable Process Data Task", - "DisableRunDiagnostics": "Disable Run Diagnostics Task", - "DisableRepairDrill": "Disable Repair Drill Task", - "DisableAlignTelescope": "Disable Align Telescope Task", - "DisableRecordTemperature": "Disable Record Temperature Task", - "DisableFillCanisters": "Disable Fill Canisters Task", - "DisableMonitorTree": "Disable Monitor Tree Task", - "DisableStoreArtifacts": "Disable Store Artifacts Task", - "DisablePutAwayPistols": "Disable Put Away Pistols Task", - "DisablePutAwayRifles": "Disable Put Away Rifles Task", - "DisableMakeBurger": "Disable Make Burger Task", - "DisableCleanToilet": "Disable Clean Toilet Task", - "DisableDecontaminate": "Disable Decontaminate Task", - "DisableSortRecords": "Disable Sort Records Task", - "DisableFixShower": "Disable Fix Shower Task", - "DisablePickUpTowels": "Disable Pick Up Towels Task", - "DisablePolishRuby": "Disable Polish Ruby Task", - "DisableDressMannequin": "Disable Dress Mannequin Task", - "DisableCommonTasks": "Disable Common Tasks", - "DisableFixWiring": "Disable Fix Wiring Task", - "DisableEnterIdCode": "Disable Enter ID Code Task", - "DisableInsertKeys": "Disable Insert Keys Task", - "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", - "DisableLongTasks": "Disable Long Tasks", - "DisableAlignEngineOutput": "Disable Align Engine Output Task", - "DisableInspectSample": "Disable Inspect Sample Task", - "DisableEmptyChute": "Disable Empty Chute Task", - "DisableClearAsteroids": "Disable Clear Asteroids Task", - "DisableWaterPlants": "Disable Water Plants Task", - "DisableOpenWaterways": "Disable Open Waterways Task", - "DisableReplaceWaterJug": "Disable Replace Water Jug Task", - "DisableRebootWifi": "Disable Reboot Wifi Task", - "DisableDevelopPhotos": "Disable Develop Photos Task", - "DisableRewindTapes": "Disable Rewind Tapes Task", - "DisableStartFans": "Disable Start Fans Task", - "DisableOtherTasks": "Disable Situational Tasks", - "DisableEmptyGarbage": "Disable Empty Garbage Task", - "DisableFuelEngines": "Disable Fuel Engines Task", - "DisableDivertPower": "Disable Divert Power Task", - "DisableActivateWeatherNodes": "Disable Weather Nodes Task", - "DisableRoastMarshmallow": "Disable Roast Marshmallow", - "DisableCollectSamples": "Disable Collect Samples", - "DisableReplaceParts": "Disable Replace Parts", - "DisableCollectVegetables": "Disable Collect Vegetables", - "DisableMineOres": "Disable Mine Ores", - "DisableExtractFuel": "Disable Extract Fuel", - "DisableCatchFish": "Disable Catch Fish", - "DisablePolishGem": "Disable Polish Gem", - "DisableHelpCritter": "Disable Help Critter", - "DisableHoistSupplies": "Disable Hoist Supplies", - "DisableFixAntenna": "Disable Fix Antenna", - "DisableBuildSandcastle": "Disable Build Sandcastle", - "DisableCrankGenerator": "Disable Crank Generator", - "DisableMonitorMushroom": "Disable Monitor Mushroom", - "DisablePlayVideoGame": "Disable Play Video Game", - "DisableFindSignal": "Disable Find Signal", - "DisableThrowFisbee": "Disable Throw Frisbee", - "DisableLiftWeights": "Disable Lift Weights", - "DisableCollectShells": "Disable Collect Shells", - "SuffixMode": "Suffix", - "SuffixMode.None": "None", - "SuffixMode.Version": "Version", - "SuffixMode.Streaming": "Streaming", - "SuffixMode.Recording": "Recording", - "SuffixMode.RoomHost": "Room Host", - "SuffixMode.OriginalName": "Original Name", - "SuffixMode.DoNotKillMe": "Don't kill me", - "SuffixMode.NoAndroidPlz": "No phones", - "SuffixMode.AutoHost": "Auto-Host", - "SuffixModeText.DoNotKillMe": "Don't kill me", - "SuffixModeText.NoAndroidPlz": "No phones please", - "SuffixModeText.AutoHost": "Auto-hosting", - "FormatNameMode": "Player Name Mode", - "FormatNameModes.None": "Disable", - "FormatNameModes.Color": "Color", - "FormatNameModes.Snacks": "Random", - "DisableEmojiName": "Disable Emoji in names", - "FixFirstKillCooldown": "Override Starting Kill Cooldown", - "FixKillCooldownValue": "Starting Kill Cooldown", - "OverclockedReduction": "Kill Cooldown Reduction", - "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", - "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", - "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", - "GhostIgnoreTasks": "Ghosts Exempt From Tasks", - "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", - "MaxImpGhostRole": "Max Impostor Ghost-Roles", - "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", - "DefaultAngelCooldown": "Default Ability Cooldown", - "DisableTaskWin": "Disable Task Win", - "DisableTaskWinIfAllCrewsAreDead": "Disable Task Win If All <#8cffff>Crewmates Are Dead", - "DisableTaskWinIfAllCrewsAreConverted": "Disable Task Win If All <#8cffff>Crewmates Are <#ffab1b>Converted", - "HideGameSettings": "Hide Game Settings", - "DIYGameSettings": "Enable only custom /n messages", - "Settings:": "Settings:", - "PlayerCanSetColor": "Players can use the /color command", - "PlayerCanSetName": "Players can use the /rn command", - "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", - "PlayerCanUseTP": "Players can use the /tpin and /tpout command", - "CanPlayMiniGames": "Players can play mini-games", - "KPDCamouflageMode": "Camouflage Appearance", - "RoleOptions": "Role Options", - "DarkTheme": "Enable Dark Theme", - "DisableLobbyMusic": "Disable Lobby Music", - "AutoStart": "Auto start", - "EnableCustomButton": "Enable Custom Button Images", - "EnableCustomSoundEffect": "Enable Custom Sound Effects", - "EnableCustomDecorations": "Enable Custom Map Decorations", - "SwitchVanilla": "Switch Vanilla", - "UnlockFPS": "Unlock FPS", - "ForceOwnLanguage": "Force mod to use your language if possible", - "ForceOwnLanguageRoleName": "Force role names in your language if possible", - "VersionCheat": "Bypass version synchronization check", - "GodMode": "God Mode", - - "AutoDisplayKillLog": "Display Kill-log", - "AutoDisplayLastRoles": "Display Last Roles", - "AutoDisplayLastResult": "Auto Display Last Result", - "RevertOldKillLog": "Revert to old kill-log", - - "HideExileChat": "Hide exile (lava) chat", - "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", - - "EnableYTPlan": "Enable Youtuber Plan", - "InvalidPermissionCMD": "INVALID PERMISSION\n\nSorry, you do not have the permission to use this command, check /me for all permissions", - "KickLowLevelPlayer": "Kick players whose level is lower than", - "TempBanLowLevelPlayer": "Temporarily ban low-level players", - "ApplyWhiteList": "Turn on Whitelist to bypass level kick", - "AllowOnlyWhiteList": "Allow only whitelisted players to join", - "AutoKickStart": "Kick players that say start", - "AutoKickStartTimes": "Number of warnings before kick", - "AutoKickStartAsBan": "Block a player after they're kicked", - "AutoKickStopWords": "Kick players who write banned words", - "AutoKickStopWordsTimes": "Number of warnings for banned words", - "AutoKickStopWordsAsBan": "Block a player after they're kicked", - "AutoWarnStopWords": "Warning to those who write banned words", - "TempBanPlayersWhoKeepQuitting": "Temporarily ban players who leave and join repeatedly", - "QuitTimesTillTempBan": "The quit frequency needed for temp ban", - "KickOtherPlatformPlayer": "Kick Non-PC players", - "OptKickAndroidPlayer": "Kick Android players", - "OptKickIphonePlayer": "Kick iOS players", - "OptKickXboxPlayer": "Kick Xbox players", - "OptKickPlayStationPlayer": "Kick PlayStation players", - "OptKickNintendoPlayer": "Kick Nintendo Switch players", - "ShareLobby": "Allow TOHE-Chan Shares Lobby Code To Discord", - "ShareLobbyMinPlayer": "Share Lobby Code When The Number Of Players Reaches", - "DisableVanillaRoles": "Disable Vanilla Roles", - "VoteMode": "Voting Mode", - "WhenSkipVote": "If the Player Skipped", - "WhenSkipVoteIgnoreFirstMeeting": "Ignore the First Meeting", - "WhenSkipVoteIgnoreNoDeadBody": "Ignore When No Dead Body", - "WhenSkipVoteIgnoreEmergency": "Ignore at Emergency Meetings", - "WhenNonVote": "If the Player didn't vote", - "Default": "No vote", - "Suicide": "Suicide", - "SelfVote": "Self Vote", - "Skip": "Skip", - "WhenTie": "When Tied Vote", - "TieMode.Default": "No ejects", - "TieMode.All": "Eject All", - "TieMode.Random": "Eject Random", - "DisableDevices": "Disable Devices", - "DisableSkeldDevices": "Disable Skeld Devices", - "DisableMiraHQDevices": "Disable MIRA HQ Devices", - "DisablePolusDevices": "Disable Polus Devices", - "DisableAirshipDevices": "Disable Airship Devices", - "DisableFungleDevices": "Disable Fungle Devices", - "DisableSkeldAdmin": "Disable Admin", - "DisableMiraHQAdmin": "Disable Admin", - "DisablePolusAdmin": "Disable Admin", - "DisableAirshipCockpitAdmin": "Disable Cockpit Admin", - "DisableAirshipRecordsAdmin": "Disable Records Admin", - "DisableSkeldCamera": "Disable Cameras", - "DisablePolusCamera": "Disable Cameras", - "DisableAirshipCamera": "Disable Cameras", - "DisableMiraHQDoorLog": "Disable DoorLog", - "DisablePolusVital": "Disable Vitals", - "DisableAirshipVital": "Disable Vitals", - "DisableFungleVital": "Disable Vitals", - "DisableFungleBinoculars": "Disable Binoculars (Not work for vanilla)", - "IgnoreConditions": "Ignore Conditions", - "IgnoreImpostors": "Ignore Impostors", - "IgnoreNeutrals": "Ignore Neutrals", - "IgnoreCrewmates": "Ignore Crewmates", - "IgnoreAfterAnyoneDied": "Ignore After First Death", - "LightsOutSpecialSettings": "Fix Lights Special Settings", - "BlockDisturbancesToSwitches": "Block Switches When They Are Up", - "DisableAirshipViewingDeckLightsPanel": "Disable Viewing Deck Lights Panel (Airship)", - "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", - "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", - "RandomSpawnMode": "Random Spawns Mode", - "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", - "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", - "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", - "CommsCamouflage": "Camouflage during Comms Sabotage", - "DisableOnSomeMaps": "Disable comms camouflage on some maps", - "DisableOnSkeld": "Disable on The Skeld", - "DisableOnMira": "Disable on MIRA HQ", - "DisableOnPolus": "Disable on Polus", - "DisableOnDleks": "Disable on dlekS ehT", - "DisableOnAirship": "Disable on Airship", - "DisableOnFungle": "Disable on The Fungle", - "DisableReportWhenCC": "Disable body reporting while camouflaged", - "EnableDebugMode": "Enable Debug Mode", - "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", - "SendRoleDescriptionFirstMeeting": "Show Role Descriptions to Unmodded Clients at First Meeting", - "RoleAssigningAlgorithm": "Role Assigning Algorithm", - "RoleAssigningAlgorithm.Default": "Default", - "RoleAssigningAlgorithm.NetRandom": ".NET System.Random", - "RoleAssigningAlgorithm.HashRandom": "HashRandom", - "RoleAssigningAlgorithm.Xorshift": "Xorshift", - "RoleAssigningAlgorithm.MersenneTwister": "MersenneTwister", - "MapModification": "Map Modifications", - "DisableAirshipMovingPlatform": "Disable Moving Platform (Airship)", - "AirshipVariableElectrical": "Variable Electrical (Airship)", - "DisableSporeTriggerOnFungle": "Disable Spore Trigger (Fungle)", - "DisableZiplineOnFungle": "Disable Zipline (Fungle)", - "DisableZiplineFromTop": "Disable Use From Top", - "DisableZiplineFromUnder": "Disable Use From Under", - "ResetDoorsEveryTurns": "Reset Doors After Meeting (Airship/Polus/Fungle)", - "DoorsResetMode": "Reset Doors Mode", - "AllOpen": "All Open", - "AllClosed": "All Closed", - "RandomByDoor": "Closed Random", - "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", - "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", - "DecontaminationTimeOnPolus": "Decontamination Time On Polus", - "ApplyDenyNameList": "Apply DenyName List", - "KickPlayerFriendCodeInvalid": "Kick players with an invalid friend code", - "TempBanPlayerFriendCodeInvalid": "Temp Ban players with an invalid friend code", - "ApplyBanList": "Apply BanList", - "RemovePetsAtDeadPlayers": "Remove pets at dead players", - "KillFlashDuration": "Kill-Flash Duration", - "ConfirmEjectionsMode": "Confirm Ejections Mode", - "ConfirmEjections.None": "None", - "ConfirmEjections.Team": "Team", - "ConfirmEjections.Role": "Role", - "ShowImpRemainOnEject": "Show remaining Impostors on ejects", - "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", - "ConfirmEgoistOnEject": "Confirm Egoists on ejection", - "ConfirmLoversOnEject": "Confirm Lovers on ejection", - "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", - "HideBittenRolesOnEject": "Hide roles of bitten players on ejection", - "ShowTeamNextToRoleNameOnEject": "Show what team the ejected player's role is on", - "Ban": "Ban", - "Kick": "Kick", - "NoticeMe": "Notify me", - "NoticeEveryone": "Notify everyone", - "TempBan": "Temporary Ban", - "OnlyCancel": "Only Cancel the cheat actions", - "CheatResponses": "When a cheating player is found", - "NeutralRoleWinTogether": "Neutrals win together", - "NeutralWinTogether": "All Neutrals win together", - "MenuTitle.Disable": "★ Disable ★", - "MenuTitle.MapsSettings": "★ Maps ★", - "MenuTitle.Sabotage": "★ Sabotage ★", - "MenuTitle.Meeting": "★ Meeting ★", - "MenuTitle.Ghost": "★ Ghost ★", - "MenuTitle.Other": "★ Different ★", - "MenuTitle.Ejections": "★ Ejection ★", - "MenuTitle.Settings": "★ Settings ★", - "MenuTitle.TaskSettings": "★ Task Management ★", - "MenuTitle.Guessers": "★ Guesser Mode ★", - "MenuTitle.GuesserModeRoles": "★ Add-ons for Guesser Mode ★", - "ShieldPersonDiedFirst": "Shield player who dead first in the last game", - "LegacyNemesis": "Use Legacy Version", - "ArsonistKeepsGameGoing": "Arsonist keeps the game going", - "ArsonistCanIgniteAnytime": "Can Ignite Anytime", - "ArsonistMinPlayersToIgnite": "Minimum doused needed for ignite", - "ArsonistMaxPlayersToIgnite": "Maximum doused needed for ignite", - "PuppeteerDoubleKills": "Puppet dies alongside victim", - "DollMasterPossessionCooldown": "Possession Cooldown", - "DollMasterPossessionDuration": "Possession Duration", - "DollMasterCanKillAsMainBody": "Can kill as the main body", - "DollMasterTargetDiesAfterPossession": "Target dies after possession", - "MastermindCD": "Manipulate Cooldown", - "MastermindTimeLimit": "Time limit to kill someone", - "MastermindDelay": "Manipulation notification delay", - "ManipulateNotify": "Kill someone in {0}s or die!", - "ManipulatedKilled": "{0} has killed someone", - "SurvivedManipulation": "You survived the Mastermind's manipulation!", - "Glitch_HackCooldown": "Hack Cooldown", - "Glitch_HackDuration": "Hack Duration", - "Glitch_MimicCooldown": "Mimic Cooldown", - "Glitch_MimicDuration": "Mimic Duration", - "Glitch_MimicButtonText": "Mimic", - "Glitch_MimicDur": "Mimic Duration: {0}s", - "Glitch_HackCD": "Hack Cooldown: {0}s", - "Glitch_KCD": "Kill Cooldown: {0}s", - "Glitch_MimicCD": "Mimic Cooldown: {0}s", - "HackedByGlitch": "You are hacked by the Glitch, you can't {0}.", - "GlitchKill": "kill", - "GlitchReport": "report", - "GlitchVent": "vent", - "ShowFPS": "Show FPS", - "FPSGame": "FPS: ", - - "ControlCooldown": "Control Cooldown", - "PoisonCooldown": "Poison Cooldown", - "PoisonerKillDelay": "Poison Kill Delay", - "WardenNotifyLimit": "Max number of alerts", - - "BombCooldown": "Bomb Cooldown", - "Warlock_CanKillSelf": "Can Kill Themselves", - "CrewpostorKnowsAllies": "Knows Impostors", - "AlliesKnowCrewpostor": "Known to Impostors", - "CrewpostorLungeKill": "Crewpostor lunges on kill", - "CrewpostorKillAfterTask": "Number of tasks completed to make one kill", - - "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", - "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", - "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", - "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", - "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", - "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", - "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", - "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", - "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", - "ImpKnowWhosMadmate": "Impostors know Madmates", - "MadmateKnowWhosImp": "Madmates know Impostors", - "MadmateKnowWhosMadmate": "Madmates know each other", - "ImpCanKillMadmate": "Impostors can kill Madmates", - "MadmateCanKillImp": "Madmates can kill Impostors", - "MadmateHasImpostorVision": "Madmates Have Impostor Vision", - "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", - "EGCanGuessImp": "Can Guess Impostor Roles", - "GGCanGuessCrew": "Can Guess Crewmate Roles", - "EGCanGuessAdt": "Can Guess Add-Ons", - "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "GGCanGuessAdt": "Can Guess Add-Ons", - "GuesserCanGuessTimes": "Maximum number of guesses", - "GuesserTryHideMsg": "Try to hide the guesser's command", - "GCanGuessImp": "Impostor can guess Impostor roles", - "GCanGuessCrew": "Crewmate can guess Crewmate roles", - "GCanGuessAdt": "Can guess Add-ons", - "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "BountyTargetChangeTime": "Time Until Target Swaps", - "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", - "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", - "BountyShowTargetArrow": "Show arrow pointing towards the target", - "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", - "DeadImpCantSabotage": "Impostors can't sabotage after they've died", - "VampireKillDelay": "Bite Kill Delay", - "VampireTargetDead": "Target died", - "VampireActionMode": "Action Mode", - "Vampire_OnlyBites": "Only Bites", - "Youtuber_KillerWinsWithYouTuber": "The Killer Wins With YouTuber", - "Maverick_MinKillsToWin": "Minimum number of kills to win", - - "Cooldown": "Cooldown", - "AbilityCooldown": "Ability Cooldown", - "CanKill": "Can Kill", - "KillCooldown": "Kill Cooldown", - "CanVent": "Can Vent", - "ImpostorVision": "Has Impostor Vision", - "CanUseSabotage": "Can Sabotage", - "CanKillImpostors": "Can Kill Impostors", - "CanGuess": "Can Guess in Guesser Mode or as Guesser", - "HideVote": "Hide Vote", - "HideAdditionalVotes": "Hide additional vote(s)", - "CanUseMeetingButton": "Can Call Emergency Meetings", - "ModeSwitchAction": "Switch Action via", - "ShowShapeshiftAnimations": "Show Shapeshift animations", - - "ShapeshifterBase_ShapeshiftCooldown": "Shapeshift Cooldown", - "ShapeshifterBase_ShapeshiftDuration": "Shapeshift Duration", - "ShapeshifterBase_LeaveShapeshiftingEvidence": "Leave Shapeshifting Evidence", - "PhantomBase_InvisCooldown": "Invis Cooldown", - "PhantomBase_InvisDuration": "Invis Duration", - "GuardianAngelBase_ProtectCooldown": "Protect Cooldown", - "GuardianAngelBase_ProtectionDuration": "Protection Duration", - "GuardianAngelBase_ImpostorsCanSeeProtect": "Protect Visible To Impostors", - "ScientistBase_BatteryCooldown": "Vitals Display Cooldown", - "ScientistBase_BatteryDuration": "Battery Duration", - "EngineerBase_VentCooldown": "Vent Cooldown", - "EngineerBase_InVentMaxTime": "Max Time In Vents", - "NoisemakerBase_ImpostorAlert": "Impostors Can Get Alert", - "NoisemakerBase_AlertDuration": "Alert Duration", - "TrackerBase_TrackingCooldown": "Tracking Cooldown", - "TrackerBase_TrackingDuration": "Tracking Duration", - "TrackerBase_TrackingDelay": "Tracking Delay", - - "KamikazeControversialSymbol": "Use controversial symbol", - "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", - "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", - "MechanicSkillLimit": "Initial repair use limit", - "MechanicFixesDoors": "Can open all doors in the same building", - "MechanicFixesReactors": "Can Fix Both Reactors Alone", - "MechanicFixesOxygens": "Can Fix Both O2 Alone", - "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", - "MechanicFixesElectrical": "Can Fix Lights With One Switch", - - "SheriffShowShotLimit": "Display Shot Limit next to Role Name", - "SheriffCanKill%role%": "Can Kill %role%", - "SheriffCanKillNeutrals": "Can Kill Neutrals", - "SheriffCanKillNeutralsMode": "Neutral Configuration", - "SheriffCanKillAll": "All ON", - "SheriffCanKillSeparately": "Individual Settings", - "In%team%": "(Team %team%)", - "SheriffMisfireKillsTarget": "Misfire Kills Target", - "SheriffShotLimit": "Max number of Kills", - "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", - "SheriffCanKillCharmed": "Can kill Charmed players", - "SheriffCanKillEgoist": "Can Kill Egoists", - "SheriffCanKillSidekick": "Can Kill Sidekicks", - "SheriffCanKillLovers": "Can Kill Lovers", - "SheriffCanKillMadmate": "Can Kill Madmates", - "SheriffCanKillInfected": "Can Kill Infected players", - "SheriffCanKillContagious": "Can Kill Contagious players", - "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", - "SheriffMadCanKillImp": "Can kill Impostors", - "SheriffMadCanKillNeutral": "Can kill Neutrals", - "SheriffMadCanKillCrew": "Can kill Crewmates", - - "ReverieIncreaseKillCooldown": "Increase kill cooldown", - "ReverieMaxKillCooldown": "Max kill cooldown", - "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", - "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", - "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", - - "VigilanteNotify": "You have become the very thing you swore to destroy", - - "DoctorTaskCompletedBatteryCharge": "Battery Duration", - "SnitchEnableTargetArrow": "See Arrow Towards Target", - "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", - "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", - "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", - "SnitchCanFindMadmate": "Can Find Madmates", - "SnitchRemainingTaskFound": "Remaining tasks to be known", - "MayorAdditionalVote": "Additional Votes Count", - "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", - "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", - "MeetingsNeededForWin": "Meetings needed to win", - "ExecutionerCanTargetImpostor": "Can Target Impostors", - "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", - "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", - "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", - "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", - "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", - "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", - "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", - "LawyerCanTargetImpostor": "Can Target Impostors", - "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", - "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", - "LawyerCanTargetCrewmate": "Can Target Crewmates", - "LawyerCanTargetJester": "Can Target Jester", - "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", - "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", - "LawyerTargetDeadInMeeting": "Your target was killed while meeting.\nYour role may change depending on the settings.", - - "MercenaryLimit": "Time Until Suicide", - "ArsonistDouseTime": "Douse Duration", - "CanTerroristSuicideWin": "Can Win By Suicide", - "FireworkerMaxCount": "Fireworker Count", - "FireworkerRadius": "Firework Explosion Radius", - "SniperCanKill": "Sniper can kill with bullets remaining", - "SniperBulletCount": "Ammo", - "SniperPrecisionShooting": "Precise Shooting", - "SniperAimAssist": "Aim Assist", - "SniperAimAssistOneshot": "One shot Assist", - - "PyroDouseCooldown": "Douse cooldown", - "PyroBurnCooldown": "Kill cooldown after killing a doused player", - - "UndertakerFreezeDuration": "Freeze Duration", - "NameDisplayAddons": "Display Add-Ons next to the role name", - "YourAddon": "Your Add-ons:", - "NoLimitAddonsNumMax": "Max Add-ons Per Player", - "LoverSpawnChances": "Spawn Chance of Lovers", - "AdditionRolesSpawnRate": "Spawn Chance", - "TorchVision": "Torch Vision", - "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", - "BewilderVision": "Bewilder Vision", - "JesterVision": "Jester Vision", - "LawyerVision": "Lawyer Vision", - "FlashSpeed": "Flash Speed", - "LoverSuicide": "Lovers die together", - "NumberOfLovers": "Number of Lover Pairs (x2 members)", - "LoverKnowRoles": "Lovers know the roles of each other", - "TrapperBlockMoveTime": "Freeze time", - "BecomeTrapperBlockMoveTime": "Freeze time", - "ImpCanBeTrapper": "Impostors can become Beartrap", - "CrewCanBeTrapper": "Crewmates can become Beartrap", - "NeutralCanBeTrapper": "Neutrals can become Beartrap", - "ImpCanBeGravestone": "Impostors can become Gravestone", - "CrewCanBeGravestone": "Crewmates can become Gravestone", - "NeutralCanBeGravestone": "Neutrals can become Gravestone", - "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", - "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", - "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", - "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", - "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", - "EvilTrackerTargetMode": "Can Set Target", - "EvilTrackerTargetMode.Never": "Never", - "EvilTrackerTargetMode.OnceInGame": "Once in-game", - "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", - "EvilTrackerTargetMode.Always": "Any time", - - "EvilHackerCanSeeDeadMark": "Can See The Location of Dead-bodies", - "EvilHackerCanSeeImpostorMark": "Can See The Location of Other Impostors", - "EvilHackerCanSeeKillFlash": "Can See Kill-Flash", - "EvilHackerCanSeeMurderRoom": "Can See The Murder Location", - "EvilHackerMurderNotify": "Murder In", - "EvilHackerLastAdminInfoTitle": "Last-minute admin information", - "EvilHackerDeadbody": "DEAD", - - "TraitorKnowMadmate": "Traitor Knows Madmates", - "NBareRed": "Neutral Benign can be red", - "NEareRed": "Neutral Evil can be red", - "NCareRed": "Neutral Chaos can be red", - "NAareRed": "Neutral Apocalypse can be red", - "CrewKillingRed": "Crewmate Killings can be red", - "PsychicCanSeeNum": "Max number of red names", - "PsychicFresh": "New red names every meeting", - "DetectiveCanknowKiller": "Can find the killer's role", - "EveryOneKnowSuperStar": "Everyone knows the Super Star", - "HackLimit": "Ability Use Count", - "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", - "NemesisCanKillNum": "Max number of revenges", - "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", - "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", - "VectorVentNumWin": "Number of Vents to win", - "CanCheckCamera": "Can track camera usage", - "DefaultKillCooldown": "Starting kill cooldown", - "ReduceKillCooldown": "Reduce kill cooldown by", - "MinKillCooldown": "Minimum kill cooldown", - "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", - "NotifyGodAlive": "Inform players at meetings that God is still alive", - "TransporterTeleportMax": "Max number of teleports", - "TriggerKill": "Kill", - "TriggerVent": "Vent", - "TriggerDouble": "Double Click", - "TimeManagerIncreaseMeetingTime": "Increase voting time by", - "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", - "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", - "AssignOnlyToCrewmate": "Assign only to Crewmates", - "WorkhorseNumLongTasks": "Additional Long Tasks", - "WorkhorseNumShortTasks": "Additional Short Tasks", - "SnitchCanBeWorkhorse": "Snitch can become Workhorse", - "InnocentCanWinByImp": "If their target was an Impostor then they win with them", - "ImpCanBeParanoia": "Impostors can become Paranoia", - "CrewCanBeParanoia": "Crewmates can become Paranoia", - "DualVotes": "Duplicate votes", - "VeteranSkillCooldown": "Alert Cooldown", - "VeteranSkillDuration": "Alert Duration", - "BodyguardProtectRadius": "Protect Radius", - "ImpCanBeEgoist": "An Impostor can become Egoist", - "CrewCanBeEgoist": "Crewmates can become Egoist", - "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", - "EgoistCountAsConverted": "Egoist count as converted neutral", - "ImpCanBeSeer": "Impostors can become Seer", - "CrewCanBeSeer": "Crewmates can become Seer", - "NeutralCanBeSeer": "Neutrals can become Seer", - "ImpCanBeGuesser": "Impostors can become Guesser", - "CrewCanBeGuesser": "Crewmates can become Guesser", - "NeutralCanBeGuesser": "Neutrals can become Guesser", - "ImpCanBeWatcher": "Impostors can become Watcher", - "CrewCanBeWatcher": "Crewmates can become Watcher", - "NeutralCanBeWatcher": "Neutrals can become Watcher", - "ImpCanBeBait": "Impostors can become Bait", - "CrewCanBeBait": "Crewmates can become Bait", - "NeutralCanBeBait": "Neutrals can become Bait", - "ImpCanBeRainbow": "Impostors can become Rainbow", - "NeutralCanBeRainbow": "Neutrals can become Rainbow", - "CrewCanBeRainbow": "Crewmates can become Rainbow", - "GuessRainbow": "He seems too obvious, doesn't he?", - "RainbowColorChangeCoolDown": "The cooldown for changing colors", - "RainbowInCamouflage": "Rainbow color changes during Camouflage", - "BaitDelayMin": "Minimum Report Delay", - "BaitDelayMax": "Maximum Report Delay", - "BaitDelayNotify": "Warn the killer about the upcoming self-report", - "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", - "BaitNotification": "Reveal Bait at the first meeting", - "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self-report.", - "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if a meeting is disabled during comms sabotage", - "DeceiverSkillCooldown": "Ability cooldown", - "DeceiverSkillLimitTimes": "Max number of uses", - "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", - "PursuerSkillCooldown": "Ability cooldown", - "PursuerSkillLimitTimes": "Max number of uses", - "AddictSuicideTimer": "Time Until Suicide", - "GrenadierSkillCooldown": "Grenade Cooldown", - "GrenadierSkillDuration": "Grenade Duration", - "GrenadierCauseVision": "Lowered vision", - "GrenadierCanAffectNeutral": "Can affect Neutrals", - "TicketsPerKill": "Votes Increase Amount Per Kill", - "GangsterRecruitCooldown": "Recruit cooldown", - "GangsterRecruitLimit": "Recruit limit", - "KamikazeMaxMarked": "Max Marked", - "RevolutionistDrawTime": "Tag Duration", - "RevolutionistCooldown": "Tag Cooldown", - "RevolutionistDrawCount": "Amount of Players needed to Tag", - "RevolutionistKillProbability": "Tagged player sacrifice probability", - "RevolutionistVentCountDown": "Time to Vent", - "PelicanKillCooldown": "Eat Cooldown", - "Pelican.TargetCannotBeEaten": "Target cannot be eaten", - "MadSnitchTasks": "Snitch Tasks", - "MedicWhoCanSeeProtect": "Who can see shield", - "MedicKnowShieldBroken": "Who sees kill attempt", - "Medic_SeeMedicAndTarget": "Medic+Shielded", - "Medic_SeeMedic": "Medic", - "Medic_SeeTarget": "Shielded", - "Medic_SeeNoOne": "Nothing", - "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", - "MedicShielDeactivationIsVisible": "Shield deactivation is visible", - "MedicShieldDeactivationIsVisible_Immediately": "Immediately", - "MedicShieldDeactivationIsVisible_AfterMeeting": "After Meeting", - "MedicShieldDeactivationIsVisible_OFF": "OFF", - "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", - "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", - "FortuneTellerSkillLimit": "Max number of ability uses", - "MadmateSpawnMode": "Madmate spawning mode", - "MadmateSpawnMode.Assign": "Assign", - "MadmateSpawnMode.FirstKill": "First Kill", - "MadmateSpawnMode.SelfVote": "Self Vote", - "MadmateCountMode": "Madmates count as", - "MadmateCountMode.None": "Nothing", - "MadmateCountMode.Imp": "Impostors", - "MadmateCountMode.Original": "Original Team", - - "SnatchesWin": "Snatches victory", - "DemonKillCooldown": "Attack Cooldown", - "DemonHealthMax": "Player max health", - "DemonDamage": "Damage ", - "DemonSelfHealthMax": "Demon max health", - "DemonSelfDamage": "Demon damage received", - "LightningConvertTime": "Duration of the transformation to Quantum Ghost", - "LightningKillCooldown": "Lightning Cooldown", - "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", - "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", - "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", - "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", - "WorkaholicCannotWinAtDeath": "Can't win after they died", - "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", - "WorkaholicGiveAdviceAlive": "Advice at the first meeting if alive, can win after death, ghost tasks ON", - "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", - "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", - "KillAttackerWhenAbilityRemaining": "Kill attacker when ability is remaining", - "JinxSpellTimes": "Amount of Jinx Spells", - "CollectorCollectAmount": "Required number of votes", - "GlitchCanVote": "Can vote", - "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", - "MeetingReserved": "Max Bullets reserved for a meeting", - "AccurateCheckMode": "Can know specific role when tasks are not done", - "RandomActiveRoles": "Show random active roles in Fortune Teller hints", - "CamouflageCooldown": "Camouflage Cooldown", - "CamouflageDuration": "Camouflage Duration", - "NinjaMarkCooldown": "Mark Cooldown", - "NinjaAssassinateCooldown": "Assassinate Cooldown", - "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", - "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", - "JudgeCanTrialNeutralB": "Can trial Neutral Benign", - "JudgeCanTrialNeutralK": "Can trial Neutral Killing", - "JudgeCanTrialNeutralE": "Can trial Neutral Evil", - "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", - "JudgeCanTrialNeutralA": "Can trial Neutral Apocalypse", - "JudgeCanTrialSidekick": "Can trial Sidekick", - "JudgeCanTrialInfected": "Can trial Infected", - "JudgeCanTrialContagious": "Can trial Contagious", - "JudgeTryHideMsg": "Hide Judge's commands", - "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", - "JudgeCanTrialMadmate": "Can trial Madmates", - "JudgeCanTrialCharmed": "Can trial Charmed players", - "JudgeDead": "Sorry, you can't trial players after death.", - "JudgeTrialMax": "\nNo more trials left!", - "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", - "Judge_TrialKill": "{0} was judged.", - "Judge_TrialKillTitle": "COURT", - "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Judge_TrialNull": "Please choose a living player for the trial", - "VeteranSkillMaxOfUseage": "Max number of Alerts", - "SwooperCooldown": "Swoop Cooldown", - "SwooperDuration": "Swoop Duration", - "WraithCooldown": "Vanish Cooldown", - "WraithDuration": "Vanish Duration", - "BastionNotify": "A bomb was set off", - "EnteredBombedVent": "That vent was bombed!", - "BastionVentButtonText": "Bomb", - "BombsClearAfterMeeting": "Bombs clear after meetings", - "BastionMaxBombs": "(Initial) Maximum bombs", - "VentBombSuccess": "Bomb has been planted", - "LowLoadMode": "Low Load Mode", - "ShowLobbyCode": "Show lobby code in Discord status", - "BKProtectDuration": "Protection Duration", - "FollowerMaxBetTimes": "Maximum Number of Follows", - "FollowerBetCooldown": "Follow Cooldown", - "FollowerMaxBetCooldown": "Maximum Follow Cooldown", - "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", - "FollowerKnowTargetRole": "Follower knows their target's role", - "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", - "FortuneTellerHideVote": "Hide Fortune Teller's Votes", - "CultistCharmCooldown": "Charm Cooldown", - "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", - "CultistCharmMax": "Maximum Number Of Charm", - "CultistKnowTargetRole": "Know Charmed Player's Role", - "CultistTargetKnowOtherTarget": "Charmed players know each other", - "CultistCanCharmNeutral": "Neutral Roles can be Charmed", - "InfectiousBiteCooldown": "Infect Cooldown", - "KnowTargetRole": "Knows role of target", - "TargetKnowsLawyer": "Target knows their Lawyer", - "InfectiousBiteMax": "Maximum Infections", - "InfectiousKnowTargetRole": "Know infected player's role", - "InfectiousTargetKnowOtherTarget": "Infected players know each other", - "DoubleClickKill": "Double click to kill the target", - - "VirusInfectMax": "Maximum Number Of Spreads", - "VirusKnowTargetRole": "Know Contagious Player's Role", - "VirusTargetKnowOtherTarget": "Contagious players know each other", - "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", - "Virus_ContagiousCountMode": "Contagious players count as", - "Virus_ContagiousCountMode_None": "Nothing", - "Virus_ContagiousCountMode_Virus": "Virus", - "Virus_ContagiousCountMode_Original": "Original Team", - "VirusNoticeTitle": "[ Infected Corpse! ]", - "VirusNoticeMessage": "The body you reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", - "VirusNoticeMessage2": "The body you reported was infected by the Virus! Vote the Virus out during this meeting, or you will die.", - - "Cultist_CharmedCountMode": "Charmed players count as", - "Cultist_CharmedCountMode_None": "Nothing", - "Cultist_CharmedCountMode_Cultist": "Cultist", - "Cultist_CharmedCountMode_Original": "Original Team", - - "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", - "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", - "JackalResetKillCooldownOn": "Kill Cooldown On Reset", - "JackalCanRecruitSidekick": "Can recruit Sidekick", - "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", - "Jackal_SidekickCountMode": "Sidekicks count as", - "Jackal_SidekickCountMode_None": "Nothing", - "Jackal_SidekickCountMode_Jackal": "Jackal", - "Jackal_SidekickCountMode_Original": "Original Team", - "Jackal_SidekickAssignMode": "Sidekick Assign Mode", - "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", - "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", - "Jackal_SidekickAssignMode_Recruit": "Recruit Only", - "JackalWinWithSidekick": "Jackal can win with Sidekick's team", - "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", - "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", - "JackalCanKillSidekick": "Jackal can kill Sidekick", - - "ImpCanBeNecroview": "Impostors can become Necroview", - "CrewCanBeNecroview": "Crewmates can become Necroview", - "NeutralCanBeNecroview": "Neutrals can become Necroview", - "ImpCanBeInLove": "Impostors can be in love", - "CrewCanBeInLove": "Crewmates can be in love", - "NeutralCanBeInLove": "Neutrals can be in love", - "ImpCanBeOblivious": "Impostors can become Oblivious", - "CrewCanBeOblivious": "Crewmates can become Oblivious", - "NeutralCanBeOblivious": "Neutrals can become Oblivious", - "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", - "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", - "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", - "HexesLookLikeSpells": "Hexes appear as spells", - "HexButtonText": "Hex", - "ObliviousBaitImmune": "Immune to Bait", - "ImpCanBeOnbound": "Impostors can become Onbound", - "CrewCanBeOnbound": "Crewmates can become Onbound", - "NeutralCanBeOnbound": "Neutrals can become Onbound", - - "ImpCanBeRebound": "Impostors can become Rebound", - "CrewCanBeRebound": "Crewmates can become Rebound", - "NeutralCanBeRebound": "Neutrals can become Rebound", - - "CrewCanBeMundane": "Crewmates can become Mundane", - "NeutralCanBeMundane": "Neutrals can become Mundane", - "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", - - "ImpCanBeUnreportable": "Impostors can become Disregarded", - "CrewCanBeUnreportable": "Crewmates can become Disregarded", - "NeutralCanBeUnreportable": "Neutrals can become Disregarded", - "PacifistCooldown": "Ability Cooldown", - "PacifistMaxOfUseage": "Max Number of Ability Uses", - "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", - "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", - - "PresidentAbilityUses": "Max Number of Ability Uses", - "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", - "HidePresidentEndCommand": "Hide President's commands", - "NeutralsSeePresident": "Neutrals can see revealed President", - "MadmatesSeePresident": "Madmates can see revealed President", - "ImpsSeePresident": "Impostors can see revealed President", - "PresidentDead": "Sorry, you can't force end the meeting after death.", - "PresidentEndMax": "No more force end meeting uses left!", - "PresidentRevealMax": "You have already revealed yourself...", - "PresidentRevealed": "[{0}] has chosen to reveal themselves as President!", - "GuessPresident": "President has revealed themselves. You can't guess them.", - "PresidentRevealTitle": "PRESIDENT REVEAL", - - "LuckyProbability": "Probability of surviving a kill", - "ImpCanBeLucky": "Impostors can become Lucky", - "CrewCanBeLucky": "Crewmates can become Lucky", - "NeutralCanBeLucky": "Neutrals can become Lucky", - "ImpCanBeFool": "Impostors can become Fool", - "CrewCanBeFool": "Crewmates can become Fool", - "NeutralCanBeFool": "Neutrals can become Fool", - "ImpCanBeDoubleShot": "Impostors can have Double Shot", - "CrewCanBeDoubleShot": "Crewmates can have Double Shot", - "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", - "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", - "DisableReportWhenCamouflageIsActive": "Disable body reporting when camouflage is active", - "CanUseCommsSabotage": "Can use comms sabotage", - "ModTag": "Moderator♥", - "ApplyModeratorList": "Apply Moderator List", - "VipTag": "VIP★", - "ApplyVipList": "Apply VIP List", - "AllowSayCommand": "Allow moderators to use /say command", - "KickCommandDisabled": "The kick command is currently disabled.", - "KickCommandNoAccess": "You do not have access to the kick command.", - "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reason]' to kick a player.\nExample :- /kick 5 not following rules", - "KickCommandKickHost": "You are not permitted to kick the host.", - "KickCommandKickMod": "You are not permitted to kick other moderators.", - "KickCommandKicked": "was kicked from the game by ", - "KickCommandKickedRole": "Their role was", - "BanCommandDisabled": "The ban command is currently disabled.", - "BanCommandNoAccess": "You do not have access to the ban command.", - "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", - "BanCommandBanHost": "You are not permitted to ban the host.", - "BanCommandBanMod": "You are not permitted to ban other moderators.", - "BanCommandBanned": "was banned from the game by ", - "BanCommandBannedRole": "Their role was", - "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", - "ColorCommandDisabled": "The modcolor command is currently disabled.", - "ColorCommandNoAccess": "You do not have access to the modcolor command.", - "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change the color of MODERATOR♥.\nExample :- /modcolor 33ccff", - "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", - "VipColorCommandDisabled": "The vipcolor command is currently disabled.", - "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", - "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change the color of VIP★.\nExample :- /vipcolor 33ccff", - "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", - "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change the color of your tag.\nExample :- /tagcolor ff00ff", - "midCommandDisabled": "The mid command is currently disabled.", - "midCommandNoAccess": "You do not have access to the mid command.", - "WarnCommandDisabled": "The warn command is currently disabled.", - "WarnCommandNoAccess": "You do not have access to the warn command.", - "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", - "WarnCommandWarnHost": "You are not permitted to warn the host.", - - "WarnCommandWarnMod": "You are not permitted to warn other moderators.", - "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", - "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", - "SayCommandDisabled": "The say command is currently disabled.", - "MessageFromModerator": "MODERATOR", - "DeathReason.Kill": "Kill", - "DeathReason.Vote": "Ejected", - "DeathReason.Suicide": "Suicide", - "DeathReason.Spell": "Spelled", - "DeathReason.Cursed": "Cursed", - "DeathReason.Hex": "Hexed", - "DeathReason.Bite": "Bitten", - "DeathReason.Poison": "Poisoned", - "DeathReason.Gambled": "Guessed", - "DeathReason.FollowingSuicide": "Heartbroken", - "DeathReason.Bombed": "Exploded", - "DeathReason.Misfire": "Misfire", - "DeathReason.Torched": "Burned", - "DeathReason.Sniped": "Sniped", - "DeathReason.Execution": "Executed", - "DeathReason.Fall": "Fall", - "DeathReason.Revenge": "Revenge", - "DeathReason.Eaten": "Eaten", - "DeathReason.Sacrifice": "Victim", - "DeathReason.Quantization": "Quantization", - "DeathReason.Overtired": "Overtired", - "DeathReason.Ashamed": "Ashamed", - "DeathReason.PissedOff": "Destroyed", - "DeathReason.Dismembered": "Dismembered", - "DeathReason.LossOfHead": "Strangled", - "DeathReason.Trialed": "Judged", - "DeathReason.Infected": "Infected", - "DeathReason.Jinx": "Jinxed", - "DeathReason.Pirate": "Plundered", - "DeathReason.Shrouded": "Shrouded", - "DeathReason.etc": "Other", - "DeathReason.Mauled": "Mauled", - "DeathReason.Hack": "Hacked", - "DeathReason.Curse": "Cursed", - "DeathReason.Drained": "Drained", - "DeathReason.Shattered": "Shattered", - "DeathReason.Trap": "Trapped", - "DeathReason.Targeted": "Targeted", - "DeathReason.Retribution": "Retribution", - "DeathReason.Slice": "Sliced", - "DeathReason.BloodLet": "Bleed", - "DeathReason.Armageddon": "Armageddon", - "DeathReason.Starved": "Starved", - "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", - "Alive": "Alive", - "Disconnected": "Disconnected", - "Win": " Wins!", - - "Last-": "Last ", - "Madmate-": "Madmate ", - "Recruit-": "Recruit ", - "Charmed-": "Charmed ", - "Soulless-": "Soulless ", - "Infected-": "Infected ", - "Contagious-": "Contagious ", - "Admired-": "Admired ", - - "DeputyHandcuffCooldown": "Handcuff Cooldown", - "DeputyHandcuffMax": "Maximum Handcuffs", - "DeputyHandcuffedPlayer": "Handcuffed target", - "HandcuffedByDeputy": "You were handcuffed!", - "DeputyInvalidTarget": "Target cannot be handcuffed", - "DeputyHandcuffText": "Handcuff", - "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", - - "RejectShapeshift.AbilityWasUsed": "Ability was used", - - "EscapisMtarkedPosition": "You marked self-position", - - "InvestigateCooldown": "Investigate Cooldown", - "InvestigateMax": "Maximum Investigations", - "InvestigateRoundMax": "Maximum Investigations in one round", - - "Color.Red": "Red", - "Color.Green": "Green", - "Color.Gray": "Gray", - "InvestigatorInvestigatedPlayer": "Player Investigated", - "InvestigatorInvalidTarget": "Can not investigate", - "InvestigatorButtonText": "Check", - - "Investigator.Suspicion": "Suspicion", - "Investigator.Role": "Role", - "SabotageCooldownControl": "Sabotage Cooldown Control", - "SabotageCooldown": "Sabotage Cooldown", - "SabotageTimeControl": "Sabotage Duration Control", - "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", - "SkeldO2TimeLimit": "The Skeld O2 Time Limit", - "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", - "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", - "PolusReactorTimeLimit": "Polus Reactor Time Limit", - "AirshipReactorTimeLimit": "Airship Reactor Time Limit", - "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", - "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", - "CommandList": "★ Command list:", - "Command.now": "→ Display active Settings", - "Command.roles": "[RoleName] → Display Role description", - "Command.myrole": "→ Displays a description of your role", - "Command.lastresult": "→ Display match results", - "Command.winner": "→ Display winners", - "CommandOtherList": "● Other commands:", - "Command.color": "[Color] → Change your color", - "Command.rename": "[Name] → Change Host Name", - "Command.quit": "→ I don't want to enter this lobby anymore", - "CommandHostList": "▲ Host Commands:", - "Command.say": "[Content] → Send message as Host", - "Command.mw": "[Seconds] → Set the message waiting duration", - "Command.solvecover": "→ Fix an issue where role names overlap the messages", - "Command.kill": "[Player ID] → Kill assigned player", - "Command.exe": "[Player ID] → Eject assigned player", - "Command.level": "[Level] → Change your in-game level", - "Command.idlist": "→ Display a list of player IDs", - "Command.qq": "→ Lobby will be posted on QQ website (China only)", - "Command.dump": "→ Output Log to Desktop", - "Command.death": "→ Display info on how you died", - "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Capitan to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.", - - "Command.iconinfo": "→ Display info on in-meeting icons", - "Command.iconhelp": "→ Display info on in-meeting icons to everyone", - "Command.Poll": "→ Start a poll with up-to 5 choices", - "IconsTitle": "Icon Meanings⚠", - "Remaining.ImpostorCount": "Impostors left: {0}", - "Remaining.MadmateCount": "Madmates left: {0}", - "Remaining.NeutralCount": "Neutral Killers left: {0}", - "Remaining.ApocalypseCount": "Neutral Apocalypse left: {0}", - "EnableKillerLeftCommand": "Enable use of /kcount command", - "ShowMadmatesInLeftCommand": "Show Madmates (including add-ons)", - "ShowApocalypseInLeftCommand": "Show Neutral Apocalypse", - "SeeEjectedRolesInMeeting": "See ejected roles in meetings", - - "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", - "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", - "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", - "NemesisKillDead": "Choose a living player to take revenge", - "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", - "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", - "NemesisKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", - - "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period!", - "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", - "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", - "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", - "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer. This death is most likely suicide.", - "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", - "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", - "GuessDead": "Sorry, but it's impossible to guess roles after your death", - "GuessSuperStar": "The Super Star can't be guessed... you thought it would be that easy, right?", - "GuessNotifiedBait": "Bait can't be guessed because it was announced. You thought it would be that easy, right?", - "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", - "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", - "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", - "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", - "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", - "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", - "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", - "GuessKill": "{0} was guessed", - "GuessNull": "Please select an ID of a living player to guess their role", - "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "GGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", - "EGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", - "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all their tasks are done? Nice try. You're not getting out of this that easily.", - "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot, so you get another chance!", - "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", - "GuessDisabled": "Sorry, the host restricted guessing for your role.", - "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", - "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", - "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", - "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", - "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", - "GuessShielded": "Sorry, you can't guess the player who the Medic shields", - "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", - "MimicDeadMsg": "Mimic's hint: ", - "FortuneTellerCheck": "According to your fortune...", - "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", - "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", - "FortuneTellerCheckReachLimit": "You've run out of fortunes.", - "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", - "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", - "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", - - "MediumContactLimit": "Max number of contacts (ability uses)", - "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", - "MediumTitle": "MEDIUM", - "MediumHelp": "/ms yes to agree\n/ms no to disagree", - "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", - "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", - "MediumDone": "You successfully responded to the Medium.", - "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", - "MediumNotifySelf": "You established contact with {0}. Please ask them questions and wait for them to respond.\n\nRemaining ability uses: {1}", - "MediumKnowPlayerDead": "Someone died somewhere", - - "ByBard": "by Bard", - "ByBardGetFailed": "Oops, I seem to be out of inspiration.", - "GangsterSuccessfullyRecruited": "You successfully recruited a player", - "GangsterRecruitmentFailure": "Target cannot be recruited", - "BeRecruitedByGangster": "The Gangster has recruited you", - "KamikazeHostage": "Can't hold target hostage", - "VeteranOnGuard": "Ability in use", - "VeteranOffGuard": "Ability expired, {0} uses remain", - "VeteranMaxUsage": "Ability use limit reached", - "GrenadierSkillInUse": "Ability in use", - "GrenadierSkillStop": "Ability expired", - "TicketsStealerGetTicket": "You've got {0} votes", - "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", - "CleanerCleanBody": "The body has been cleaned", - "QuickShooterStoraging": "Bullets stored successfully", - "PoisonerTargetDead": "Target died", - "BloodthirstAdded": "Your bloodthirst is now active!", - "WarlockNoTarget": "Manipulation failed due to no target", - "WarlockNoTargetYet": "You haven't marked a target.", - "WarlockTargetDead": "Manipulation failed due to target dead", - "WarlockControlKill": "Target died", - "OnCelebrityDead": "Warning: Celebrity death!", - "OnCyberDead": "Warning: Cyber died!", - "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", - "TeleportedByTransporter": "Swapping places with: {0}", - "ErrorTeleport": "Teleport failed", - "EraseLimit": "Max Erases", - "EraserHideVote": "Hide Eraser Votes", - "EraserEraseMsgTitle": "ERASER", - "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", - "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", - "EraserEraseSelf": "Unfortunately, you can't erase yourself... Wait, why would you do that in the first place?!", - "EraserTryingGuessErasedPlayer": "You can't guess the role of the player you erased, except add-ons", - "LostRoleByEraser": "You lost your role because of the Eraser", - "KilledByScavenger": "The Scavenger killed you and thus teleported off-map", - "SnitchDoneTasks": "Call a meeting to find the impostors", - "SwooperCanVent": "Vent to turn invisible", - "SwooperInvisState": "You're invisible", - "SwooperInvisStateOut": "You're now visible", - "SwooperInvisInCooldown": "Swoop cooldown isn't up yet. Swooping failed", - "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", - "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", - "WraithCanVent": "Vent to turn invisible", - "WraithInvisState": "You are invisible", - "WraithInvisStateOut": "You are visible again", - "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", - "WraithInvisStateCountdown": "Invisibility will expire in {0}s", - "WraithInvisCooldownRemain": "{0}s left in invisibility", - "WerewolfKillButtonText": "Maul", - "BKInProtect": "Currently immortal", - "BKProtectOut": "Shield expired", - "BKSkillTimeRemain": "You're immune for {0} seconds", - "BKSkillNotice": "Kill a player to enter immune status", - "BKOffsetKill": "Someone tried killing you", - "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", - "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", - "FollowerBetPlayer": "You're now following your target", - "FollowerBetOnYou": "The Follower is now following you", - "CultistCharmedPlayer": "You successfully charmed a player", - "CharmedByCultist": "You have been charmed by the Cultist", - "CultistInvalidTarget": "Target cannot be charmed", - "KillBaitNotify": "You'll self-report in {0}s", - "InfectiousInvalidTarget": "Target cannot be infected", - "BittenByInfectious": "The Infectious infected you!", - "InfectiousBittenPlayer": "You successfully infected a player", - "GuessNotAllowed": "Sorry, your role does not have access to guessing.", - "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", - "GuessSpecter": "You can't guess a Specter. That allows them to win!", - "PacifistOnGuard": "Ability used, {0} uses remain", - "PacifistMaxUsage": "Ability use limit reached", - "PacifistSkillNotify": "Pacifist reset your kill cooldown", - "BeRecruitedByJackal": "The Jackal has recruited you", - "CoronerTrackRecorded": "Track recorded", - "CoronerNoTrack": "Nothing to track", - "CoronerIsTrackingYou": "The Coroner is tracking you!", - "CoronerReportButtonText": "Track", - "MerchantAddonDelivered": "Add-on sold", - "MerchantAddonSell": "The Merchant sold you a new Add-on", - "MerchantAddonSellFail": "Could not sell an Add-on", - "BribedByMerchant": "The Merchant bribed you. You can't kill him", - "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", - "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", - "TrapTrapsterBody": "Trap Trapster's body", - "TrapConsecutiveBodies": "Trap consecutive bodies", - "HauntedByEvilSpirit": "Haunted by an Evil Spirit", - "MonarchKnightCooldown": "Knight Cooldown", - "MonarchKnightMax": "Maximum Knights", - "HideAdditionalVotesForKnighted": "Hide additional vote for Knighted players", - "MonarchKnightedPlayer": "You successfully knighted a player!", - "KnightedByMonarch": "A Monarch has knighted you!", - "MonarchInvalidTarget": "Target cannot be knighted", - "GhostTransformTitle": "Your Role Has Transformed!", - "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", - "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", - "OverseerRevealCooldown": "Reveal Cooldown", - "OverseerRevealTime": "Reveal Time", - "OverseerVision": "Overseer Vision", - "MerchantMaxSell": "Max number of Add-ons to sell", - "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", - "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", - "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", - "MerchantTargetCrew": "Can sell to Crewmates", - "MerchantTargetImpostor": "Can sell to Impostors", - - "MerchantTargetNeutral": "Can sell to Neutrals", - "MerchantSellHelpful": "Can sell Helpful Add-ons", - "MerchantSellHarmful": "Can sell Harmful Add-ons", - "MerchantSellMixed": "Can sell Mixed Add-ons", - "MerchantSellExperimental": "Can sell experimental Add-ons", - "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", - "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", - "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", - - "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", - "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", - "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", - "SpiritcallerProtectTime": "Evil Spirit ability protect time", - "SpiritcallerCauseVision": "Evil Spirit ability caused vision", - "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", - "Message.SetToSeconds": "Set to [{0}] seconds.", - "Message.MessageWaitHelp": "Specify the first argument in seconds.", - "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", - "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", - "Message.SyncButtonLeft": "There are {0} more emergency buttons left", - "Message.Executed": "{0} was executed", - "Message.HideGameSettings": "The host has hidden the game settings.", - "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", - "Message.NoDescription": "No description", - "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", - "Message.BannedByBanList": "{0} was banned because they were banned in the past.", - "Message.BannedByEACList": "{0} has been banned because he is in the EAC list of Banned people.", - "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", - "Message.DumpcmdUsed": "{0} used /dump command.", - "Message.KickedByInvalidFriendCode": "{0} was kicked because their friend code is invalid.", - "Message.TempBannedByInvalidFriendCode": "{0} was temporarily banned because their friend code is invalid.", - "Message.AddedPlayerToBanList": "Added {0} to the ban list", - "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", - "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", - "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", - "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", - "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", - "Message.NoticeByEAC": "[{0}]Detected:{1}", - "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", - "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", - "Message.KickedByWhiteList": "{0} kicked because their friendcode was not found in WhiteList.txt", - "Message.SetLevel": "Your game level is set to: {0}", - "Message.SetColor": "Your color is set to: {0}", - "Message.SetName": "Your name is set to: {0}", - "Message.AllowLevelRange": "The game level can be set in the range: 0-100", - "Message.AllowNameLength": "Nickname can be set length: 1-10", - "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", - "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", - "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", - "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", - "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", - "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", - "Message.HostLeftGameInGame": "★Warning★ Host left the game, and the game wouldn't start normally next time. Please exit the lobby or wait until the new Host opens a lobby.", - "Message.HostLeftGameInLobby": "★Warning★ Host left the game, and the game wouldn't start normally next time. If the new Host has TOHE, you need to re-enter the lobby to play normally.", - "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, start a game and end it immediately to reset the lobby!", - "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please exit the lobby or wait until the new Host opens a lobby.", - "Message.LobbyShared": "The lobby has successfully been shared!", - "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", - "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", - "Message.YTPlanSelected": "In the next game, your role will be {0}", - "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", - "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. If the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", - "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", - "Message.MaxPlayers": "Maximum players set to ", - "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", - "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", - "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", - "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", - "Message.MeCommandNoPermission": "You are not allowed to use /me command for others", - - "PollTitle": "〖 Poll 〗", - "PollResultTitle": "Poll Results", - "Poll.Result": "And... The winner was {0} with {1} votes!\n\nRunner ups:", - "Poll.Tied": "Uh oh, The vote was tied between {0}, all having {1} votes.", - "Poll.MissingPlayers": "You can't start a poll with yourself dummy ;3", - - "Poll.Begin": "You may vote using /pv {answer}, ps: a number also works.", - "Poll.TimeInfo": "The results will be final in 2 minuttes", - "Poll.OnlyInLobby": "<#ab4f75>Sorry, this command may only be used in lobby", - - "Poll.Inactive": "There isn't any active poll currently.", - "Poll.AlreadyVoted": "You already cast your vote, so it won't be counted.", - - "Poll.VotingInfo": "Use /pv {answer} to vote, answer can be a character or a number.", - "Poll.YouVoted": "You have voted for {0}, which has now {1} votes.", - "PollUsage": "To create a poll type \n/poll {Question}? {Answer A} {Answer B} \n{Answer C (Optional)} {Answer D (Optional)} {Answer E (Optional)}\nIt is important you end the question with a ? \n\nUse /poll Replay to replay the latest poll", - "Replay": "Replay", - - "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", - "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", - "WarningTitle": "Warning!", - "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", - "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map, the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", - - "AntiBlackoutProtectionTitle": "Anti Blackout", - "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normally", - "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in the last meeting.\n", - "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", - - - - "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE are installed.", - "Warning.NoModHost": "TOHE is not installed on the host", - "Warning.MismatchedVersion": "{0} has a different version of {1}", - "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", - "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", - "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", - "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", - "Error.InvalidColor": "Error: Only default colors are available", - "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors. Otherwise, it will result in a serious error", - "ErrorLevel1": "Bugs may occur.", - "ErrorLevel2": "This may be a bug.", - "ErrorLevel3": "This version shouldn't have been released.", - "TerminateCommand": "Abort Command", - "ERR-000-000-0": "No Error", - "ERR-000-900-0": "Test Error Lv.0", - "ERR-000-910-1": "Test Error Lv.1", - "ERR-000-920-2": "Test Error Lv.2", - "ERR-000-930-3": "Test Error Lv.3", - "ERR-000-804-1": "Sorry, TOHE temporarily not support the Vanilla HnS, so mod unloaded", - "ERR-001-000-3": "Main dictionary has duplicated keys.", - "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", - "DefaultSystemMessageTitle": "SYSTEM MESSAGE", - "MessageFromTheHost": "HOST MESSAGE", - "MessageFromEAC": "EAC", - "DetectiveNoticeTitle": "INVESTIGATION", - "SleuthNoticeTitle": "SLEUTH", - "GuessKillTitle": "GUESSING INFO", - "CelebrityNewsTitle": "CELEBRITY", - "CyberNewsTitle": "CYBER", - "GodAliveTitle": "GOD ", - "WorkaholicAliveTitle": "WORKAHOLIC", - "BaitAliveTitle": "BAIT", - "MessageFromKPD": "KARPED1EM ", - "MessageFromSponsor": "SPONSOR MESSAGE ", - "MessageFromDev": "DEVELOPER MESSAGE ", - "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", - "MimicMsgTitle": "MIMIC", - "MorticianCheckTitle": "CORPSE EXAMINATION", - "NemesisRevengeTitle": "NEMESIS", - "RetributionistRevengeTitle": "RETRIBUTIONIST", - "TabVanilla.GameSettings": "Game Settings", - "TabGroup.SystemSettings": "System Settings", - "TabGroup.ModSettings": "Mod Settings", - "TabGroup.ModifierSettings": "Game Modifiers", - "TabGroup.CrewmateRoles": "Crewmate Roles", - "TabGroup.NeutralRoles": "Neutral Roles", - "TabGroup.ImpostorRoles": "Impostor Roles", - "TabGroup.Addons": "Add-Ons", - "TabMenuDescription_General": "Here you can configure the functions that are in the mod", - "TabMenuDescription_Roles&AddOns": "Here you can add, remove and change the settings of all roles or add-ons in the mod", - "Experimental.Roles": "★ Experimental Roles (NOTICE: Use with caution, as these require testing)", - "ActiveRolesList": "Active Roles List", - "ForExample": "Example Use", - "updateButton": "Update", - "updatePleaseWait": "Please Wait...", - "updateManually": "Update failed.\nPlease try again or Update Manually.", - "updateInProgress": "Updating...", - "deletingFiles": "Deleting update files...", - "updateRestart": "Update Finished!\nPlease restart the game.", - "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease Update.", - "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", - "UnsupportedVersion": "Unsupported Among Us version.\nPlease Update Among Us", - "DisabledByProgram": "The program has disabled public rooms", - "EnterVentToWin": "Enter Vent to Win!!", - "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", - "FireworkerPutPhase": "{0} Fireworker Left", - "FireworkerWaitPhase": "Wait for it...", - "FireworkerReadyFirePhase": "Fire!", - "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", - "On": "ON", - "Off": "OFF", - "ColoredOn": "ON", - "ColoredOff": "OFF", - "CurrentActiveSettingsHelp": "Current Active Settings Help", - "WitchCurrentMode": "Current Mode", - "WitchModeKill": "Kill", - "WitchModeSpell": "Spell", - "HexMasterModeHex": "Hex", - "HexMasterModeKill": "Kill", - "PoisonerPoisonButtonText": "Poison", - "WitchModeDouble": "Double Click = Kill, Single Click = Spell", - "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", - "BountyCurrentTarget": "Current Target", - "Roles": "Roles", - "Settings": "Settings", - "Addons": "Add-Ons", - "LastResult": "★ Match Results", - "LastEndReason": "★ End Reason", - "KillLog": "Kill Log", - "Maximum": "Max", - "RoleRate": "ON", - "RoleOn": "ALWAYS", - "RoleOff": "OFF", - "Chance0": "0%", - "Chance5": "5%", - "Chance10": "10%", - "Chance15": "15%", - "Chance20": "20%", - "Chance25": "25%", - "Chance30": "30%", - "Chance35": "35%", - "Chance40": "40%", - "Chance45": "45%", - "Chance50": "50%", - "Chance55": "55%", - "Chance60": "60%", - "Chance65": "65%", - "Chance70": "70%", - "Chance75": "75%", - "Chance80": "80%", - "Chance85": "85%", - "Chance90": "90%", - "Chance95": "95%", - "Chance100": "100%", - "Preset": "Preset", - "Preset_1": "Preset 1", - "Preset_2": "Preset 2", - "Preset_3": "Preset 3", - "Preset_4": "Preset 4", - "Preset_5": "Preset 5", - "Standard": "Standard", - "GameMode": "Game Mode", - "PressTabToNextPage": "Press Tab or Number for Next Page...", - "RoleSummaryText": "Role Summary:", - "doOverride": "Override %role%'s Tasks", - "assignCommonTasks": "%role% has Common Tasks", - "roleLongTasksNum": "Amount of Long Tasks for %role%", - "roleShortTasksNum": "Amount of Short Tasks for %role%", - "Format.Players": "{0}", - "Format.Seconds": "{0}s", - "Format.Percent": "{0}%", - "Format.Times": "{0}", - "Format.Multiplier": "{0}x", - "Format.Votes": "{0}", - "Format.Pieces": "{0}", - "Format.Health": "{0}", - "Format.Level": "{0}", - "KillButtonText": "Kill", - "ReportButtonText": "Report", - "VentButtonText": "Vent", - "SabotageButtonText": "Sabotage", - "SniperSnipeButtonText": "Snipe", - "FireworkerExplosionButtonText": "Detonate", - "FireworkerInstallAtionButtonText": "Install", - "MercenarySuicideButtonText": "Suicide Timer", - "WarlockCurseButtonText": "Curse", - "NinjaShapeshiftText": "Kill", - "NinjaMarkButtonText": "Mark", - "WitchSpellButtonText": "Spell", - "VampireBiteButtonText": "Bite", - "MinerTeleButtonText": "Warp", - "ArsonistDouseButtonText": "Douse", - "PuppeteerOperateButtonText": "Manipulate", - "WarlockShapeshiftButtonText": "Spell", - "BountyHunterChangeButtonText": "Swap", - "EvilTrackerChangeButtonText": "Track", - "InnocentButtonText": "Frame", - "PelicanButtonText": "Eat", - "DeceiverButtonText": "Cheat", - "PursuerButtonText": "Trick", - "GangsterButtonText": "Recruit", - "RevolutionistDrawButtonText": "Win over", - "HaterButtonText": "Hatred", - "MedicalerButtonText": "Protect", - "DemonButtonText": "Attack", - "SoulCatcherButtonText": "Teleport", - "LightningButtonText": "Evaporate", - "ProvocateurButtonText": "Greet", - "ButcherButtonText": "Dismember", - "BomberShapeshiftText": "Explode", - "QuickShooterShapeshiftText": "Keep", - "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", - "CamouflagerShapeshiftTextAfterDisguise": "Duration", - "AnonymousShapeshiftText": "Hack", - "DefaultShapeshiftText": "Shift", - "CleanerReportButtonText": "Clean", - "SwooperVentButtonText": "Swoop", - "SwooperRevertVentButtonText": "Expose", - "WraithVentButtonText": "Vanish", - "WraithRevertVentButtonText": "Expose", - "VectorVentButtonText": "Hop", - "VeteranVentButtonText": "Alert", - "GrenadierVentButtonText": "Flash", - "MayorVentButtonText": "Button", - "SheriffKillButtonText": "Shoot", - "UndertakerButtonText": "Mark", - "ArsonistVentButtonText": "Ignite", - "RevolutionistVentButtonText": "Revolution", - "FollowerKillButtonText": "Follow", - "PacifistVentButtonText": "Reset", - "CultistKillButtonText": "Charm", - "InfectiousKillButtonText": "Infect", - "MonarchKillButtonText": "Knight", - "OverseerKillButtonText": "Reveal", - "DisabledBySettings": "Disabled by Settings", - "Disabled": "Disabled", - "FailToTrack": "Failed To Track", - "KillCount": "Kills: {0}", - "CantUse.lastroles": "Unable to use /lastroles during a game.", - "CantUse.killlog": "Unable to use /killlog during a game.", - "CantUse.lastresult": "Unable to use /lastresult during a game.", - "IllegalColor": "Please enter the correct color", - "DisableUseCommand": "The Host's settings do not allow this command to be used.", - "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", - "PlayerIdList": "List of player IDs: ", - "CancelStartCountDown": "The starting countdown was canceled", - "RestTOHESetting": "TOHE settings have been restored to default", - "FPSSetTo": "FPS Set To: {0}", - "HostKillSelfByCommand": "The lobby Host decided to commit suicide", - "SyncCustomSettingsRPC": "Synchronized RPC", - "Mode": "Mode", - "Target": "Target", - "PlayerInfo": "Player Info", - "NoInfoExists": "No Info Exists", - "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", - "PlayerLeftByError": "Game will auto-end to prevent black screens.", - "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", - "KickBecauseLowLevel": "{0} was kicked because their level was too low", - "TempBannedBecauseLowLevel": "{0} was temporarily banned because their level was too low", - "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", - - "FFADisplayScore": "Ranking: {0} Score: {1}", - "FFATimeRemain": "Time Remaining: {0} second(s)", - - "GameOver": "Game Over", - "TOHEOptions": "TOHE Options", - "Cancel": "Cancel", - "Back": "Back", - "Yes": "Yes", - "No": "No", - - "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent a black screen.", - "AntiBlackOutNotifyInLobby": "An error occurred to prevent a black screen. Do a «/dump» and send the logs to the discord server TOHE in «bug-reports» and we will try to fix it.", - - "EndWhenPlayerBug": "End the game when a modded player gets a critical error (While loading)", - "AntiBlackOutRequestHostToForceEnd": "You were the reason for the black screen. The game will end", - "AntiBlackOutHostRejectForceEnd": "You were the reason for the black screen, and the host is not going to end the game\nYou will be disconnected soon", - - "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred. To prevent a black screen, turn off [{1}] in settings.", - "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, the game will end to prevent a black screen.", - "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", - - "NextPage": "Next Page", - "PreviousPage": "Previous Page", - "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", - "PressF1ShowMainRoleDes": "Press F1: Show Role Description", - "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", - "PressF3ShowRoleSettings": "Press F3: Show Role Settings", - "PressF4ShowAddOnsSettings": "Press F4: Show Add-ons Settings", - "FakeTask": "Fake Tasks:", - "PVP.ATK": "Attack", - "PVP.DF": "Defend", - "PVP.RCO": "Recover", - "SettingsAreLoading": "Loading\nsettings...", - "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", - "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", - "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", - "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", - "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", - "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", - "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", - "IsGood": "{0} was a good guy", - "BelongTo": "{0} belongs to {1}", - "PlayerIsRole": "{0} was The {1}", - "PlayerExiled": "{0} was ejected", - "NoImpRemain": "0 Impostors remain", - "OneImpRemain": "1 Impostor remains", - "TwoImpRemain": "2 Impostors remain", - "ThreeImpRemain": "3 Impostors remain", - "ImpRemain": "{0} Impostors remaining", - "NeutralRemain": "\n{0} Neutral Killers remain", - "OneNeutralRemain": "\n{0} Neutral Killer remains", - "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", - "GameOverReason.HumansByTask": "The Crewmates completed all tasks", - "GameOverReason.HumansDisconnect": "Crewmates disconnected", - "GameOverReason.ImpostorByVote": "The Crewmates were ejected", - "GameOverReason.ImpostorByKill": "The Impostors killed everyone", - "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", - "GameOverReason.ImpostorDisconnect": "Impostors disconnected", - "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", - "DevAndSpnTitle": "TOHE family", - "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", - "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", - "SunnyboyChance": "Sunnyboy Chance", - "BardChance": "Bard Chance", - "SkeldChance": "Chance that the map is The Skeld", - "MiraChance": "Chance that the map is MIRA HQ", - "PolusChance": "Chance that the map is Polus", - "DleksChance": "Chance that the map is dlekS ehT", - "AirshipChance": "Chance that the map is Airship", - "FungleChance": "Chance that the map is The Fungle", - "UseMoreRandomMapSelection": "Use a more random map selection", - "CamouflageMode.Default": "Default", - "CamouflageMode.Host": "Host", - "CamouflageMode.Random": "Random", - "CamouflageMode.OnlyRandomColor": "Only Random Color", - "CamouflageMode.Karpe": "KARPED1EM", - "CamouflageMode.Lauryn": "Lauryn", - "CamouflageMode.Moe": "Moe", - "CamouflageMode.Pyro": "Pyro", - "CamouflageMode.ryuk": "ryuk", - "CamouflageMode.Gurge44": "Gurge44", - "CamouflageMode.TommyXL": "TommyXL", - "CamouflageMode.Sarha": "Sarha", - "DeathCmd.HeyPlayer": "Hey ", - "DeathCmd.YouAreRole": ", looks like you're the ", - "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", - "DeathCmd.KillerName": "You were killed by ", - "DeathCmd.KillerRole": "Their role is ", - "DeathCmd.DeathReason": "Your cause of death was ", - "DeathCmd.YourName": "You are ", - "DeathCmd.YourRole": "Your role is ", - "DeathCmd.Ejected": "You were ejected during a meeting", - "DeathCmd.Misfired": "You misfired.", - "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", - "DeathCmd.Lovers": "Your lover had died.", - - "RpsCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", - "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", - "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", - "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I have the world's worst luck algorithm.", - - "CoinFlipCommandInfo": "This Command can only be used when in the lobby or after you die.", - "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", - - "GNoCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", - "GNoLost": "Oh, you were so close! Just one more guess: you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", - "GNoLow": "Oh, you're really nailing this! It's so low. I almost need a shovel to dig it up!\nYou have {0} guesses left!", - "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high that I need a telescope to see it from here! \nYou have {0} guesses left!", - "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", - - "RandCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", - "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", - - "8BallTitle": "The Magic 8 Ball Reveals...", - "8BallYes": "Yes", - "8BallNo": "No", - "8BallMaybe": "Maybe", - "8BallTryAgainLater": "Ask again later", - "8BallCertain": "It is certain", - "8BallNotLikely": "Outlook not so good", - "8BallLikely": "Outlook good", - "8BallDontCount": "Don't count on it", - "8BallStop": "Stop using an 8Ball in an Among Us mod", - "8BallPossibly": "Possibly", - "8BallProbably": "Probably", - "8BallProbablyNot": "Probably not", - "8BallBetterNotTell": "Better not tell you now", - "8BallCantPredict": "Cannot predict now", - "8BallWithoutDoubt": "Without a doubt", - "8BallWithDoubt": "Very doubtful", - - "ChanceToMiss": "Chance to miss a kill", - - "SoulCollectorPointsToWin": "Required number of souls", - "SoulCollectorTarget": "You have predicted the death of {0}", - "SoulCollectorTitle": "SOUL COLLECTOR", - "SoulCollector_CollectOwnSoulOpt": "Can collect their own soul", - "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", - "SoulCollectorToDeath": "You have become Death!!!", - "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", - "GetPassiveSouls": "Gain a passive soul every round", - "PassiveSoulGained": "You have gained a passive soul from the underworld.", - "SoulCollectorTargetUsed": "You've already targeted someone this round!", - "SoulCollectorSoulGained": "Soul gained", - "SoulCollectorCanVent": "Soul Collector can Vent", - "DeathMeetingTimeIncrease": "Increased Meeting time when Death exists", - "SoulCollectorMeetingDeath": "Your target has died during the meeting. You have gained a soul.", - "SoulCollectorKillButtonText": "Predict", - - "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", - "ApocalypseImmune": "This player is immune because they are invincible!", - "BakerToFamine": "You have become Famine!!!", - "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", - "BakerAlreadyBreaded": "That player already has bread!", - "BakerBreadUsedAlready": "You've already given a player bread this round!", - "BakerBreaded": "Player given bread", - "BakerBreadNeededToTransform": "Required number of bread to become Famine", - "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", - "BakerKillButtonText": "Bread", - "BakerRevealBread": "Reveal", - "BakerRoleblockBread": "Roleblock", - "BakerBarrierBread": "Barrier", - "BakerCurrentBread": "Current Bread: ", - "BakerCanVent": "Baker can Vent", - "BakerBreadGivesEffects": "Bread gives additional effects", - "FamineKillButtonText": "Starve", - "FamineStarveCooldown": "Famine starve cooldown", - "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", - "FamineAlreadyStarved": "That player has already been starved!", - "FamineStarved": "Player starved", - - "ChronomancerKillCooldown": "Ability Charge Time", - "ChronomancerDecreaseTime": "Slaughter Decrease Time (lower is faster)", - "ChronomancerStartMassacre": "SLAUGHTER: ACTIVATED", - "ChronomancerVisionMassacre": "Vision When In Slaughter", - - "ShamanButtonText": "Voodoo", - "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", - "Shaman_KillerCannotMurderChosenTarget": "The killer cannot murder chosen target", - "VoodooCooldown": "Voodoo Cooldown", - - "AdminWarning": "Admin Table in use!", - "VitalsWarning": "Vitals in use!", - "DoorlogWarning": "Doorlogs in use!", - "CameraWarning": "Cameras in use!", - "MinWaitAutoStart": "Minutes to wait before auto-starting", - "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", - "PlayerAutoStart": "Minimum Player Threshold to auto-start", - "AutoStartTimer": "Initial countdown for auto-starting", - "ImmediateAutoStart": "Immediately start the game when reaching certain conditions", - "ImmediateStartTimer": "Initial countdown for Immediate-starting", - "StartWhenPlayersReach": "Immediately Start when we have enough players above", - "StartWhenTimerLowerThan": "Immediately Start when Lobby Timer goes below", - "AutoPlayAgainCountdown": "Delay before re-entering lobby", - "AutoPlayAgain": "Auto Play Again", - "AutoRehost": "Auto Re-Host on Bad Disconnect", - "CountdownText": "Rejoining lobby in {0}s", - "TimeMasterSkillDuration": "Time Shield Duration", - "TimeMasterSkillCooldown": "Time Shield Cooldown", - "TimeMasterOnGuard": "Time Shield is active!", - "TimeMasterSkillStop": "Time Shield has ended!", - "TimeMasterVentButtonText": "Time Shield", - "BodyCannotBeReported": "Body could not be reported", - "BurstKillDelay": "Burst Kill Delay", - "BurstNotify": "That was a Burst! Get in a vent or die.", - "ImpCanBeBurst": "Impostors can become Burst", - "CrewCanBeBurst": "Crewmates can become Burst", - "NeutralCanBeBurst": "Neutrals can become Burst", - "BurstFailed": "Burst failed to bomb you", - "ShroudButtonText": "Shroud", - "ShroudCooldown": "Shroud Cooldown", - "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", - "LudopathRandomKillCD": "Maximum kill cooldown", - "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", - "GodfatherTargetCountMode": "Killer turns into", - "GodfatherCount_Refugee": "Refugee", - "GodfatherCount_Madmate": "Madmate", - "MissChance": "Chance To Miss", - "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", - "HawkMissed": "Missed!", - "HawkCanKillNum": "Max Slices", - "HawkKillMax": "You've run out of ability uses", - "HawkKillTooManyDead": "Too many people are dead", - "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", - "BloodMoonCanKillNum": "Max BloodLettings", - "BloodMoonTimeTilDie": "Time Until Death", - "DeathTimer": "Death In: {DeathTimer}s", - "BerserkerKillCooldown": "Berserker kill cooldown", - "BerserkerMax": "Max level that Berserker can reach", - "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", - "WarHasImpostorVision": "War Has Impostor Vision", - "BerserkerCanVent": "Berserker Can Vent", - "WarCanVent": "War Can Vent", - "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", - "BerserkerOneKillCooldown": "Kill cooldown after unlocking", - "BerserkerTwoCanScavenger": "Unlock scavenged kills", - "BerserkerThreeCanBomber": "Unlock bombed kills", - "BerserkerFourCanNotKill": "Become War", - "BerserkerMaxReached": "Maximum level reached!", - "BerserkerLevelChanged": "Increased level to {0}", - "BerserkerLevelRequirement": "Level requirement for unlock", - "KilledByBerserker": "Killed by Berserker", - "BerserkerToWar": "You have become War!!!", - "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", - "WarKillCooldown": "War kill cooldown", - - "ImpCanBeUnlucky": "Impostors can become Unlucky", - "CrewCanBeUnlucky": "Crewmates can become Unlucky", - "NeutralCanBeUnlucky": "Neutrals can become Unlucky", - "BlackmailerSkillCooldown": "Blackmail Cooldown", - "BlackmailerMax": "Maximum times blackmailed players may speak", - "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", - "BlackmaileKillTitle": "BLACKMAILER", - "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", - "UnluckyKillSuicideChance": "Chance to suicide from killing", - "UnluckyVentSuicideChance": "Chance to suicide from venting", - "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", - "UnluckyOpenDoorSuicideChance": "Chance to suicide from opening a door", - "ImpCanBeVoidBallot": "Impostors can become VoidBallot", - "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", - "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", - "ImpCanBeAware": "Impostors can become Aware", - "NeutralCanBeAware": "Neutrals can become Aware", - "CrewCanBeAware": "Crewmates can become Aware", - "AwareKnowRole": "Knows the role of the player", - "AwareInteracted": "{0} tried to reveal your role.", - "AwareTitle": "AWARE MESSAGE", - "LighterVentButtonText": "Light", - "LighterSkillCooldown": "Light Cooldown", - "LighterSkillDuration": "Light Duration", - "LighterVisionNormal": "Increased Vision", - "LighterVisionOnLightsOut": "Increased Vision During Lights Out", - "LighterSkillInUse": "Ability in use", - "LighterSkillStop": "Ability expired", - "StealthDarkened": "Darkened: {0}", - "StealthExcludeImpostors": "Ignore Impostors when Blinding", - "StealthDarkenDuration": "Blinding Duration", - "PenguinAbductTimerLimit": "Dragging Time", - "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", - "PenguinKillButtonText": "Drag", - "PenguinTimerText": "Drag Timer", - "PenguinTargetOnCheckMurder": "You are grabbed. Try to escape that first!", - "WitnessTime": "Max Time after killing where killer appears red", - "WitnessButtonText": "Examine", - "WitnessFoundInnocent": "✓", - "WitnessFoundKiller": "⚠", - "SwapperMax": "Maximum swaps", - "CanSwapSelfVotes": "Can exchange your own votes.", - "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", - "CantSwapSelf": "Can't exchange of one's own vote", - "SwapVote": "The votes of {0} and {1} were swapped!", - "SwapDead": "Sorry, you can't swap votes after death.", - "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", - "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", - "Swap1": "Swap target 1 selected", - "Swap2": "Swap target 2 selected", - "CancelSwap": "Cleared your previous swap!", - "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your targets is dead.", - "Swap1=Swap2": "The target you input is the same as Swap target 1.\nPls input a different one", - "SwapTitle": "SWAPPER", - "SwapperTryHideMsg": "Try to hide Swapper's command", - "SwapperPreResult": "Currently, you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", - "ImpCanBeFragile": "Impostors can become Fragile", - "NeutralCanBeFragile": "Neutrals can become Fragile", - "CrewCanBeFragile": "Crewmates can become Fragile", - "ImpCanKillFragile": "Impostors can force kill Fragile", - "NeutralCanKillFragile": "Neutrals can force kill Fragile", - "CrewCanKillFragile": "Crewmates can force kill Fragile", - "FragileKillerLunge": "Killer lunges on kill", - "CrusaderSkillLimit": "Maximum Crusades", - "CrusaderSkillCooldown": "Crusade Cooldown", - "CrusaderKillButtonText": "Crusade", - "JailorKillButtonText": "Jail", - "AgitaterKillButtonText": "Pass", - "HasSerialKillerBuddy": "Has Serial Killer buddy", - "ChanceToSpawn": "Chance to spawn", - "ChanceToSpawnAnother": "Chance to spawn another", - "BloodthirstKillCD": "Bloodthirst Kill Cooldown", - "BloodthirstPlayerCount": "Max players alive for Bloodthirst", - "ReflectHarmfulInteractions": "Reflect harmful interactions", - - "ImpCanBeDiseased": "Impostors can become Diseased", - "NeutralCanBeDiseased": "Neutrals can become Diseased", - "CrewCanBeDiseased": "Crewmates can become Diseased", - "DiseasedCDOpt": "Increase the cooldown by", - "DiseasedCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeAntidote": "Impostors can become Antidote", - "NeutralCanBeAntidote": "Neutrals can become Antidote", - "CrewCanBeAntidote": "Crewmates can become Antidote", - "AntidoteCDOpt": "Decrease the cooldown by", - "AntidoteCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeRadar": "Impostors can become Radar", - "NeutralCanBeRadar": "Neutrals can become Radar", - "CrewCanBeRadar": "Crewmates can become Radar", - - "ImpCanBeGlow": "Impostors can become Glow", - "NeutralCanBeGlow": "Neutrals can become Glow", - "CrewCanBeGlow": "Crewmates can become Glow", - "GlowRadius": "Glow Radius", - "GlowVisionOthers": "Vision Boost for nearby Players", - "GlowVisionSelf": "Vision Boost for Glow", - - "ImpCanBeStubborn": "Impostors can become Stubborn", - "NeutralCanBeStubborn": "Neutrals can become Stubborn", - "CrewCanBeStubborn": "Crewmates can become Stubborn", - - "ImpCanBeAvanger": "Impostors can become Avenger", - "NeutralCanBeAvanger": "Neutrals can become Avenger", - "CrewCanBeAvanger": "Crewmates can become Avenger", - "ImpCanBeSleuth": "Impostors can become Sleuth", - "CrewCanBeSleuth": "Crewmates can become Sleuth", - "NeutralCanBeSleuth": "Neutrals can become Sleuth", - "SleuthCanKnowKillerRole": "Can find the role of the killer", - "SleuthNoticeKiller": "\nThe killer's role is {0}.", - "SleuthNoticeVictim": "{0}'s role is {1}.", - "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", - "BomberDiesInExplosion": "Bomber dies in their explosion", - "ImpostorsSurviveBombs": "Impostors survive bombs", - - "PunchingBagKillMax": "Amount of attacks needed to win", - "GuessPunchingBag": "You just tried to guess a Punching Bag!\nThey're now one step closer to winning!", - "GuessPunchingBagAgain": "You just tried to guess a Punching Bag again!\n\nIt no longer counts your attacks by guessing", - "PunchingBagKill": "You were attacked!", - "SelfGuessPunchingBag": "You can't self-guess as a Punching Bag, you cheater!", - "GuessPunchingBagBlocked": "Punching Bag cannot guess due to self-guessing.", - "EradicatePunchingBag": "You just tried to terminate punching bag, that is not allowed.", - - "RememberCooldown": "Imitate Cooldown", - "RefugeeKillCD": "Refugee's Kill Cooldown", - "RememberedNeutralKiller": "You remembered you were a neutral killer!", - "RememberedMaverick": "You remembered you were a Maverick!", - "RememberedPursuer": "You remembered you were a Pursuer!", - "RememberedFollower": "You remembered you were a Follower!", - "RememberedAmnesiac": "You failed to remember your role.", - "RememberedImitator": "You remembered you were an Imitator.", - "RememberedImpostor": "You remembered you were an Impostor!", - "RememberedCrewmate": "You remembered you were a crewmate!", - "ImitatorImitated": "An Imitator imitated your role!", - "ImitatorInvalidTarget": "Imitation failed", - "RememberButtonText": "Remember", - "ImitatorKillButtonText": "Imitate", - "IncompatibleNeutralMode": "If neutral is incompatible, turn into", - "RememberedYourRole": "An Amnesiac remembered your role!", - "YouRememberedRole": "You remembered who you were!", - - "BanditStealMode": "Steal Mode", - "BanditStealMode_OnMeeting": "On Meeting", - "BanditStealMode_Instantly": "Instantly", - "BanditMaxSteals": "Maximum Steals", - "BanditCanStealBetrayalAddon": "Can Steal Betrayal Add-ons", - "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", - "Bandit_NoStealableAddons": "Could not steal add-on from the player", - "BanditStealCooldown": "Steal cooldown", - - "DoppelMaxSteals": "Maximum Steals", - "DoppelCurrentVictimCanSeeRolesAsDead": "Last victim can see role and add-on info of alive players as a ghost", - - "NecromancerRevengeTime": "Necromancy time", - "NecromancerRevenge": "You have {0}s to kill {1}", - "NecromancerSuccess": "Necromancy complete! You live to see another day.", - "NecromancerHide": "Venting is disabled, hide from the Necromancer!", - "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", - "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", - "RetributionistKillMax": "You've reached the maximum amount of kills. You can't kill anymore!", - "RetributionistKillDead": "Choose a living player to kill.", - "RetributionistKillSucceed": "{0} was killed by the Retributionist!", - "RetributionistKillDisable": "You can't retribute until your tasks are done.", - "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", - "RetributionistCanKillNum": "Max retributions", - "RetributionistKillTooManyDead": "Too many players are dead. You can't retribute.", - "MinimumPlayersAliveToRetri": "Minimum players alive to retribute", - "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", - "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", - - "TwisterCooldown": "Twist Cooldown", - "TwisterButtonText": "Twist", - "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", - "InstigatorAbilityLimit": "Ability Use Count", - "InstigatorKillsPerAbilityUse": "Kills per Ability use", - - "CrewCanFindCaptain": "Crewmates can find Captain", - "MadmateCanFindCaptain": "Madmates can find Captain", - "ReducedSpeed": "Reduced speed", - "ReducedSpeedTime": "Time duration for reduced speed", - "CaptainCanTargetNB": "Captain can target Neutral Benign", - "CaptainCanTargetNE": "Captain can target Neutral Evil", - "CaptainCanTargetNC": "Captain can target Neutral Chaos", - "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", - "CaptainCanTargetNK": "Captain can target Neutral Killer", - "CaptainSpeedReduced": "Captain reduced your speed", - "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", - "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", - - "InspectorTryHideMsg": "Hide Inspector's commands", - "MaxInspectCheckLimit": "Max inspections per game", - "InspectCheckLimitPerMeeting": "Max inspections per meeting", - "InspectCheckTargetKnow": "Targets know they were checked by Inspector", - "InspectCheckOtherTargetKnow": "Targets know who they were checked with", - "InspectorDead": "You can not use your power after death", - "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", - "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", - "InspectCheckSelf": "HA!! You thought it would be this easy. You can not check yourself", - "InspectCheckReveal": "HA! You thought it would be this easy. You can not check a role that is revealed", - "InspectCheckTitle": "INSPECTOR ", - "InspectCheckTrue": "{0} and {1} are in the same team!", - "InspectCheckFalse": "{0} and {1} are NOT in the same team!", - "InspectCheckTargetMsg": " were checked by Inspector.", - "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "InspectCheckNull": "Please select an ID of a living player to check their team", - "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", - "InspectCheckRevealTarget": "When tasks are done, the target knows the team of the other target", - "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", - - "EgoistCountMode.Original": "Original", - "EgoistCountMode.Neutral": "Neutral", - - "JailerJailCooldown": "Jail cooldown", - "JailerMaxExecution": "Maximum executions", - "JailerNBCanBeExe": "Can execute Neutral Benign", - "JailerNCCanBeExe": "Can execute Neutral Chaos", - "JailerNECanBeExe": "Can execute Neutral Evil", - "JailerNKCanBeExe": "Can execute Neutral Killing", - "JailerNACanBeExe": "Can execute Neutral Apocalypse", - "JailerCKCanBeExe": "Can execute Crew Killing", - "JailerTargetAlreadySelected": "You have already selected a target", - "SuccessfullyJailed": "Target successfully jailed", - "CantGuessJailed": "You can not guess the target", - "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", - "CanNotTrialJailed": "You can not trial the target.", - "notifyJailedOnMeeting": "Notify jailed player when a meeting starts", - "JailedNotifyMsg": "The Jailer has jailed you. No one can guess or judge you. You can only guess The Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", - "JailerTitle": "Jailer", - - "CopyCatCopyCooldown": "Copy cooldown", - "CopyCatRoleChange": "Your role has been changed to {0}", - "CopyCatCanNotCopy": "You can not copy the target's role", - "CopyButtonText": "Copy", - "CopyCrewVar": "Can copy evil variants of crew roles", - "CopyTeamChangingAddon": "Can copy team changing add-on", - - "MaxCleanserUses": "Max cleanses", - "CleansedCanGetAddon": "Cleansed player can get Add-on", - "CleanserTitle": "CLEANSER", - "CleanserRemoveSelf": "You can not cleanse yourself", - "CleanserCantRemove": "Oops! the player can not be cleansed.", - "CleanserRemovedRole": "{0} has been cleansed. All their Add-ons will be removed after the meeting.", - "LostAddonByCleanser": "The cleanser removed all your Add-ons", - - "MaxProtections": "Max protections", - "KeeperHideVote": "Hide Keeper's vote", - "KeeperProtect": "You chose to protect {0}, your vote has been returned", - "KeeperTitle": "Keeper", - - "MaulRadius": "Maul Radius", - "ImpCanBeAutopsy": "Impostors can become Autopsy", - "CrewCanBeAutopsy": "Crewmates can become Autopsy", - "NeutralCanBeAutopsy": "Neutrals can become Autopsy", - "ImpCanBeCyber": "Impostors can become Cyber", - "CrewCanBeCyber": "Crewmates can become Cyber", - "NeutralCanBeCyber": "Neutrals can become Cyber", - "ImpKnowCyberDead": "Impostors know if Cyber died", - "CrewKnowCyberDead": "Crewmates know if Cyber died", - "NeutralKnowCyberDead": "Neutrals know if Cyber died", - "CyberKnown": "Everyone can see Cyber", - "ImpCanBeInfluenced": "Impostors can become Influenced", - "CrewCanBeInfluenced": "Crewmates can become Influenced", - "NeutralCanBeInfluenced": "Neutrals can become Influenced", - "ImpCanBeBewilder": "Impostors can become Bewilder", - "CrewCanBeBewilder": "Crewmates can become Bewilder", - "NeutralCanBeBewilder": "Neutrals can become Bewilder", - "KillerGetBewilderVision": "Killer gets Bewilder's vision", - "ImpCanBeOiiai": "Impostors can be OIIAI", - "CrewCanBeOiiai": "Crewmates can be OIIAI", - "NeutralCanBeOiiai": "Neutrals can be OIIAI", - "OiiaiCanPassOn": "OIIAI can pass on to the killer", - "NeutralChangeRolesForOiiai": "Neutrals turns to ", - "LostRoleByOiiai": "You got erased by OIIAI!", - "ImpCanBeLoyal": "Impostors can become Loyal", - "CrewCanBeLoyal": "Crewmates can become Loyal", - "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", - "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", - "SheriffCanBeMadmate": "Sheriff can become Madmate", - "MayorCanBeMadmate": "Mayor can become Madmate", - "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", - "SnitchCanBeMadmate": "Snitch can become Madmate", - "JudgeCanBeMadmate": "Judge can become Madmate", - "MarshallCanBeMadmate": "Marshall can become Madmate", - "GanRetributionistCanBeMadmate": "Retributionist can be converted", - "RetributionistCanBeMadmate": "Retributionist can become Madmate", - "OverseerCanBeMadmate": "Overseer can become Madmate", - "GanSheriffCanBeMadmate": "Sheriff can be converted", - "GanMayorCanBeMadmate": "Mayor can be converted", - "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", - "GanJudgeCanBeMadmate": "Judge can be converted", - "GanMarshallCanBeMadmate": "Marshall can be converted", - "GanOverseerCanBeMadmate": "Overseer can be converted", - "RascalAppearAsMadmate": "Appear As Madmate On Ejection", - - "CouncillorDead": "Sorry, you can't murder from the dead.", - "CouncillorMurderMaxMeeting": "Sorry, you've reached the maximum amount of murders for the meeting.", - "CouncillorMurderMaxGame": "Sorry, you've reached the maximum amount of murders for the game.", - "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", - "Councillor_MurderKill": "{0} was murdered.", - "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Councillor_MurderNull": "Please choose a living player to murder.", - "Councillor_MurderKillTitle": "WICKED COURT ", - "CouncillorMakeEvilJudgeClear": "Show Trial as Councillor Murder", - "Councillor_CannotMurderImpTeam": "Sorry, you can not murder your teammate.", - "Councillor_SuicideForMurderImps": "You died because you are trying to murder your team members.", - "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", - "CouncillorMurderLimitPerGame": "Maximum Kills Per Game", - "CouncillorCanMurderMadmate": "Can Murder Madmates", - "CouncillorCanMurderImpostor": "Can Murder Impostors", - "CouncillorSuicideOnJudgeImpTeam": "Suicide when judge Impostors Team Wrongly", - "CouncillorCanMurderTaskDoneSnitch": "Can Murder Snitch with All Tasks Done", - "CouncillorTryHideMsg": "Try to hide Councillor's commands", - - "DazzlerDazzled": "You were dazzled by the Dazzler!", - "DazzlerCauseVision": "Reduced vision", - "DazzlerDazzleLimit": "Max number of players affected by reduced vision", - "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", - "DazzleCooldown": "Dazzle Cooldown", - "DazzleButtonText": "Dazzle", - - "MoleVentButtonText": "Dig", - "MoleVentCooldown": "Dig cooldown", - - "AddictVentButtonText": "Get Fix", - "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", - "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerable", - - "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", - "AlchemistShieldDur": "Resistance Potion Duration", - "AlchemistInvisDur": "Invisibility Potion Duration", - "AlchemistVision": "Night Vision", - "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", - "AlchemistVisionDur": "Night Vision Potion Duration", - "AlchemistSpeed": "Speed Potion Boost", - "AlchemistVentButtonText": "Drink", - "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", - "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", - "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", - "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", - "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", - "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", - "AlchemistGotBloodthirstPotion": "Potion of Harming: Kill the next player you touch", - "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", - "NoPotion": "You have no potions", - - "StoreShield": "Potion of Resistance", - "StoreSuicide": "Potion of Poison", - "StoreTP": "Potion of Warping", - "StoreSP": "Potion Of Speed", - "StoreQF": "Potion of Fixing", - "StoreBL": "Potion of Harming", - "StoreNS": "Potion of Night Vision", - "StoreINV": "Potion of Invisibility", - "StoreNull": "None", - "PotionStore": "Potion in store: ", - "WaitQFPotion": "\nPotion of Fixing waiting for use", - - "AlchemistShielded": "Potion of Resistance started", - "AlchemistHasVision": "Potion of Night Vision started", - "AlchemistShieldOut": "Potion of Resistance ended", - "AlchemistVisionOut": "Potion of Night Vision ended", - "AlchemistPotionBloodthirst": "You gained bloodthirst", - "AlchemistHasSpeed": "Potion Of Speed started", - "AlchemistSpeedOut": "Potion Of Speed ended", - - "DeathpactDuration": "Death Pact duration", - "DeathPactCooldown": "Death Pact Assign Cooldown", - "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", - "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", - "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", - "DeathpactVisionWhileInPact": "Vision for players in Death Pact", - "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", - "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", - "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", - "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", - "DeathpactComplete": "Death Pact was concluded.", - "DeathpactExecuted": "Death Pact was executed.", - "DeathpactAverted": "Death Pact was averted.", - "DeathpactButtonText": "Assign", - "DevourerHideNameConsumed": "Hide the names of consumed players", - "DevourCooldown": "Devour Cooldown", - "DevourerButtonText": "Devour", - "DollMasterPossessionButtonText": "Possess", - "DollMasterUnPossessionButtonText": "UnPossess", - "DollMaster_PossessedTarget": "Possessed target", - "DollMaster_CannotPossessImpTeammate": "Unable to possess teammate", - "DollMaster_CouldNotSwapWithTarget": "Unable to possess player", - "DollMaster_CanNotSwapWithDeadTarget": "Possesing a dead player isn't possible", - "DollMaster_MainBody": "Main Body", - "DollMaster_Doll": "Doll", - "DollMaster_UnableToUseAbility": "Unable to use your ability on player", - "Doppelganger_RoleInfo": "Spoofed Role: {0}", - "EatenByDevourer": "The Devourer ate your skin", - "DevourerEatenSkin": "Target skin is eaten", - "DevouredName": "Devoured", - "PitfallTrapCooldown": "Trap Cooldown", - "PitfallMaxTrapCount": "Number of Traps that can be set", - "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", - "PitfallTrapDuration": "Time the Trap remains active", - "PitfallTrapRadius": "Trap Radius", - "PitfallTrapFreezeTime": "Trap freeze time", - "PitfallTrapCauseVision": "Trap caused vision", - "PitfallTrapCauseVisionTime": "Trap caused vision time", - "PitfallTrap": "You have fallen into a trap!", - "ConsigliereDivinationMaxCount": "Maximum Reveals", - "RitualMaxCount": "Maximum Reveals", - "CleanserHideVote": "Hide Cleanser's vote", - "OracleSkillLimit": "Maximum Uses", - "OracleHideVote": "Hide Oracle's vote", - "OracleCheckReachLimit": "You're out of uses!", - "OracleCheckSelfMsg": "You can't even trust yourself, huh?", - "OracleCheckLimit": "Reminder: You have {0} uses left", - "OracleCheckMsgTitle": "ORACLE ", - "OracleCheck.NotCrewmate": "Appears not to be a crewmate", - "OracleCheck.Crewmate": "Appears to be a crewmate", - "OracleCheck.Neutral": "Appears to be a neutral", - "OracleCheck.Impostor": "Appears to be an Impostor", - "OracleCheck": "Target Results:", - "FailChance": "Chance of showing incorrect result", - "OracleCheckAddons": "Oracle checks add-ons", - "ChameleonCanVent": "Vent to disguise", - "ChameleonInvisState": "You are disguising!", - "ChameleonInvisStateOut": "Your disguise ended", - "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", - "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", - "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", - "ChameleonCooldown": "Disguise Cooldown", - "ChameleonDuration": "Disguise Duration", - "ChameleonRevertDisguise": "Expose", - "ChameleonDisguise": "Disguise", - "KillCooldownAfterCleaning": "Kill Cooldown On Clean", - "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", - "MedusaStoneBody": "Body stoned", - "MedusaReportButtonText": "Stone", - - "CursedSoulCurseCooldown": "Soul Snatch Cooldown", - "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", - "CursedSoulCurseMax": "Maximum Soul Snatches", - "CursedSoulKnowTargetRole": "Know the roles of Soulless players", - "CursedSoulCanCurseNeutral": "Neutral roles have souls", - "CursedSoulKillButtonText": "Snatch", - "SoullessByCursedSoul": "A Cursed Soul snatched your soul", - "CursedSoulSoullessPlayer": "Soul snatched", - "CursedSoulInvalidTarget": "No soul found", - - "AdmireCooldown": "Admire Cooldown", - "AdmirerKnowTargetRole": "Know the roles of Admired players", - "AdmirerSkillLimit": "Skill Limit", - "AdmireButtonText": "Admire", - "AdmirerAdmired": "The Admirer admired you!", - "AdmiredPlayer": "Player admired", - "AdmirerInvalidTarget": "Target cannot be admired", - - "SpiritualistNoticeTitle": "SPIRITUALIST ", - "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", - "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", - "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", - "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", - "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", - "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", - "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", - "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", - "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", - "EnigmaClueHat1": "The Killer wears a Hat!", - "EnigmaClueHat2": "The Killer does not wear a Hat!", - "EnigmaClueHat3": "The Killer wears {0} as a Hat!", - "EnigmaClueSkin1": "The Killer wears a Skin!", - "EnigmaClueSkin2": "The Killer does not wear a Skin!", - "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", - "EnigmaClueVisor1": "The Killer wears a Visor!", - "EnigmaClueVisor2": "The Killer does not wear a Visor!", - "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", - "EnigmaCluePet1": "The Killer does have a Pet!", - "EnigmaCluePet2": "The Killer does not have a Pet!", - "EnigmaCluePet3": "The Killer has {0} as a Pet!", - "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", - "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", - "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", - "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", - "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", - "EnigmaClueColor1": "The Killer has a light color!", - "EnigmaClueColor2": "The Killer has a dark color!", - "EnigmaClueColor3": "The Killer's color is {0}!", - "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", - "EnigmaClueStatus1": "The Killer is currently inside a Vent!", - "EnigmaClueStatus2": "The Killer is currently on a Ladder!", - "EnigmaClueStatus3": "The Killer is already Dead!", - "EnigmaClueStatus4": "The Killer is still Alive!", - "EnigmaClueRole1": "The Killer is an Impostor!", - "EnigmaClueRole2": "The Killer is a Neutral!", - "EnigmaClueRole3": "The Killer is a Crewmate!", - "EnigmaClueRole4": "The Killer's Role is {0}!", - "EnigmaClueLevel1": "The Killer's Level is above 50!", - "EnigmaClueLevel2": "The Killer's Level is below 50!", - "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", - "EnigmaClueLevel4": "The Killer's Level is {0}!", - "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", - "EnigmaClueHatTitle": "Enigma Hat Clue!", - "EnigmaClueVisorTitle": "Enigma Visor Clue!", - "EnigmaClueSkinTitle": "Enigma Skin Clue!", - "EnigmaCluePetTitle": "Enigma Pet Clue!", - "EnigmaClueNameTitle": "Enigma Name Clue!", - "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", - "EnigmaClueColorTitle": "Enigma Color Clue!", - "EnigmaClueLocationTitle": "Enigma Location Clue!", - "EnigmaClueStatusTitle": "Enigma Status Clue!", - "EnigmaClueRoleTitle": "Enigma Role Clue!", - "EnigmaClueLevelTitle": "Enigma Level Clue!", - "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", - - "VotesPerKill": "Votes gained for each kill", - "PickpocketGetVote": "You've got {0} votes", - "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "VultureNumberOfReportsToWin": "Bodies needed to win", - "VultureReportBody": "Body eaten!", - "VultureEatButtonText": "Consume", - "VultureReportCooldown": "Eat Cooldown", - "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", - "VultureCooldownUp": "Eat Cooldown finished", - - "GhastlyPossessCD": "Possess Cooldown", - "GhastlyMaxPossessions": "Max Possessions", - "GhastlyPossessionDuration": "Possession Duration", - "GhastlySpeed": "Ghastly Speed", - "GhastlyKillAllies": "Ghastly cannot possess allies", - "GhastlyCannotPossessTarget": "Couldn't Possess Target", - "GhastlyChooseTarget": "Now: Choose Target", - "GhastlyNoMorePossess": "You've run out of possessions!'", - "GhastlyNotUrTarget": "That is not your target", - "GhastlyYouvePosses": "You've Been Possessed!", - "GhastlyPossessedUser": "You have possessed: {0}", - "GhastlyExpired": "{0} is no longer possessed", - - "TasksMarkPerRound": "Number of tasks that can be marked in one round", - "TaskinatorBombPlanted": "Bomb has been planted", - - "ShieldDuration": "Shield duration", - "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", - "BenefactorTaskMarked": "Task marked successfully", - "BenefactorTargetGotShield": "You got a shield by Benefactor", - - "PirateTryHideMsg": "Hide Pirate's commands", - "SuccessfulDuelsToWin": "Number of successful duels needed to win", - "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the Duel if you choose the same option as the target", - "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", - "PirateTitle": "PIRATE ", - "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", - "PirateDead": "Ye be dead. Ye cannot duel anymore.", - "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", - "DuelDone": "Ye 'ave chosen yer option fer the Duel.\nWait fer the meetin' to end to see the result.", - "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", - "PirateDuelButtonText": "Duel", - "DuelCooldown": "Duel Cooldown", - "Rock": "Rock", - "Paper": "Paper", - "Scissors": "Scissors", - "Heads": "Heads", - "Tails": "Tails", - "SpyRedNameDur": "Colored Name Duration", - "SpyInteractionBlocked": "Block kill button interaction", - "AgitaterBombCooldown": "Agitator bomb cooldown", - "AgitaterPassCooldown": "Bomb pass cooldown", - "BombExplodeCooldown": "Bomb explode cooldown", - "AgitaterPassNotify": "Bomb successfully passed", - "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", - "AgitaterCanGetBombed": "Agitator can get bomb", - "AgitaterAutoReportBait": "Agitator Auto Report Bait", - - "SeekerPointsToWin": "Number of points required to win", - "SeekerTagCooldown": "Tag Cooldown", - "SeekerNotify": "Your target is {0}", - "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", - "SeekerKillButtonText": "Tag", - - "PixiePointsToWin": "Number of points required to win", - "MaxTargets": "Maximum number of targets per round", - "MarkCooldown": "Mark cooldown", - "PixieSuicide": "Pixie suicides if the target is not voted out", - "PixieMaxTargetReached": "You have already selected all the targets this round", - "PixieTargetAlreadySelected": "Target is already selected", - "PixieButtonText": "Mark", - - "PlagueBearerCooldown": "Plague cooldown", - "PestilenceCooldown": "Pestilence Kill cooldown", - "PestilenceCanVent": "Pestilence Can Vent", - "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", - "PlagueBearerAlreadyPlagued": "Player has already been plagued", - "PlagueBearerToPestilence": "You have turned into Pestilence!!", - "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", - "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", - "RomanticBetCooldown": "Pick Partner Cooldown", - "RomanticProtectCooldown": "Protect Cooldown", - "RomanticBetPlayer": "You picked your partner", - "RomanticBetOnYou": "The Romantic chose you as their Partner!", - "VengefulKCD": "Vengeful Romantic Kill Cooldown", - "VengefulCanVent": "Vengeful Romantic Can Vent", - "RuthlessKCD": "Ruthless Romantic Kill Cooldown", - "RuthlessCanVent": "Ruthless Romantic Can Vent", - "RomanticProtectPartner": "Your partner is under protection", - "RomanticIsProtectingYou": "The Romantic is protecting you", - "ProtectingOver": "Shield expired", - "RomanticProtectDuration": "Protect Duration", - "RomanticKnowTargetRole": "Romantic knows their target's role", - "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", - "RomanticPartnerButtonText": "Pick Partner", - "RomanticProtectButtonText": "Protect", - - "GuessMasterMisguess": "{0} misguessed", - "GuessMasterTargetRole": "Someone tried to guess {0}", - "GuessMasterTitle": "Guess Master ", - - "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", - "DCanGuessImpostors": "Can Guess Impostors", - "DCanGuessCrewmates": "Can Guess Crewmates", - "DCanGuessNeutrals": "Can Guess Neutrals", - "DCanGuessAdt": "Can Guess Add-Ons", - "DoomsayerAdvancedSettings": "Advanced Settings", - "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", - "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", - "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", - "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", - "DoomsayerTryHideMsg": "Hide Doomsayer's commands", - "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", - "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", - "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", - "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", - "DoomsayerGuessCountTitle": "DOOMSAYER", - "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", - - "EveryoneCanKnowMini": "Everyone can see the Mini", - "CanBeEvil": "Mini can be an Impostor", - "EvilMiniSpawnChances": "Probability of Mini being an Impostor", - "GuessMini": "Sorry, you can't hurt a kid Mini.", - "GrowUpDuration": "Time required to grow (s)", - "MajorCooldown": "Kill Cooldown when over 18", - "UpDateAge": "Display age change in real-time", - "Cantkillkid": "You can't kill a Mini that hasn't grown up.", - "CantEat": "You can't eat a Mini that hasn't grown up", - "CantShroud": "You can't control a Mini that hasn't grown up.", - "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", - "CantRecruit": "You can't recruit a Mini that hasn't grown up.", - "CantDuel": "You can't duel a Mini that hasn't grown up.", - "CantMark": "You can't mark a Mini that hasn't grown up.", - "CantBlood": "You can't blood a Mini that hasn't grown up.", - "CantPosses": "You can't possess a Mini that hasn't grown up.", - "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", - "MiniUp": "You're a year older!", - "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilt while you can no longer guess.\nYou can guess again after you have grown up.", - "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", - "CountMeetingTime": "Meeting time can continue to grow", - "YouKillRandomizer1": "You kill Randomizer, Self-report!", - "YouKillRandomizer2": "You kill Randomizer, Cannot move!", - "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", - "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", - "MadmateCanBeHurried": "Madmate can be Hurried on game start", - "TaskBasedCrewCanBeHurried": "Task-based Crews can be Hurried", - "HurriedCanBeConverted": "Hurried can be recruited in the game (excludes madmate)", - "Developer": "Developer", - "Sponsor": "Sponsor", - "Booster": "Server Booster", - "Translator": "Translator", - "NoAccess": "Unauthorized Access!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", - "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", - "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", - "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", - "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", - "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", - "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", - "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", - "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", - "DCNotify.Inactivity": "The lobby closed due to inactivity.", - "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", - "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", - "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", - "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", - "RoleType.VanillaRoles": "★ Vanilla Roles", - "RoleType.ImpKilling": "★ Impostor Killing Roles", - "RoleType.ImpSupport": "★ Impostor Support Roles", - "RoleType.ImpConcealing": "★ Impostor Concealing Roles", - "RoleType.ImpHindering": "★ Impostor Hindering Roles", - "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", - "RoleType.Madmate": "★ Madmate Roles", - "RoleType.CrewSupport": "★ Crewmate Support Roles", - "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", - "RoleType.CrewPower": "★ Crewmate Power Roles", - "RoleType.CrewKilling": "★ Crewmate Killing Roles", - "RoleType.CrewBasic": "★ Crewmate Basic Roles", - "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", - "RoleType.NeutralEvil": "★ Neutral Evil Roles", - "RoleType.NeutralBenign": "★ Neutral Benign Roles", - "RoleType.NeutralChaos": "★ Neutral Chaos Roles", - "RoleType.NeutralKilling": "★ Neutral Killing Roles", - "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", - "RoleType.Harmful": "★ Harmful Add-ons", - "RoleType.Support": "★ Supportive Add-ons", - "RoleType.Helpful": "★ Helpful Add-ons", - "RoleType.Mixed": "★ Mixed Add-ons", - "RoleType.Misc": "★ Miscellaneous Add-ons", - "RoleType.Impostor": "★ Impostor Add-ons", - "RoleType.Neut": "★ Neutral Add-ons", - "SubType.Impostor": "★ Impostors", - "SubType.Shapeshifter": "★ Shapeshifters", - "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", - "SubType.Madmate": "★ Madmates", - "SubType.CrewmateKilling": "★ Crewmate Killings", - "SubType.Crewmate": "★ Regular Crewmates", - "SubType.New": "★ New!", - "CrewmateRoles": "★ Crewmate Roles ★", - "ImpostorRoles": "★ Impostor Roles ★", - "NeutralRoles": "★ Neutral Roles ★", - "AddonRoles": "★ Add-ons ★", - "WinnerRoleText.Impostor": "Impostors Win!", - "WinnerRoleText.Crewmate": "Crewmates Win!", - "WinnerRoleText.Apocalypse": "Apocalypse Wins!", - "WinnerRoleText.Terrorist": "Terrorist Wins!", - "WinnerRoleText.Jester": "Jester Wins!", - "WinnerRoleText.Lovers": "Lovers Win!", - "WinnerRoleText.Executioner": "Executioner Wins!", - "WinnerRoleText.Arsonist": "Arsonist Wins!", - "WinnerRoleText.Revolutionist": "Revolutionist Wins!", - "WinnerRoleText.Jackal": "Jackals Win!", - "WinnerRoleText.God": "God Wins!", - "WinnerRoleText.Vector": "Vector Wins!", - "WinnerRoleText.Innocent": "Innocent Wins!", - "WinnerRoleText.Pelican": "Pelican Wins!", - "WinnerRoleText.Youtuber": "YouTuber Wins!", - "WinnerRoleText.Necromancer": "Necromancer Wins!", - "WinnerRoleText.Egoist": "Egoists Win!", - "WinnerRoleText.Demon": "Demon Wins!", - "WinnerRoleText.Stalker": "Stalker Wins!", - "WinnerRoleText.Workaholic": "Workaholic Wins!", - "WinnerRoleText.Collector": "Collector Wins!", - "WinnerRoleText.BloodKnight": "Blood Knight Wins!", - "WinnerRoleText.Poisoner": "Poisoner Wins!", - "WinnerRoleText.Huntsman": "Huntsman Wins!", - "WinnerRoleText.HexMaster": "Hex Master Wins!", - "WinnerRoleText.Cultist": "Cultist Wins!", - "WinnerRoleText.Wraith": "Wraith Wins!", - "WinnerRoleText.SerialKiller": "Serial Killers Win!", - "WinnerRoleText.Juggernaut": "Juggernaut Wins!", - "WinnerRoleText.Infectious": "Infectious Wins!", - "WinnerRoleText.Virus": "Virus Wins!", - "WinnerRoleText.Specter": "Specter Wins!", - "WinnerRoleText.Jinx": "Jinx Wins!", - "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", - "WinnerRoleText.PotionMaster": "Potion Master Wins!", - "WinnerRoleText.Pickpocket": "Pickpocket Wins!", - "WinnerRoleText.Traitor": "Traitor Wins!", - "WinnerRoleText.Vulture": "Vulture Wins!", - "WinnerRoleText.Medusa": "Medusa Wins!", - "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", - "WinnerRoleText.Glitch": "Glitch Wins!", - "WinnerRoleText.Pestilence": "Pestilence Wins!", - "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", - "WinnerRoleText.PunchingBag": "Punching Bag Wins!", - "WinnerRoleText.Doomsayer": "Doomsayer Wins!", - "WinnerRoleText.Pirate": "Pirate Wins!", - "WinnerRoleText.Shroud": "Shroud Wins!", - "WinnerRoleText.Werewolf": "Werewolf Wins!", - "WinnerRoleText.Seeker": "Seeker Wins!", - "WinnerRoleText.Occultist": "Occultist Wins!", - "WinnerRoleText.SoulCollector": "Soul Collector Wins!", - "WinnerRoleText.NiceMini": "Nice Mini Wins!", - "WinnerRoleText.Mini": "Nice Mini was killed", - "WinnerRoleText.Bandit": "Bandit Wins!", - "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", - "WinnerRoleText.Solsticer": "Solsticer Wins!", - "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", - "WinnerRoleText.Doppelganger": "Doppelganger Wins!", - "WinnerRoleText.Quizmaster": "Quizmaster Wins!", - "WinnerRoleText.Agitater": "Agitator Wins!", - "AdditionalWinnerRoleText.Sidekick": "Sidekick", - "AdditionalWinnerRoleText.Taskinator": "Taskinator", - "AdditionalWinnerRoleText.Opportunist": "Opportunist", - "AdditionalWinnerRoleText.Lawyer": "Lawyer", - "AdditionalWinnerRoleText.Hater": "Hater", - "AdditionalWinnerRoleText.Provocateur": "Provocateur", - "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", - "AdditionalWinnerRoleText.Follower": "Follower", - "AdditionalWinnerRoleText.Pursuer": "Pursuer", - "AdditionalWinnerRoleText.Jester": "Jester", - "AdditionalWinnerRoleText.Lovers": "Lovers", - "AdditionalWinnerRoleText.Executioner": "Executioner", - "AdditionalWinnerRoleText.Specter": "Specter", - "AdditionalWinnerRoleText.Maverick": "Maverick", - "AdditionalWinnerRoleText.Shaman": "Shaman", - "AdditionalWinnerRoleText.Pixie": "Pixie", - "AdditionalWinnerRoleText.NiceMini": "Nice Mini", - "AdditionalWinnerRoleText.Romantic": "Romantic", - "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", - "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", - "ErrorEndText": "An error occurred", - "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", - "ForceEnd": "Aborted", - "EveryoneDied": "Everyone died", - "ForceEndText": "Host has aborted the game", - "NiceMiniDied": "Nice Mini was killed", - "HaterMisFireKillTarget": "Hater kills target when misfiring", - "HaterChooseConverted": "Select add-ons that Hater can kill", - "HaterCanKillMadmate": "Can kill madmate", - "HaterCanKillCharmed": "Can kill charmed", - "HaterCanKillLovers": "Can kill lovers", - "HaterCanKillSidekick": "Can kill jackal team", - "HaterCanKillEgoist": "Can kill egoist", - "HaterCanKillInfected": "Can kill infected team", - "HaterCanKillContagious": "Can kill virus team", - "HaterCanKillAdmired": "Can kill admirer", - "HorseMode": "Enable to become a horse", - "LongMode": "Enable to have a long neck", - "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", - - - "FFA": "Free For All", - "ModeFFA": "Gamemode: FFA", - "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", - "KillerInfoLong": "In the FFA (Free For All) game mode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", - "FFA_GameTime": "Maximum Game Length", - "FFA_KCD": "Kill Cooldown", - "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", - "FFA_EnableRandomAbilities": "Enable Random Events", - "FFA_ShieldDuration": "Shield Duration", - "FFA_IncreasedSpeed": "Increased Speed", - "FFA_DecreasedSpeed": "Decreased Speed", - "FFA_ModifiedSpeedDuration": "Modified Speed Duration", - "FFA_LowerVision": "Lowered Vision", - "FFA_ModifiedVisionDuration": "Lowered Vision Duration", - "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", - "FFA-Event-GetShield": "You have a temporary shield!", - "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", - "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", - "FFA-Event-GetHighKCD": "You got a higher kill cooldown", - "FFA-Event-GetLowVision": "You have lower vision temporarily", - "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", - "FFA-Event-GetTP": "You got teleported to a random vent!", - "FFA-Event-RandomTP": "Everyone was swapped with someone", - "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", - "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", - "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", - "FFA_TargetIsShielded": "The player you tried to kill is shielded!", - "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", - "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", - "Killer": "FREE FOR ALL", - "KillerInfo": "Kill Everyone to Win", - - "Hide&SeekTOHE": "Hide & Seek", - "MenuTitle.Hide&Seek": "Hide & Seek Settings", - "NumImpostorsHnS": "Num Impostors", - - "EveryOneKnowSolsticer": "Every One Know who is Solsticer", - "SolsticerKnowItsKiller": "Solsticer knows the role of whom used the kill button on it", - "SolsticerSpeed": "Movement speed of Solsticer", - "SolsticerRemainingTaskWarned": "Remaining tasks to be known", - "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", - "SolsticerMurdered": "{0} attempted to murder you!", - "MurderSolsticer": "You stopped Solsticer this round!", - "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", - "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", - "SolsticerTitle": "Solsticer", - "GuessSolsticer": "Sorry, but you can not guess Solsticer!", - "VoteSolsticer": "Sorry, but you can not vote Solsticer!", - "SolsticerTasksReset": "Your tasks get reset!", - "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", - "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", - - "VoteDead": "The player you voted for was exiled before the meeting concluded. Your vote was rescinded.", - - "ImpCanBeSilent": "Impostors can become Silent", - "CrewCanBeSilent": "Crewmates can become Silent", - "NeutralCanBeSilent": "Neutrals can become Silent", - "LastMessageReplay": "Last System Message Replay", - "Contributor": "Contributor", - - "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", - "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", - - "ImpCanBeSusceptible": "Impostors can become Susceptible", - "CrewCanBeSusceptible": "Crewmates can become Susceptible", - "NeutralCanBeSusceptible": "Neutrals can become Susceptible", - - "Quizmaster": "Quizmaster", - "QuizmasterInfo": "Quiz people to kill them in meetings", - "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will have \"?!\" next to their name. The player will die if they answer the question wrong or doesn't answer. The player will live if the Quizmaster is killed/ejected in the same meeting.\nThe Quizmaster cannot mark multiple people in the same round", - "QuizmasterKillButtonText": "Quiz", - - "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", - "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", - "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", - "QuizmasterChat.CorrectTarget": "Correct", - "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", - "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", - "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", - "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", - "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", - "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die\n\nQuestion for {QMTARGET} => {QMQUESTION}", - "QuizmasterChat.Title": "Quizmaster Information", - "QuizmasterChat.CantAnswer": "As the quizmaster, you can't answer questions", - "QuizmasterChat.AnswerNotValid": "Your answer must be A, B, or C", - "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", - - "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", - "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", - "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", - "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", - "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", - - "Quizmaster.None": "None", - - "QuizmasterSabotages.Lights": "Lights", - "QuizmasterSabotages.Reactor": "Reactor", - "QuizmasterSabotages.Communications": "Communications", - "QuizmasterSabotages.O2": "O2", - "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", - "QuizmasterAnswers.One": "One", - "QuizmasterAnswers.Two": "Two", - "QuizmasterAnswers.Three": "Three", - "QuizmasterAnswers.Four": "Four", - "QuizmasterAnswers.Five": "Five", - "QuizmasterAnswers.Pacifist": "Pacifist", - "QuizmasterAnswers.Vampire": "Vampire", - "QuizmasterAnswers.Snitch": "Snitch", - "QuizmasterAnswers.Vigilante": "Vigilante", - "QuizmasterAnswers.Jackal": "Jackal", - "QuizmasterAnswers.Mole": "Mole", - "QuizmasterAnswers.Sniper": "Sniper", - "QuizmasterAnswers.Coven": "Coven", - "QuizmasterAnswers.Sabotuer": "Saboteur", - "QuizmasterAnswers.Sorcerers": "Sorcerers", - "QuizmasterAnswers.Killer": "Killer", - "QuizmasterAnswers.Edition": "Edition", - "QuizmasterAnswers.Experimental": "Experimental", - "QuizmasterAnswers.Enhanced": "Enhanced", - "QuizmasterAnswers.Edited": "Edited", - - "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", - "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", - "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", - "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", - "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called the last meeting before this meeting?", - "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", - "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", - "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", - "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", - "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed in an update later?", - "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", - "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", - "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", - "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", - "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", - "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", - "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", - - "DeathReason.WrongAnswer": "Wrong Quiz Answer", - - "TPCooldown": "Teleport Cooldown", - "RiftsTooClose": "Location too close to the first rift", - "RiftCreated": "Rift made successfully", - "RiftsDestroyed": "All rifts Destroyed", - "RiftRadius": "Rift Radius", - - "TiredVision": "Vision When Tired", - "TiredSpeed": "Speed When Tired", - "TiredDur": "Tired Duration", - "ImpCanBeTired": "Impostors can become Tired", - "CrewCanBeTired": "Crewmates can become Tired", - "NeutralCanBeTired": "Neutrals can become Tired", - - "TiredNotify": "Zzz..", - - "PlagueDoctorInfectLimit": "Infect Limit", - "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", - "PlagueDoctorInfectTime": "Infect Time", - "PlagueDoctorInfectDistance": "Infect Distance", - "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", - "PlagueDoctorCanInfectSelf": "Can Infect Self", - "PlagueDoctorCanInfectVent": "Can Infect While In Vent", - "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", - - "StatueSlow": "Statue Slowness", - "StatuePeopleToSlow": "People Needed To Slow", - - "ImpCanBeStatue": "Impostors can become Statue", - "CrewCanBeStatue": "Crewmates can become Statue", - "NeutralCanBeStatue": "Neutrals can become Statue", - - "WardenIncreaseSpeed": "Increase Speed By", - "WardenWarn": "DANGER! RUN!", - - "MinionAbilityTime": "Ability Duration", - "Minion_Blind": "blinded" + "LanguageID": "0", + "HostText": "Host", + "HostColor": "#902efd", + "IconColor": "#4bf4ff", + "Icon": "♥", + "NameColor": "#ffc0cb", + + "HideHostText": "Hide 'Host♥' Text", + "HideAllTagsAndText": "Hide All Tags (for «AutoMuteUs»)", + + "SupportUs": "Support Us", + "update": "Update", + "GitHub": "GitHub", + "Discord": "Discord", + "Website": "Website", + "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", + + "HostIconInMeeting": "HOST: {0}", + + "SubText.Crewmate": "Find and exile the Impostors", + "SubText.Impostor": "Sabotage and kill everyone", + "SubText.Neutral": "Work alone to achieve your victory", + "SubText.Apocalypse": "Become unstoppable with your team", + "SubText.Madmate": "Help the Impostors", + + "TypeImpostor": "Impostors", + "TypeCrewmate": "Crewmates", + "TypeNeutral": "Neutrals", + "TypeAddon": "Add-ons", + "GuesserMode": "Guesser Mode", + + "TeamImpostor": "Impostor", + "TeamNeutral": "Neutral", + "TeamCrewmate": "Crewmate", + "TeamMadmate": "Madmate", + + "YouAreCrewmate": "You are a Crewmate", + "YouAreImpostor": "You are an Impostor", + "YouAreNeutral": "You are a Neutral", + "YouAreMadmate": "You are a Madmate", + + + "Role_Crewmate": "Crewmate", + "Role_Jester": "Jester", + "Role_Opportunist": "Opportunist", + "Role_Celebrity": "Celebrity", + "Role_Bodyguard": "Bodyguard", + "Role_Dictator": "Dictator", + "Role_Mayor": "Mayor", + "Role_Doctor": "Doctor", + "Role_Maverick": "Maverick", + "Role_Pursuer": "Pursuer", + "Role_Follower": "Follower", + "Role_Amnesiac": "Amnesiac", + "Role_Imitator": "Imitator", + "Role_Sheriff": "Sheriff", + "Role_Knight": "Knight", + "Role_Deputy": "Deputy", + "Role_NoChange": "Don't change the role", + + "CrewmatesCanGuess": "Crewmates can guess", + "ImpostorsCanGuess": "Impostors can guess", + "NeutralKillersCanGuess": "Neutral Killers can guess", + "NeutralApocalypseCanGuess": "Neutral Apocalypse can guess", + "PassiveNeutralsCanGuess": "Passive Neutrals can guess", + + "CanGuessAddons": "Can Guess Add-ons", + "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", + "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", + "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", + "GuessImmune": "Sorry, but target is immune to being guessed!", + + + "GM": "Game Master", + "Sunnyboy": "Sunnyboy", + "Bard": "Bard", + "Crewmate": "Crewmate", + "CrewmateTOHE": "Crewmate", + "Engineer": "Engineer", + "EngineerTOHE": "Engineer", + "Scientist": "Scientist", + "ScientistTOHE": "Scientist", + "Noisemaker": "Noisemaker", + "NoisemakerTOHE": "Noisemaker", + "Tracker": "Tracker", + "TrackerTOHE": "Tracker", + "GuardianAngel": "Guardian Angel", + "GuardianAngelTOHE": "Guardian Angel", + "Impostor": "Impostor", + "ImpostorTOHE": "Impostor", + "Shapeshifter": "Shapeshifter", + "ShapeshifterTOHE": "Shapeshifter", + "Phantom": "Phantom", + "PhantomTOHE": "Phantom", + + "BountyHunter": "Bounty Hunter", + "Fireworker": "Fireworker", + "Mercenary": "Mercenary", + "ShapeMaster": "Shapemaster", + "Vampire": "Vampire", + "Warlock": "Warlock", + "Ninja": "Ninja", + "Zombie": "Zombie", + "Anonymous": "Anonymous", + "Miner": "Miner", + "KillingMachine": "Killing Machine", + "Escapist": "Escapist", + "Witch": "Witch", + "Nemesis": "Nemesis", + "Bloodmoon": "Bloodmoon", + "Puppeteer": "Puppeteer", + "Mastermind": "Mastermind", + "TimeThief": "Time Thief", + "Sniper": "Sniper", + "Undertaker": "Undertaker", + "RiftMaker": "Rift Maker", + "EvilTracker": "Evil Tracker", + "EvilHacker": "Evil Hacker", + "EvilGuesser": "Evil Guesser", + "AntiAdminer": "Anti Adminer", + "Arrogance": "Arrogance", + "Bomber": "Bomber", + "Scavenger": "Scavenger", + "Trapster": "Trapster", + "Gangster": "Gangster", + "Cleaner": "Cleaner", + "Lightning": "Lightning", + "Greedy": "Greedy", + "CursedWolf": "Cursed Wolf", + "SoulCatcher": "Soul Catcher", + "QuickShooter": "Quick Shooter", + "Camouflager": "Camouflager", + "Eraser": "Eraser", + "Butcher": "Butcher", + "Hangman": "Hangman", + "Swooper": "Swooper", + "Crewpostor": "Crewpostor", + "Wildling": "Wildling", + "Trickster": "Trickster", + "Vindicator": "Vindicator", + "Parasite": "Parasite", + "Disperser": "Disperser", + "Inhibitor": "Inhibitor", + "Saboteur": "Saboteur", + "Councillor": "Councillor", + "Dazzler": "Dazzler", + "Deathpact": "Deathpact", + "Devourer": "Devourer", + "Consigliere": "Consigliere", + "Morphling": "Morphling", + "Twister": "Twister", + "Lurker": "Lurker", + "Visionary": "Visionary", + "Refugee": "Refugee", + "Underdog": "Underdog", + "Ludopath": "Ludopath", + "Godfather": "Godfather", + "Chronomancer": "Chronomancer", + "Pitfall": "Pitfall", + "EvilMini": "Evil Mini", + "Blackmailer": "Blackmailer", + "Instigator": "Instigator", + "LazyGuy": "Lazy Guy", + "SuperStar": "Super Star", + "Celebrity": "Celebrity", + "Cleanser": "Cleanser", + "Keeper": "Keeper", + "Knight": "Knight", + "Mayor": "Mayor", + "Psychic": "Psychic", + "Mechanic": "Mechanic", + "Sheriff": "Sheriff", + "Vigilante": "Vigilante", + "Jailer": "Jailer", + "CopyCat": "Copycat", + "Snitch": "Snitch", + "Marshall": "Marshall", + "Doctor": "Doctor", + "Dictator": "Dictator", + "Detective": "Detective", + "NiceGuesser": "Nice Guesser", + "GuessMaster": "Guess Master", + "Transporter": "Transporter", + "TimeManager": "Time Manager", + "Veteran": "Veteran", + "Bastion": "Bastion", + "Bodyguard": "Bodyguard", + "Deceiver": "Deceiver", + "Grenadier": "Grenadier", + "Medic": "Medic", + "FortuneTeller": "Fortune Teller", + "Judge": "Judge", + "Mortician": "Mortician", + "Medium": "Medium", + "Pacifist": "Pacifist", + "Observer": "Observer", + "Monarch": "Monarch", + "Overseer": "Overseer", + "Coroner": "Coroner", + "Merchant": "Merchant", + "President": "President", + "Hawk": "Hawk", + "Retributionist": "Retributionist", + "Deputy": "Deputy", + "Investigator": "Investigator", + "Guardian": "Guardian", + "Addict": "Addict", + "Mole": "Mole", + "Alchemist": "Alchemist", + "Tracefinder": "Tracefinder", + "Oracle": "Oracle", + "Spiritualist": "Spiritualist", + "Chameleon": "Chameleon", + "Inspector": "Inspector", + "Captain": "Captain", + "Admirer": "Admirer", + "TimeMaster": "Time Master", + "Crusader": "Crusader", + "Reverie": "Reverie", + "Lookout": "Lookout", + "Telecommunication": "Telecommunication", + "Lighter": "Lighter", + "TaskManager": "Task Manager", + "Witness": "Witness", + "Swapper": "Swapper", + "NiceMini": "Nice Mini", + "Mini": "Mini", + "Spy": "Spy", + "Randomizer": "Randomizer", + "Enigma": "Enigma", + "Jester": "Jester", + "Arsonist": "Arsonist", + "Pyromaniac": "Pyromaniac", + "Kamikaze": "Kamikaze", + "Huntsman": "Huntsman", + "Terrorist": "Terrorist", + "Executioner": "Executioner", + "Lawyer": "Lawyer", + "Opportunist": "Opportunist", + "Vector": "Vector", + "Jackal": "Jackal", + "God": "God", + "Innocent": "Innocent", + "Stealth": "Stealth", + "Penguin": "Penguin", + "Pelican": "Pelican", + "PlagueDoctor": "Plague Scientist", + "Revolutionist": "Revolutionist", + "Hater": "Hater", + "Demon": "Demon", + "Stalker": "Stalker", + "Workaholic": "Workaholic", + "Solsticer": "Solsticer", + "Collector": "Collector", + "Provocateur": "Provocateur", + "BloodKnight": "Blood Knight", + "Apocalypse": "Apocalypse", + "PlagueBearer": "Plaguebearer", + "Pestilence": "Pestilence", + "SoulCollector": "Soul Collector", + "Death": "Death", + "Baker": "Baker", + "Famine": "Famine", + "Berserker": "Berserker", + "War": "War", + "Glitch": "Glitch", + "Sidekick": "Sidekick", + "Follower": "Follower", + "Cultist": "Cultist", + "SerialKiller": "Serial Killer", + "Juggernaut": "Juggernaut", + "Infectious": "Infectious", + "Virus": "Virus", + "Pursuer": "Pursuer", + "Specter": "Specter", + "Pirate": "Pirate", + "Agitater": "Agitator", + "Maverick": "Maverick", + "CursedSoul": "Cursed Soul", + "Pickpocket": "Pickpocket", + "Traitor": "Traitor", + "Vulture": "Vulture", + "Taskinator": "Taskinator", + "Benefactor": "Benefactor", + "Medusa": "Medusa", + "Spiritcaller": "Spiritcaller", + "Amnesiac": "Amnesiac", + "Imitator": "Imitator", + "Bandit": "Bandit", + "Doppelganger": "Doppelganger", + "PunchingBag": "Punching Bag", + "Doomsayer": "Doomsayer", + "Shroud": "Shroud", + "Werewolf": "Werewolf", + "Shaman": "Shaman", + "Seeker": "Seeker", + "Pixie": "Pixie", + "Occultist": "Occultist", + "SchrodingersCat": "Schrodingers Cat", + "Romantic": "Romantic", + "VengefulRomantic": "Vengeful Romantic", + "RuthlessRomantic": "Ruthless Romantic", + "Poisoner": "Poisoner", + "HexMaster": "Hex Master", + "Wraith": "Wraith", + "Jinx": "Jinx", + "PotionMaster": "Potion Master", + "Necromancer": "Necromancer", + "Warden": "Warden", + "Minion": "Minion", + "Ghastly": "Ghastly", + "LastImpostor": "Last Impostor", + "Overclocked": "Overclocked", + "Lovers": "Lovers", + "Madmate": "Madmate", + "Watcher": "Watcher", + "Flash": "Flash", + "Torch": "Torch", + "Seer": "Seer", + "Tiebreaker": "Tiebreaker", + "Oblivious": "Oblivious", + "Bewilder": "Bewilder", + "Workhorse": "Workhorse", + "Fool": "Fool", + "Avanger": "Avenger", + "Youtuber": "YouTuber", + "Egoist": "Egoist", + "TicketsStealer": "Stealer", + "Paranoia": "Paranoia", + "Mimic": "Mimic", + "Guesser": "Guesser", + "Necroview": "Necroview", + "Reach": "Reach", + "Charmed": "Charmed", + "Cleansed": "Cleansed", + "Bait": "Bait", + "Trapper": "Beartrap", + "Infected": "Infected", + "Onbound": "Onbound", + "Rebound": "Rebound", + "Mundane": "Mundane", + "Knighted": "Knighted", + "Unreportable": "Disregarded", + "Contagious": "Contagious", + "Lucky": "Lucky", + "Unlucky": "Unlucky", + "VoidBallot": "Void Ballot", + "Aware": "Aware", + "Fragile": "Fragile", + "DoubleShot": "Double Shot", + "Rascal": "Rascal", + "Soulless": "Soulless", + "Gravestone": "Gravestone", + "Lazy": "Lazy", + "Autopsy": "Autopsy", + "Loyal": "Loyal", + "EvilSpirit": "Evil Spirit", + "Recruit": "Recruit", + "Admired": "Admired", + "Glow": "Glow", + "Radar": "Radar", + "Diseased": "Diseased", + "Antidote": "Antidote", + "Stubborn": "Stubborn", + "Swift": "Swift", + "Ghoul": "Ghoul", + "Bloodthirst": "Bloodthirst", + "Mare": "Mare", + "Burst": "Burst", + "Sleuth": "Sleuth", + "Clumsy": "Clumsy", + "Nimble": "Nimble", + "Circumvent": "Circumvent", + "Cyber": "Cyber", + "Hurried": "Hurried", + "Oiiai": "OIIAI", + "Influenced": "Influenced", + "Silent": "Silent", + "Susceptible": "Susceptible", + "Tricky": "Tricky", + "Rainbow": "Rainbow", + "Tired": "Tired", + "Statue": "Statue", + "DollMaster": "Dollmaster", + "BracketAddons": "Add Brackets To Add-ons", + "EngineerTOHEInfo": "Use the vents to catch the Impostors", + "ScientistTOHEInfo": "Access portable vitals from anywhere", + "NoisemakerTOHEInfo": "Send out an alert when killed", + "TrackerTOHEInfo": "Track a players with your map", + "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", + "PhantomTOHEInfo": "Turn invisible", + "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", + "ImpostorTOHEInfo": "Kill and sabotage", + "CrewmateTOHEInfo": "Search for the Impostors", + "BountyHunterInfo": "Eliminate your target", + "FireworkerInfo": "Go out with a BANG", + "MercenaryInfo": "Keep killing, else you suicide", + "ShapeMasterInfo": "Swiftly kill with no shift cooldown", + "VampireInfo": "Your kills are delayed", + "WarlockInfo": "Curse crewmates then shift to make them kill", + "NinjaInfo": "Mark a target, then shift to kill", + "ZombieInfo": "You are very slow", + "AnonymousInfo": "Force a player to report a body", + "MinerInfo": "Warp to your last used vent by shifting", + "KillingMachineInfo": "You can ONLY kill, but low cooldown", + "EscapistInfo": "Shift to mark places and warp back to them", + "WitchInfo": "Spell crewmates to kill them in meetings", + "NemesisInfo": "Kill when you're the last Impostor", + "BeforeNemesisInfo": "You can't kill yet", + "AfterNemesisInfo": "Now start killing", + "BloodmoonInfo": "Seek havoc upon the crewmates", + "PuppeteerInfo": "Make players kill for you", + "MastermindInfo": "Make others kill for you", + "TimeThiefInfo": "Lower meeting time by killing", + "SniperInfo": "Snipe players from a distance by shifting", + "UndertakerInfo": "Teleport dead body to a marked location", + "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", + "EvilTrackerInfo": "Track players by shifting", + "EvilHackerInfo": "Hack systems", + "AntiAdminerInfo": "Know when players are near devices", + "ArroganceInfo": "With each kill you make, your cooldown decreases", + "BomberInfo": "Shapeshift to explode", + "TrapsterInfo": "Trap your kills", + "ScavengerInfo": "Your kills are unreportable", + "EvilGuesserInfo": "Guess crew roles in meetings to kill", + "GangsterInfo": "Convert players to your side", + "CleanerInfo": "Report bodies to make them unreportable", + "LightningInfo": "Convert players to Quantum Ghosts", + "GreedyInfo": "Your kill cooldown shifts", + "CursedWolfInfo": "You survive a few kill attempts", + "SoulCatcherInfo": "You swap places with your shift target", + "QuickShooterInfo": "Store ammo to offset kill cooldown", + "CamouflagerInfo": "Camouflage everyone for easy kills", + "EraserInfo": "Erase the role of your vote target", + "ButcherInfo": "Enjoy my beautiful work", + "HangmanInfo": "I will decide when your life will end", + "SwooperInfo": "Turn invisible temporarily", + "CrewpostorInfo": "Kill by completing tasks", + "WildlingInfo": "Kill with strength and disguise", + "TricksterInfo": "Kill and trick the crew", + "VindicatorInfo": "Use your extra votes to kill everyone", + "ParasiteInfo": "Help the Impostors kill the crew", + "DisperserInfo": "Teleport everyone to random vents", + "InhibitorInfo": "You cannot kill during sabotages", + "SaboteurInfo": "You can only kill during sabotages", + "CouncillorInfo": "Kill off crewmates during meetings", + "DazzlerInfo": "Reduce the vision of the crew", + "DeathpactInfo": "Assign players to a death pact", + "DevourerInfo": "Consume the skin of the crew", + "ConsigliereInfo": "Discover the roles of other players", + "MorphlingInfo": "You can only kill while shapeshifted", + "TwisterInfo": "Swap all player positions", + "LurkerInfo": "Reduce your kill cooldown by venting", + "ConvictInfo": "Your target died, now help the Impostors", + "VisionaryInfo": "You see the alignments of the living", + "RefugeeInfo": "Help the Impostors kill off the crew", + "UnderdogInfo": "Start killing on a low player count", + "LudopathInfo": "Your kill cooldown is random", + "GodfatherInfo": "Convert players to Refugees by voting", + "ChronomancerInfo": "Kill in bursts", + "PitfallInfo": "Setup traps around the map", + "EvilMiniInfo": "No one can hurt you until you grow up", + "BlackmailerInfo": "Silence other players", + "InstigatorInfo": "Sow discord among the crewmates", + "LazyGuyInfo": "You're too lazy", + "SuperStarInfo": "Everyone knows you", + "CleanserInfo": "Erase All Add-ons of your vote target", + "KeeperInfo": "Reject the Eject, Keeper Protect!", + "MayorInfo": "Your vote counts multiple times", + "PsychicInfo": "One of the red names are evil", + "MechanicInfo": "Vent around and fix sabotages", + "SheriffInfo": "Shoot the Impostors", + "VigilanteInfo": "Not the hero we deserved but the hero we needed", + "JailerInfo": "Jail suspicious players", + "CopyCatInfo": "Use kill button to copy target's role", + "SnitchInfo": "Finish your tasks to find the Impostors", + "MarshallInfo": "Finish your tasks to prove your innocence", + "DoctorInfo": "Know how each player died", + "DictatorInfo": "Exile a player based on your own judgment", + "DetectiveInfo": "Gain extra info from your body reports", + "UndercoverInfo": "Impostors see you as their partner", + "KnightInfo": "You can kill 1 player", + "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", + "GuessMasterInfo": "Whispers heard, every guessed word.", + "TransporterInfo": "Do tasks to swap two players' locations", + "TimeManagerInfo": "Increase meeting time by doing tasks", + "VeteranInfo": "Alert to kill anyone who interacts with you", + "BastionInfo": "Bomb vents", + "BodyguardInfo": "Prevent nearby kills", + "DeceiverInfo": "Try to fool the players", + "GrenadierInfo": "Reduce Impostors' vision by venting", + "MedicInfo": "Cast a shield onto a player", + "FortuneTellerInfo": "Get clues to people's roles", + "JudgeInfo": "Silence in the courtroom!", + "MorticianInfo": "Locate dead bodies", + "MediumInfo": "Talk with ghosts", + "ObserverInfo": "You can see all shield-animations", + "PacifistInfo": "Vent to reset kill cooldowns", + "MonarchInfo": "Give your crew extra voting power!", + "StealthInfo": "Killing Blinds Everyone in the Room", + "PenguinInfo": "Drag your victims", + "OverseerInfo": "Reveal roles of other players", + "CoronerInfo": "Find corpses and their killers", + "PresidentInfo": "You are in charge of the meeting", + "MerchantInfo": "Sell add-ons and bribe killers", + "RetributionistInfo": "Help the crew after you die", + "HawkInfo": "Seek murdering the bad guys!", + "DeputyInfo": "Handcuff killers to increase their cooldowns", + "InvestigatorInfo": "Find potential evils", + "GuardianInfo": "Complete your tasks to become immortal", + "AddictInfo": "Vent to become invulnerable, or you'll die", + "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", + "AlchemistInfo": "Brew potions by completing tasks", + "TracefinderInfo": "Sense the location of dead bodies", + "OracleInfo": "Vote a player to see their alignment", + "SpiritualistInfo": "Be guided by the ghostly life", + "ChameleonInfo": "Vent to disguise into your surroundings", + "InspectorInfo": "Validate the alignments of two players", + "CaptainInfo": "Sail with the Captain, lest add-ons be abandoned.", + "AdmirerInfo": "Choose a player to side with you", + "TimeMasterInfo": "Rewind time!", + "CrusaderInfo": "Kill a player's attacker", + "ReverieInfo": "With each kill, your cooldown decreases", + "LookoutInfo": "See through disguises", + "TelecommunicationInfo": "Track device usage", + "LighterInfo": "Catch killers with your enhanced vision", + "TaskManagerInfo": "See the total tasks completed in real-time", + "WitnessInfo": "Find out if someone killed recently", + "GhastlyInfo": "Control somebody!", + "SwapperInfo": "Swap the votes of two players", + "NiceMiniInfo": "No one can hurt you until you grow up.", + "ArsonistInfo": "Douse everyone and ignite", + "PyromaniacInfo": "Douse and kill everyone", + "HuntsmanInfo": "Kill your targets for a low cooldown", + "SpyInfo": "You know who interacts with you", + "RandomizerInfo": "You're going to be someone's burden when you die?", + "EnigmaInfo": "Get Clues about Killers", + "JesterInfo": "Get voted out", + "OpportunistInfo": "Stay alive until the end", + "TerroristInfo": "Finish your tasks, THEN die", + "ExecutionerInfo": "Get your target voted out", + "LawyerInfo": "Help your target win!", + "VectorInfo": "Jump in! Jump out!", + "JackalInfo": "Murder everyone", + "GodInfo": "Everything is under your control", + "InnocentInfo": "Get someone ejected by making them kill you", + "PelicanInfo": "Eat all players", + "RevolutionistInfo": "Recruit players to win with you", + "HaterInfo": "Kill Lovers and Neptunes", + "DemonInfo": "Consume blood volumes", + "StalkerInfo": "Descend into the darkness, release fear!", + "WorkaholicInfo": "Finish all tasks to win solo!", + "SolsticerInfo": "Speed run all your tasks!", + "CollectorInfo": "Collect votes from players", + "ProvocateurInfo": "Victory with help target", + "BloodKnightInfo": "Killing gives you a temporary shield", + "PlagueBearerInfo": "Plague everyone to turn into Pestilence", + "PestilenceInfo": "Obliterate everyone!", + "SoulCollectorInfo": "Predict deaths to collect souls", + "DeathInfo": "Enact Armageddon", + "BakerInfo": "Feed Players Bread to become Famine", + "FamineInfo": "Starve Everyone", + "BerserkerInfo": "Kill to increase your level", + "WarInfo": "Destroy everything", + "GlitchInfo": "Hack and kill everyone", + "SidekickInfo": "Help the Jackal kill everyone", + "FollowerInfo": "Follow a player and help them", + "CultistInfo": "Charm everyone", + "SerialKillerInfo": "Kill off everyone to win!", + "JuggernautInfo": "With each kill, your cooldown decreases", + "InfectiousInfo": "Infect everyone", + "VirusInfo": "Kill and infect everyone", + "PursuerInfo": "Protect yourself and live to the end!", + "PlagueDoctorInfo": "Spread the infection!", + "SpecterInfo": "Get killed and finish your tasks to win!", + "PirateInfo": "Successfully plunder players to win", + "AgitaterInfo": "Pass a Bomb onto others", + "MaverickInfo": "Kill and survive to the end", + "CursedSoulInfo": "Snatch souls and steal the win", + "PickpocketInfo": "Steal votes from your kills", + "TraitorInfo": "Eliminate the Impostors, then win", + "VultureInfo": "Eat bodies by reporting to win", + "TaskinatorInfo": "Silent tasks, deadly blasts", + "BenefactorInfo": "Task complete, shield elite!", + "MedusaInfo": "Stone bodies by reporting them", + "SpiritcallerInfo": "Turn Players into Evil Spirits", + "AmnesiacInfo": "Remember the role of a dead body", + "ImitatorInfo": "Imitate a player's role", + "BanditInfo": "Rob a player's add-on", + "DoppelgangerInfo": "Steal your target's identity", + "PunchingBagInfo": "Get attacked a few times to win!", + "KamikazeInfo": "Kill players with a suicidal mission", + "DoomsayerInfo": "Successfully guess players to win", + "ShroudInfo": "Shroud players to make them kill", + "WerewolfInfo": "Kill crewmates in groups", + "ShamanInfo": "Deflect all the attacks on Voodoo doll", + "SeekerInfo": "Play Hide and Seek with your target", + "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", + "OccultistInfo": "Kill and curse your enemies", + "SchrodingersCatInfo": "The cat is both alive and dead until observed.", + "RomanticInfo": "Protect your partner to win together", + "VengefulRomanticInfo": "Revenge your partner to win together", + "RuthlessRomanticInfo": "Kill everyone to win with your partner", + "PoisonerInfo": "Kill everyone with delayed kills", + "HexMasterInfo": "Hex players to kill them in meetings", + "WraithInfo": "Vent to go invisible temporarily", + "JinxInfo": "Reflect attacks onto your attackers", + "PotionMasterInfo": "Use your potions to your advantage", + "NecromancerInfo": "Kill your killer to defy death", + "WardenInfo": "(Ghost) Alert about danger", + "MinionInfo": "(Ghost) Blind enemies", + "LoversInfo": "Stay alive and win together", + "MadmateInfo": "Help the Impostors", + "WatcherInfo": "You see all the colors of votes", + "LastImpostorInfo": "Lower kill cooldown", + "OverclockedInfo": "Lower cooldown", + "FlashInfo": "You're faster", + "TorchInfo": "You have enhanced vision!", + "SeerInfo": "You are alerted when somebody has died", + "TiebreakerInfo": "Break tied votes", + "ObliviousInfo": "You can't report bodies", + "BewilderInfo": "A twist of vision, a web of confusion", + "WorkhorseInfo": "Be the first to complete all tasks and get more", + "FoolInfo": "You can't fix sabotages", + "AvangerInfo": "You take someone with you upon death", + "YoutuberInfo": "Get killed first to win", + "CelebrityInfo": "Everyone knows when you die", + "EgoistInfo": "Win on your own", + "TicketsStealerInfo": "Gain votes with kills", + "ParanoiaInfo": "You're dead and alive simultaneously", + "MimicInfo": "Reveal killed players' roles to impostors upon death", + "GuesserInfo": "Guess roles of players in meetings to kill", + "NecroviewInfo": "See the team of the dead", + "ReachInfo": "You have a longer kill range", + "BaitInfo": "Your killer self-reports your body", + "TrapperInfo": "Freeze your killer for a few seconds", + "OnboundInfo": "You can't be guessed", + "ReboundInfo": "Guess me right, and face your plight!", + "MundaneInfo": "Tasks all done, guessing's begun.", + "UnreportableInfo": "Your body can't be reported", + "LuckyInfo": "Dodge attackers", + "DoubleShotInfo": "You have an extra life when guessing", + "RascalInfo": "You appear evil in some cases", + "SoullessInfo": "You have no soul", + "GravestoneInfo": "Your role is revealed when you die", + "LazyInfo": "You're too lazy", + "AutopsyInfo": "You see how others died", + "LoyalInfo": "You cannot be recruited", + "EvilSpiritInfo": "You are an evil Spirit", + "RecruitInfo": "Help the Jackal", + "AdmiredInfo": "The Admirer chose you as their love", + "GlowInfo": "You glow in the dark", + "RadarInfo": "Arrow's hue, closest to you!", + "DiseasedInfo": "Increase the cooldown of the player who interacts with you", + "AntidoteInfo": "Decrease the cooldown of the player who interacts with you", + "StubbornInfo": "Protect your role and add-ons", + "SwiftInfo": "Your kills don't cause a lunge", + "UnluckyInfo": "Doing things has a chance to kill you", + "VoidBallotInfo": "Your vote count is 0", + "AwareInfo": "Know who revealed your role", + "FragileInfo": "Die instantly if someone uses the kill button on you", + "GhoulInfo": "Kill your killer after dying", + "BloodthirstInfo": "Become bloodthirsty and kill", + "MareInfo": "Kill in the darkness", + "BurstInfo": "Make your killer burst!", + "SleuthInfo": "Gain info from dead bodies", + "ClumsyInfo": "You have a chance to miss your kill", + "NimbleInfo": "You can vent!", + "CircumventInfo": "You can no longer vent", + "OiiaiInfo": "OIIAIOIIIAI", + "CyberInfo": "You're popular!", + "HurriedInfo": "God, I got too much stuff!", + "InfluencedInfo": "You lack decisiveness!", + "SilentInfo": "Vote like a Ghost!", + "SusceptibleInfo": "Death-reason lotto!", + "TrickyInfo": "Tricky slays, in mysterious ways.", + "TiredInfo": "Labor makes you rest Zzz..", + "StatueInfo": "You're still as a rock nearby people", + "GMInfo": "Spectate the chaos!", + "NotAssignedInfo": "No assigned role", + "SunnyboyInfo": "Shine, shine my sunshine!", + "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", + "RainbowInfo": "Colorful melodies! You don't even know your own color.", + "DollMasterInfo": "Take control of players actions!", + "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", + "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", + "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", + "TrackerTOHEInfoLong": "(Crewmates):\nAs the Tracker, press your tracker button on a player to track their location via the map for a limited amount of time.", + "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", + "PhantomTOHEInfoLong": "(Impostors):\nAs the Phantom, you can press your vanish button to go invisible to escape a kill. You can click your appear button if you want to become visible before the timer runs out or not.\nNote: You will make a smoke cloud whenever you go invisible and become visible. So make sure you are in a safe area where no one will see you.", + "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", + "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", + "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", + "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased. The Target swaps after a certain amount of time.", + "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworks up to the maximum amount the host sets.\nWhen you are the last Impostor and all Fireworks have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworks, it's considered an Impostor victory.", + "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline, as shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", + "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", + "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. This means that even if a meeting is called first, your target still dies. However, if you bite a Bait, you kill normally and report the body. Depending on the settings, you can use double trigger (bite players - single click, kill normally - double click).", + "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", + "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown but moves very slowly and has very little vision. Zombie can not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", + "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark a target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", + "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", + "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", + "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown with tiny vision. However, you cannot vent, sabotage, report, nor call emergency meetings.\n\nNote: You will bypass any shields, killing bait and beartrap won't take any effect", + "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport; be careful).", + "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", + "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", + "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", + "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", + "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", + "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", + "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player, you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting, your marked location will reset.\n\nAfter every teleported kill, you will freeze for a configurable amount of time.", + "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker, you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out, therefore destroying all the rifts.\n\nNote: Up to two rifts can be placed at a time; if you try to place a third, it removes the first one.", + "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other players, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", + "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", + "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", + "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", + "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", + "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", + "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", + "TrapsterInfoLong": "(Impostors):\nThe Trapster has a unique method of killing. By initiating a body report, the Trapster can eliminate the player attempting to report the body the Trapster killed.\nNote: If Trapster kills the Bait, the Trapster will die immediately.", + "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the Host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", + "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned-up body cannot be reported (including bait).", + "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they encounter to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", + "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always odd.", + "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the max of times you can counterattack)", + "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", + "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the kill cooldown; he will kill even if it's still on cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", + "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shapeshifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", + "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA crew target can only be erased once (include Oiiai)", + "ButcherInfoLong": "(Impostors):\nThe Butcher's kills, including passive ones, leave multiple dead bodies on targets, which can be a bit confusing when reporting. Here's the rule: the killed target must repeatedly display the animation of being killed, which cannot be skipped, and they cannot participate in the meeting normally during this period. And if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", + "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", + "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", + "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you finish a task.", + "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but cannot vent.\nWhen you kill, you temporarily become immune to attacks.", + "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear as a crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", + "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", + "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", + "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", + "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", + "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not teleport after they shapeshift and players who are in the vent will not teleport.", + "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Lights or Reactor), you cannot kill.", + "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Comms or O2), then you can kill.", + "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nDepending on the settings, Councillor will suicide when he judge his teammates.\nConverted Councillor can judge freely.", + "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", + "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, You shapeshift to mark your targets for a deathpact.\nIf you have enough players marked for a death pact, they must meet within a specific period; if they fail to do so, they die.\nIf a marked player dies before the death pact becomes complete, the pact is withdrawn.", + "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to change the appearance of the target of the shapeshift permanently. Additionally, when each player's appearance changes, you will have your kill cooldown reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", + "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshifted.", + "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shapeshifting to swap the position of all players randomly. The swap happens twice, once when you start your shapeshift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone, and players in vents will not teleport.", + "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown resets to its original value.", + "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following information will be displayed on the players:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in the range of the infected player becomes infected themselves.\nInfection progress is cumulative and does not reset with distance or after meetings.", + "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", + "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", + "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", + "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", + "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round, if someone kills the target, the killer will turn into a Refugee.", + "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you have a charge bar which indicates when the slaughter is ready. When it is at 100% the next time you kill someone, you go into slaughter mode, meaning you can kill indefinitely until your bar runs out of charge. Otherwise, you have a normal KCD.", + "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized quickly, and their vision will be affected.", + "EvilMiniInfoLong": "(Impostors):\nAs the Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which gets drastically shortened as you grow up.", + "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", + "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate gets voted out in a meeting, if you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The Host determines the number of additional players dying.", + "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task. In addition, the Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for Anonymous, being marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", + "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only die when the Murderer is alone with the Super Star (regular kills only). In addition, the Guessers can't guess the Super Star. ", + "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", + "CleanserInfoLong": "(Crewmates):\nAs The Cleanser, you can vote to erase the add-ons of any target at the meeting. This erasure takes effect after the meeting ends. Depending on the settings, the cleansed player may never receive add-ons again.", + "KeeperInfoLong": "(Crewmates):\nAs keeper, you can vote for someone to protect them from being ejected. You can only do this a configurable number of times.", + "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. Depending on the settings, players can't see your extra votes, you can vent to call a meeting at any time, or you can have yourself revealed as Mayor upon task completion.", + "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting; at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", + "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, and Communications using only one side. You can fix Lights by flicking only one switch. Opening a door will open all doors in the map.", + "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", + "VigilanteInfoLong": "(Crewmates):\nAs the Vigilante, you are tasked with eliminating potential threats to the Crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster cannot convert Vigilante into madmate.", + "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or get voted (the vote count will be 0). The Jailer may choose to execute the prisoner by voting for them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote: Jailed players cannot be guessed or judged, and jailed players can only guess Jailer.", + "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see the Impostor's names displayed in red on the meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", + "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the Crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", + "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, the Doctor can access vitals wherever you are while he still has battery left.", + "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes for someone, the meeting will end on the spot, and the player they voted for will be ejected from the meeting. The moment the Dictator votes someone out, the Dictator will also die.", + "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the Host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", + "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", + "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", + "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role the guesser tried to guess, and you will also be notified in case of a misguess.", + "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill anyone but only do it once the whole game.", + "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", + "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", + "VeteranInfoLong": "(Crewmates):\nAs the Veteran, you can enter the alert state by venting. If a player tries to kill the Veteran in the alert state, the Veteran will kill the murderer instead. Veteran will see a shield animation on their body and a text above their head as a reminder when they enter and exit the alert state.", + "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though; crewmates can also be killed with the bombs.", + "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy the target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil with a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to Copycat after every meeting.", + "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate, and the murderer is an Impostor, the Bodyguard will not activate the skill.", + "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the killing ability has the counterfeit, he will commit suicide when he tries to kill someone next time.", + "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", + "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game. Depending on the settings, the target's shield can or cannot deactivate when the Medic dies. The Medic can also see if someone is trying to break the target's shield.\nDepending on the Host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", + "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote: If the setting to give random active players as a hint is on, you cannot check the same player multiple times.", + "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the Host). If it is wrong, the Judge commits suicide.\nCommand for judgment: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", + "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body, they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", + "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after someone reports a dead body. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question, which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", + "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. The shied animations typically indicate a role ability, so look out for this.", + "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has multiple votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exiled.", + "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", + "OverseerInfoLong": "(Crewmates):\nAs The Overseer, you have minimal vision, but you can use your kill button to reveal the role of a nearby player. A 「○」 will be displayed next to the revealed target after you use the kill button on them, and you will also be scanning them (only you can see this). Stay near the target for a defined time to reveal his role; if you move too far away, the reveal will cancel.", + "CoronerInfoLong": "(Crewmates):\nAs a Coroner, you can't report corpses; instead, after trying to report the corpse, you will see an arrow leading you to the killer. If someone calls a meeting, the arrows disappear. Depending on the settings, players can't report the body you found.", + "PresidentInfoLong": "(Crewmates):\nThe President has two abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President, and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", + "MerchantInfoLong": "(Crewmates):\nAs a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can prevent the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The money used is lost and not available for additional bribes.", + "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", + "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk, you can kill a limited amount of players decided by the host, though there's a chance you miss, slicing someone multiple times increases the chances.", + "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", + "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when someone calls a meeting.", + "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon task completion. Guessers can't even guess you in meetings.", + "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires, you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is 0 seconds, you still have a short time to vent.\nIf you don't make it, you die; if you make it, the suicide timer is reset.\nAlso after you vent, no one can interact with you for a defined period.\nAfter; the period is over, and you are immobilized for another defined period, and cannot report any bodies.", + "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you exit the vent, you will spawn near a random vent in the map (Except the one you used).", + "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", + "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double-click to kill normally. When you die, all marked also die, with death reason Targeted.", + "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by the Host.", + "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", + "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", + "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", + "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message if they are on the same team or a denial message if they are not on the same team.\n\nAll neutrals and converted players are counted in the same team. Trickster counts as Crew, and Rascal counts as Impostor.\nChecking command: /cmp [player id 1] [player id 2].", + "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non-crew role. Crewmates can see ☆besides Captain's name.\n\nIf anyone betrays the Captain's trust by voting Captain out, they will lose an add-on.", + "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", + "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", + "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", + "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", + "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", + "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", + "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", + "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real-time.", + "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings).", + "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", + "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, your survival is crucial. You can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses. This unique role adds a new dynamic to the game, where your survival is not just for your benefit but for the entire Crew's success.", + "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the kill button interaction is blocked, the player's cooldown will reset to 10s'", + "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player.", + "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the kill button on the player and following them for a few seconds. When the dousing starts, and it's successful, a shield animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", + "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting. You may have to report the body to receive a clue, depending on the settings. The more tasks you complete, the more precise the clues get.", + "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", + "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your kill cooldown goes down permanently by the set amount. However, if you kill someone who is not one of your targets, your kill cooldown permanently increases by the set amount. A colored name indicates your targets.", + "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini has two roles. A Nice or Evil Mini is chosen.\n\nUse'/r nice mini' and '/r evil mini' respectively for more details.", + "JesterInfoLong": "(Neutrals):\nIf the Jester gets voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", + "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", + "ExecutionerInfoLong": "(Neutrals):\nThe Executioner is a role with an execution target, indicated by a diamond symbol「♦」next to their name. If the execution target is killed, the Executioner's role will change to either Crewmate, Jester, or Opportunist, depending on the game settings. However, if the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", + "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", + "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", + "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", + "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill people normally (i.e., don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", + "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you steal the win, i.e., everyone else loses, and you win.", + "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target gets voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", + "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", + "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by the Host, to kill players (though they are still recruited). When the required number of players are recruited (displayed next to your name), you must vent within the specified time to win the game immediately with all your recruits. If you do not vent in time, you lose and die.", + "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, depending on the settings, you can only kill Lovers and other recruiting roles and add-ons. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", + "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", + "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause a Lights sabotage (if Lights sabotage is already active, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the Host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker wins alone.", + "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on the Host's settings, you can only win if you are alive and or revealed to everyone at the beginning (these settings are rarely both on).", + "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting finishes, your tasks reset, and you need to start all over again.\nVotes on the Solsticer will be directly canceled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in-game.", + "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required votes, the game ends, and you win alone, even if you voted a Jester or Executioner's target out.", + "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", + "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", + "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", + "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", + "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", + "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone at the meeting after you transform.", + "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", + "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after meeting)", + "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", + "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", + "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", + "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", + "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain the majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", + "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive.", + "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", + "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you can outnumber the Crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", + "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", + "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, survive to the end of the game.", + "SpecterInfoLong": "(Neutrals):\nAs the Specter, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", + "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both the Pirate and the target choose the same number, the Pirate wins.\nAdditionally, if the Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command: /duel X (where X can be 0, 1, or 2)\n\nYou win after winning a certain number of duels set by the Host.\n\nNote: The kill would not count towards pirate victory if the target did not participate in the duel.", + "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", + "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", + "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", + "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", + "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", + "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", + "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", + "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", + "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", + "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", + "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", + "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", + "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button one time to steal a player's addon and twice to kill. You may instantly steal the addon or after the meeting starts, depending on the settings. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable addons on the target or the target is stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can vent is on, Nimble will become unstealable.", + "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", + "PunchingBagInfoLong": "(Neutrals):\nAs the Punching Bag, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", + "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings), then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themselves after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", + "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", + "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.\nNote: If the killer cannot kill the chosen target, murder is canceled, but if the killer rechecks the Shaman, the killer will kill the Shaman.", + "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If the seeker tags the wrong player, a point is deducted, and if the seeker tags the correct player, a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target.\n\nThe seeker needs to collect a certain number of points set by the Host to win.", + "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark up to x amount of targets each round by using the kill button on them. You must have one of the marked targets ejected when the meeting starts. If unsuccessful, you will commit suicide, except if you didn't mark any targets or all the targets are dead. The selected targets reset to 0 after the meeting ends. If you succeed, you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the Host.", + "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use its kill button on you, the interaction is not blocked, and you will die.", + "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield that protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote: If your role changes, your win condition will be changed accordingly", + "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", + "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", + "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", + "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", + "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", + "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", + "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three different potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons default to killing.", + "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally when someone tries to kill you, you will block the kill, and you will teleport to a random vent. You will have a limited time to kill your killer. If you succeed in doing so, you live. If the time runs out before you kill your killer, you die permanently. If you try to kill someone else other than your killer, you will die.", + "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", + "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nThis feature is only assigned to roles with a kill button.", + "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when they are the last ones standing, and their victory is shared. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the Host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", + "MadmateInfoLong": "(Add-ons):\nOnly Crewmates can become Madmate. Madmate's task is to help the Impostors win the game. Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors, and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone, including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone\nPacifist => Their ability only works on Crewmates.", + "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", + "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the Host)", + "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", + "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", + "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreakers choose different tie targets simultaneously, the skills of the Tiebreaker will not take effect.", + "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. The Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still report automatically, and Oblivious can still be used as a scapegoat for Anonymous.", + "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder has died, the murderer's vision may become the same as the Bewilder's, depending on the settings.", + "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, and Workhorse will give the player extra tasks. The Host sets the number of additional tasks.", + "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", + "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out, and irregular kills will not count), the Avenger will revenge a random player.", + "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to die in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc., will not trigger the skills of the YouTuber.", + "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", + "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the Host sets the vote number, and the decimal is rounded down).\nAlso, extra votes from the Stealer are hidden during the meeting depending on the options.", + "ParanoiaInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Paranoia, you will be considered as two players in the game to determine when the game ends due to killers having the majority. Additionally, this grants you an extra vote, depending on options.", + "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called. This message will include information on roles which the Mimic killed.", + "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess the roles of players in meetings to kill them.\nGuessing the incorrect role kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", + "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. Unlike everyone else, you have the longest kill range possible in the game.", + "BaitInfoLong": "(Add-ons):\nWhen the Bait dies, the murderer who killed the Bait will self-report the Bait's body. However, this won't happen when a Scavenger, Cleaner, Swooper, Wraith, Medusa, or Killing Machine kills the Bait. The report may have a delay according to the Host's settings.", + "TrapperInfoLong": "(Add-ons):\nWhen Beartrap dies, Beartrap immobilizes killer for a configurable amount of time.", + "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", + "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", + "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", + "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", + "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", + "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess once you complete all of your tasks.", + "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", + "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse will be unreportable.", + "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", + "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on, there is a probability for you to evade the kill; the Host sets the specific probability. The killer will see the shield animation when the evasion takes effect, but you will not know anything.", + "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", + "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", + "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul steals your soul, you get this add-on.\n\nYou are not counted as alive.", + "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", + "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", + "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", + "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", + "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", + "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", + "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", + "GlowInfoLong": "(Add-ons):\nDuring lights out, you and players nearby you will receive a vision boost.", + "RadarInfoLong": "(Add-ons):\nAs Radar, you have arrows pointing towards the closest person at all times.", + "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be increased by a configurable amount of time.", + "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be decreased by a configurable amount of time.", + "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add-on, Eraser can't erase your role, Cleanser can't cleanse you, Bandit can't steal from you, and Monarch can't knight you.\nAdditionally, you can't gain any new add-ons from the Merchant.", + "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.\nNote: Swift also ignores Bait", + "UnluckyInfoLong": "(Add-ons):\nAs Unlucky, when you complete tasks, kill, venting, or open door, you have a chance to die.", + "VoidBallotInfoLong": "(Add-ons):\nHolder of this add-on will have 0 vote count.", + "AwareInfoLong": "(Add-ons):\nAs the Aware, you get a notification in the next meeting if a revealing role had interacted with you.", + "FragileInfoLong": "(Add-ons):\nAs Fragile, you will instantly die if someone tries to use the kill button on you (even if the role cannot directly kill).", + "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on task completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nThis is only assigned to crewmates, and not crewmates with no tasks or are task-based.", + "BloodthirstInfoLong": "(Add-ons):\nAs the Bloodthirst, doing tasks allows you to become bloodthirsty and kill players.\nWhen you finish a task, the next player you come in contact with dies.\n\nYour Bloodthirst remains after a meeting.\nUpon making a kill, your Bloodthirst clears till the next task you complete.\nBloodthirsts do not stack.\n\nOnly assigned to crewmates with tasks.", + "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", + "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", + "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", + "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset, and the target remains untouched.\n\nOnly assigned to killers.", + "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", + "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", + "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced, then the vote result won't shift\nCollector cannot become influenced.", + "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", + "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", + "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", + "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", + "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", + "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", + "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", + "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", + "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy.", + "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence does not affect the game, and all players know who the Game Master is. The Game Master role will be assigned to the Host, who will automatically become a ghost at the start of the game.", + "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. The game will not end when you are alive due to killers gaining the majority.\nAdditionally, you have access to portable vitals.", + "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown will be permanently halved.", + "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", + "GhastlyInfoLong": "(Crewmates [Ghost]):\nAs the Ghastly, possess an unsuspecting person, after that choose a target for them, now they'll only be able to use their kill (or kill ability) on target until you possess someone else or possess time runs out.", + "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", + "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", + "ShowTextOverlay": "Text Overlay", + "Overlay.GuesserMode": "Guesser Mode", + "Overlay.NoGameEnd": "No Game End", + "Overlay.DebugMode": "Debug Mode", + "Overlay.LowLoadMode": "Low Load Mode", + "Overlay.AllowConsole": "Console", + "DisableShieldAnimations": "Disable Unnecessary Shield Animations", + "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", + "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", + "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", + "AbilityUseLimit": "Initial Ability Use Limit", + "ShowArrows": "Has Arrows pointing toward bodies", + "ArrowDelayMin": "Minimum Arrow show-up delay", + "ArrowDelayMax": "Maximum Arrow show-up delay", + "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", + "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", + + "AbilityCD": "Ability Cooldown", + "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", + "ShowSpecificRole": "Know specific roles on Task Completion", + "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", + "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", + "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", + "DisableMeeting": "Disable Meetings", + "DisableCloseDoor": "Disable Doors Sabotage", + "DisableSabotage": "Disable Sabotages", + "NoGameEnd": "No Game End", + "AllowConsole": "BepInEx Console", + "DebugMode": "Debug Mode", + "SyncButtonMode": "Limit Meeting Times", + "RandomMapsMode": "Random Maps Mode", + "SyncedButtonCount": "Max Number of Emergency Meetings per Game", + "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", + "HHFailureKCDIncrease": "Kill cooldown increase on killing others", + "HHNumOfTargets": "Number of targets", + "Targets": "Targets: ", + "HHMaxKCD": "Maximum kill cooldown", + "HHMinKCD": "Minimum kill cooldown", + "AllAliveMeeting": "Meeting When No One is Dead", + "AllAliveMeetingTime": "Meeting Time When No One is Dead", + "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", + "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", + "AdditionalEmergencyCooldownTime": "Additional Cooldown", + "LadderDeath": "Fall From Ladders", + "LadderDeathChance": "Fall To Death Chance", + "EveryoneCanSeeDeathReason": "Everyone Can See Death Reason", + "DisableSwipeCardTask": "Disable Swipe Card Task", + "DisableSubmitScanTask": "Disable Submit Scan Task", + "DisableUnlockSafeTask": "Disable Unlock Safe Task", + "DisableUploadDataTask": "Disable Upload Data Task", + "DisableStartReactorTask": "Disable Start Reactor Task", + "DisableResetBreakerTask": "Disable Reset Breakers Task", + "DisableShortTasks": "Disable Short Tasks", + "DisableCleanVent": "Disable Clean Vent Task", + "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", + "DisableChartCourse": "Disable Chart Course Task", + "DisableStabilizeSteering": "Disable Stabilize Steering Task", + "DisableCleanO2Filter": "Disable Clean O2 Filter Task", + "DisableUnlockManifolds": "Disable Unlock Manifolds Task", + "DisablePrimeShields": "Disable Prime Shields Task", + "DisableMeasureWeather": "Disable Measure Weather", + "DisableBuyBeverage": "Disable Buy Beverage", + "DisableAssembleArtifact": "Disable Assemble Artifact Task", + "DisableSortSamples": "Disable Sort Samples Task", + "DisableProcessData": "Disable Process Data Task", + "DisableRunDiagnostics": "Disable Run Diagnostics Task", + "DisableRepairDrill": "Disable Repair Drill Task", + "DisableAlignTelescope": "Disable Align Telescope Task", + "DisableRecordTemperature": "Disable Record Temperature Task", + "DisableFillCanisters": "Disable Fill Canisters Task", + "DisableMonitorTree": "Disable Monitor Tree Task", + "DisableStoreArtifacts": "Disable Store Artifacts Task", + "DisablePutAwayPistols": "Disable Put Away Pistols Task", + "DisablePutAwayRifles": "Disable Put Away Rifles Task", + "DisableMakeBurger": "Disable Make Burger Task", + "DisableCleanToilet": "Disable Clean Toilet Task", + "DisableDecontaminate": "Disable Decontaminate Task", + "DisableSortRecords": "Disable Sort Records Task", + "DisableFixShower": "Disable Fix Shower Task", + "DisablePickUpTowels": "Disable Pick Up Towels Task", + "DisablePolishRuby": "Disable Polish Ruby Task", + "DisableDressMannequin": "Disable Dress Mannequin Task", + "DisableCommonTasks": "Disable Common Tasks", + "DisableFixWiring": "Disable Fix Wiring Task", + "DisableEnterIdCode": "Disable Enter ID Code Task", + "DisableInsertKeys": "Disable Insert Keys Task", + "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", + "DisableLongTasks": "Disable Long Tasks", + "DisableAlignEngineOutput": "Disable Align Engine Output Task", + "DisableInspectSample": "Disable Inspect Sample Task", + "DisableEmptyChute": "Disable Empty Chute Task", + "DisableClearAsteroids": "Disable Clear Asteroids Task", + "DisableWaterPlants": "Disable Water Plants Task", + "DisableOpenWaterways": "Disable Open Waterways Task", + "DisableReplaceWaterJug": "Disable Replace Water Jug Task", + "DisableRebootWifi": "Disable Reboot Wifi Task", + "DisableDevelopPhotos": "Disable Develop Photos Task", + "DisableRewindTapes": "Disable Rewind Tapes Task", + "DisableStartFans": "Disable Start Fans Task", + "DisableOtherTasks": "Disable Situational Tasks", + "DisableEmptyGarbage": "Disable Empty Garbage Task", + "DisableFuelEngines": "Disable Fuel Engines Task", + "DisableDivertPower": "Disable Divert Power Task", + "DisableActivateWeatherNodes": "Disable Weather Nodes Task", + "DisableRoastMarshmallow": "Disable Roast Marshmallow", + "DisableCollectSamples": "Disable Collect Samples", + "DisableReplaceParts": "Disable Replace Parts", + "DisableCollectVegetables": "Disable Collect Vegetables", + "DisableMineOres": "Disable Mine Ores", + "DisableExtractFuel": "Disable Extract Fuel", + "DisableCatchFish": "Disable Catch Fish", + "DisablePolishGem": "Disable Polish Gem", + "DisableHelpCritter": "Disable Help Critter", + "DisableHoistSupplies": "Disable Hoist Supplies", + "DisableFixAntenna": "Disable Fix Antenna", + "DisableBuildSandcastle": "Disable Build Sandcastle", + "DisableCrankGenerator": "Disable Crank Generator", + "DisableMonitorMushroom": "Disable Monitor Mushroom", + "DisablePlayVideoGame": "Disable Play Video Game", + "DisableFindSignal": "Disable Find Signal", + "DisableThrowFisbee": "Disable Throw Frisbee", + "DisableLiftWeights": "Disable Lift Weights", + "DisableCollectShells": "Disable Collect Shells", + "SuffixMode": "Suffix", + "SuffixMode.None": "None", + "SuffixMode.Version": "Version", + "SuffixMode.Streaming": "Streaming", + "SuffixMode.Recording": "Recording", + "SuffixMode.RoomHost": "Room Host", + "SuffixMode.OriginalName": "Original Name", + "SuffixMode.DoNotKillMe": "Don't kill me", + "SuffixMode.NoAndroidPlz": "No phones", + "SuffixMode.AutoHost": "Auto-Host", + "SuffixModeText.DoNotKillMe": "Don't kill me", + "SuffixModeText.NoAndroidPlz": "No phones please", + "SuffixModeText.AutoHost": "Auto-hosting", + "FormatNameMode": "Player Name Mode", + "FormatNameModes.None": "Disable", + "FormatNameModes.Color": "Color", + "FormatNameModes.Snacks": "Random", + "DisableEmojiName": "Disable Emoji in names", + "FixFirstKillCooldown": "Override Starting Kill Cooldown", + "FixKillCooldownValue": "Starting Kill Cooldown", + "OverclockedReduction": "Kill Cooldown Reduction", + "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", + "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", + "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", + "GhostIgnoreTasks": "Ghosts Exempt From Tasks", + "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", + "MaxImpGhostRole": "Max Impostor Ghost-Roles", + "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", + "DefaultAngelCooldown": "Default Ability Cooldown", + "DisableTaskWin": "Disable Task Win", + "DisableTaskWinIfAllCrewsAreDead": "Disable Task Win If All <#8cffff>Crewmates Are Dead", + "DisableTaskWinIfAllCrewsAreConverted": "Disable Task Win If All <#8cffff>Crewmates Are <#ffab1b>Converted", + "HideGameSettings": "Hide Game Settings", + "DIYGameSettings": "Enable only custom /n messages", + "Settings:": "Settings:", + "PlayerCanSetColor": "Players can use the /color command", + "PlayerCanSetName": "Players can use the /rn command", + "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", + "PlayerCanUseTP": "Players can use the /tpin and /tpout command", + "CanPlayMiniGames": "Players can play mini-games", + "KPDCamouflageMode": "Camouflage Appearance", + "RoleOptions": "Role Options", + "DarkTheme": "Enable Dark Theme", + "DisableLobbyMusic": "Disable Lobby Music", + "AutoStart": "Auto start", + "EnableCustomButton": "Enable Custom Button Images", + "EnableCustomSoundEffect": "Enable Custom Sound Effects", + "EnableCustomDecorations": "Enable Custom Map Decorations", + "SwitchVanilla": "Switch Vanilla", + "UnlockFPS": "Unlock FPS", + "ForceOwnLanguage": "Force mod to use your language if possible", + "ForceOwnLanguageRoleName": "Force role names in your language if possible", + "VersionCheat": "Bypass version synchronization check", + "GodMode": "God Mode", + + "AutoDisplayKillLog": "Display Kill-log", + "AutoDisplayLastRoles": "Display Last Roles", + "AutoDisplayLastResult": "Auto Display Last Result", + "RevertOldKillLog": "Revert to old kill-log", + + "HideExileChat": "Hide exile (lava) chat", + "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", + + "EnableYTPlan": "Enable Youtuber Plan", + "InvalidPermissionCMD": "INVALID PERMISSION\n\nSorry, you do not have the permission to use this command, check /me for all permissions", + "KickLowLevelPlayer": "Kick players whose level is lower than", + "TempBanLowLevelPlayer": "Temporarily ban low-level players", + "ApplyWhiteList": "Turn on Whitelist to bypass level kick", + "AllowOnlyWhiteList": "Allow only whitelisted players to join", + "AutoKickStart": "Kick players that say start", + "AutoKickStartTimes": "Number of warnings before kick", + "AutoKickStartAsBan": "Block a player after they're kicked", + "AutoKickStopWords": "Kick players who write banned words", + "AutoKickStopWordsTimes": "Number of warnings for banned words", + "AutoKickStopWordsAsBan": "Block a player after they're kicked", + "AutoWarnStopWords": "Warning to those who write banned words", + "TempBanPlayersWhoKeepQuitting": "Temporarily ban players who leave and join repeatedly", + "QuitTimesTillTempBan": "The quit frequency needed for temp ban", + "KickOtherPlatformPlayer": "Kick Non-PC players", + "OptKickAndroidPlayer": "Kick Android players", + "OptKickIphonePlayer": "Kick iOS players", + "OptKickXboxPlayer": "Kick Xbox players", + "OptKickPlayStationPlayer": "Kick PlayStation players", + "OptKickNintendoPlayer": "Kick Nintendo Switch players", + "ShareLobby": "Allow TOHE-Chan Shares Lobby Code To Discord", + "ShareLobbyMinPlayer": "Share Lobby Code When The Number Of Players Reaches", + "DisableVanillaRoles": "Disable Vanilla Roles", + "VoteMode": "Voting Mode", + "WhenSkipVote": "If the Player Skipped", + "WhenSkipVoteIgnoreFirstMeeting": "Ignore the First Meeting", + "WhenSkipVoteIgnoreNoDeadBody": "Ignore When No Dead Body", + "WhenSkipVoteIgnoreEmergency": "Ignore at Emergency Meetings", + "WhenNonVote": "If the Player didn't vote", + "Default": "No vote", + "Suicide": "Suicide", + "SelfVote": "Self Vote", + "Skip": "Skip", + "WhenTie": "When Tied Vote", + "TieMode.Default": "No ejects", + "TieMode.All": "Eject All", + "TieMode.Random": "Eject Random", + "DisableDevices": "Disable Devices", + "DisableSkeldDevices": "Disable Skeld Devices", + "DisableMiraHQDevices": "Disable MIRA HQ Devices", + "DisablePolusDevices": "Disable Polus Devices", + "DisableAirshipDevices": "Disable Airship Devices", + "DisableFungleDevices": "Disable Fungle Devices", + "DisableSkeldAdmin": "Disable Admin", + "DisableMiraHQAdmin": "Disable Admin", + "DisablePolusAdmin": "Disable Admin", + "DisableAirshipCockpitAdmin": "Disable Cockpit Admin", + "DisableAirshipRecordsAdmin": "Disable Records Admin", + "DisableSkeldCamera": "Disable Cameras", + "DisablePolusCamera": "Disable Cameras", + "DisableAirshipCamera": "Disable Cameras", + "DisableMiraHQDoorLog": "Disable DoorLog", + "DisablePolusVital": "Disable Vitals", + "DisableAirshipVital": "Disable Vitals", + "DisableFungleVital": "Disable Vitals", + "DisableFungleBinoculars": "Disable Binoculars (Not work for vanilla)", + "IgnoreConditions": "Ignore Conditions", + "IgnoreImpostors": "Ignore Impostors", + "IgnoreNeutrals": "Ignore Neutrals", + "IgnoreCrewmates": "Ignore Crewmates", + "IgnoreAfterAnyoneDied": "Ignore After First Death", + "LightsOutSpecialSettings": "Fix Lights Special Settings", + "BlockDisturbancesToSwitches": "Block Switches When They Are Up", + "DisableAirshipViewingDeckLightsPanel": "Disable Viewing Deck Lights Panel (Airship)", + "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", + "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", + "RandomSpawnMode": "Random Spawns Mode", + "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", + "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", + "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", + "CommsCamouflage": "Camouflage during Comms Sabotage", + "DisableOnSomeMaps": "Disable comms camouflage on some maps", + "DisableOnSkeld": "Disable on The Skeld", + "DisableOnMira": "Disable on MIRA HQ", + "DisableOnPolus": "Disable on Polus", + "DisableOnDleks": "Disable on dlekS ehT", + "DisableOnAirship": "Disable on Airship", + "DisableOnFungle": "Disable on The Fungle", + "DisableReportWhenCC": "Disable body reporting while camouflaged", + "EnableDebugMode": "Enable Debug Mode", + "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", + "SendRoleDescriptionFirstMeeting": "Show Role Descriptions to Unmodded Clients at First Meeting", + "RoleAssigningAlgorithm": "Role Assigning Algorithm", + "RoleAssigningAlgorithm.Default": "Default", + "RoleAssigningAlgorithm.NetRandom": ".NET System.Random", + "RoleAssigningAlgorithm.HashRandom": "HashRandom", + "RoleAssigningAlgorithm.Xorshift": "Xorshift", + "RoleAssigningAlgorithm.MersenneTwister": "MersenneTwister", + "MapModification": "Map Modifications", + "DisableAirshipMovingPlatform": "Disable Moving Platform (Airship)", + "AirshipVariableElectrical": "Variable Electrical (Airship)", + "DisableSporeTriggerOnFungle": "Disable Spore Trigger (Fungle)", + "DisableZiplineOnFungle": "Disable Zipline (Fungle)", + "DisableZiplineFromTop": "Disable Use From Top", + "DisableZiplineFromUnder": "Disable Use From Under", + "ResetDoorsEveryTurns": "Reset Doors After Meeting (Airship/Polus/Fungle)", + "DoorsResetMode": "Reset Doors Mode", + "AllOpen": "All Open", + "AllClosed": "All Closed", + "RandomByDoor": "Closed Random", + "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", + "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", + "DecontaminationTimeOnPolus": "Decontamination Time On Polus", + "ApplyDenyNameList": "Apply DenyName List", + "KickPlayerFriendCodeInvalid": "Kick players with an invalid friend code", + "TempBanPlayerFriendCodeInvalid": "Temp Ban players with an invalid friend code", + "ApplyBanList": "Apply BanList", + "RemovePetsAtDeadPlayers": "Remove pets at dead players", + "KillFlashDuration": "Kill-Flash Duration", + "ConfirmEjectionsMode": "Confirm Ejections Mode", + "ConfirmEjections.None": "None", + "ConfirmEjections.Team": "Team", + "ConfirmEjections.Role": "Role", + "ShowImpRemainOnEject": "Show remaining Impostors on ejects", + "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", + "ConfirmEgoistOnEject": "Confirm Egoists on ejection", + "ConfirmLoversOnEject": "Confirm Lovers on ejection", + "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", + "HideBittenRolesOnEject": "Hide roles of bitten players on ejection", + "ShowTeamNextToRoleNameOnEject": "Show what team the ejected player's role is on", + "Ban": "Ban", + "Kick": "Kick", + "NoticeMe": "Notify me", + "NoticeEveryone": "Notify everyone", + "TempBan": "Temporary Ban", + "OnlyCancel": "Only Cancel the cheat actions", + "CheatResponses": "When a cheating player is found", + "NeutralRoleWinTogether": "Neutrals win together", + "NeutralWinTogether": "All Neutrals win together", + "MenuTitle.Disable": "★ Disable ★", + "MenuTitle.MapsSettings": "★ Maps ★", + "MenuTitle.Sabotage": "★ Sabotage ★", + "MenuTitle.Meeting": "★ Meeting ★", + "MenuTitle.Ghost": "★ Ghost ★", + "MenuTitle.Other": "★ Different ★", + "MenuTitle.Ejections": "★ Ejection ★", + "MenuTitle.Settings": "★ Settings ★", + "MenuTitle.TaskSettings": "★ Task Management ★", + "MenuTitle.Guessers": "★ Guesser Mode ★", + "MenuTitle.GuesserModeRoles": "★ Add-ons for Guesser Mode ★", + "ShieldPersonDiedFirst": "Shield player who dead first in the last game", + "LegacyNemesis": "Use Legacy Version", + "ArsonistKeepsGameGoing": "Arsonist keeps the game going", + "ArsonistCanIgniteAnytime": "Can Ignite Anytime", + "ArsonistMinPlayersToIgnite": "Minimum doused needed for ignite", + "ArsonistMaxPlayersToIgnite": "Maximum doused needed for ignite", + "PuppeteerDoubleKills": "Puppet dies alongside victim", + "DollMasterPossessionCooldown": "Possession Cooldown", + "DollMasterPossessionDuration": "Possession Duration", + "DollMasterCanKillAsMainBody": "Can kill as the main body", + "DollMasterTargetDiesAfterPossession": "Target dies after possession", + "MastermindCD": "Manipulate Cooldown", + "MastermindTimeLimit": "Time limit to kill someone", + "MastermindDelay": "Manipulation notification delay", + "ManipulateNotify": "Kill someone in {0}s or die!", + "ManipulatedKilled": "{0} has killed someone", + "SurvivedManipulation": "You survived the Mastermind's manipulation!", + "Glitch_HackCooldown": "Hack Cooldown", + "Glitch_HackDuration": "Hack Duration", + "Glitch_MimicCooldown": "Mimic Cooldown", + "Glitch_MimicDuration": "Mimic Duration", + "Glitch_MimicButtonText": "Mimic", + "Glitch_MimicDur": "Mimic Duration: {0}s", + "Glitch_HackCD": "Hack Cooldown: {0}s", + "Glitch_KCD": "Kill Cooldown: {0}s", + "Glitch_MimicCD": "Mimic Cooldown: {0}s", + "HackedByGlitch": "You are hacked by the Glitch, you can't {0}.", + "GlitchKill": "kill", + "GlitchReport": "report", + "GlitchVent": "vent", + "ShowFPS": "Show FPS", + "FPSGame": "FPS: ", + + "ControlCooldown": "Control Cooldown", + "PoisonCooldown": "Poison Cooldown", + "PoisonerKillDelay": "Poison Kill Delay", + "WardenNotifyLimit": "Max number of alerts", + + "BombCooldown": "Bomb Cooldown", + "Warlock_CanKillSelf": "Can Kill Themselves", + "CrewpostorKnowsAllies": "Knows Impostors", + "AlliesKnowCrewpostor": "Known to Impostors", + "CrewpostorLungeKill": "Crewpostor lunges on kill", + "CrewpostorKillAfterTask": "Number of tasks completed to make one kill", + + "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", + "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", + "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", + "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", + "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", + "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", + "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", + "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", + "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", + "ImpKnowWhosMadmate": "Impostors know Madmates", + "MadmateKnowWhosImp": "Madmates know Impostors", + "MadmateKnowWhosMadmate": "Madmates know each other", + "ImpCanKillMadmate": "Impostors can kill Madmates", + "MadmateCanKillImp": "Madmates can kill Impostors", + "MadmateHasImpostorVision": "Madmates Have Impostor Vision", + "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", + "EGCanGuessImp": "Can Guess Impostor Roles", + "GGCanGuessCrew": "Can Guess Crewmate Roles", + "EGCanGuessAdt": "Can Guess Add-Ons", + "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "GGCanGuessAdt": "Can Guess Add-Ons", + "GuesserCanGuessTimes": "Maximum number of guesses", + "GuesserTryHideMsg": "Try to hide the guesser's command", + "GCanGuessImp": "Impostor can guess Impostor roles", + "GCanGuessCrew": "Crewmate can guess Crewmate roles", + "GCanGuessAdt": "Can guess Add-ons", + "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "BountyTargetChangeTime": "Time Until Target Swaps", + "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", + "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", + "BountyShowTargetArrow": "Show arrow pointing towards the target", + "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", + "DeadImpCantSabotage": "Impostors can't sabotage after they've died", + "VampireKillDelay": "Bite Kill Delay", + "VampireTargetDead": "Target died", + "VampireActionMode": "Action Mode", + "Vampire_OnlyBites": "Only Bites", + "Youtuber_KillerWinsWithYouTuber": "The Killer Wins With YouTuber", + "Maverick_MinKillsToWin": "Minimum number of kills to win", + + "Cooldown": "Cooldown", + "AbilityCooldown": "Ability Cooldown", + "CanKill": "Can Kill", + "KillCooldown": "Kill Cooldown", + "CanVent": "Can Vent", + "ImpostorVision": "Has Impostor Vision", + "CanUseSabotage": "Can Sabotage", + "CanKillImpostors": "Can Kill Impostors", + "CanGuess": "Can Guess in Guesser Mode or as Guesser", + "HideVote": "Hide Vote", + "HideAdditionalVotes": "Hide additional vote(s)", + "CanUseMeetingButton": "Can Call Emergency Meetings", + "ModeSwitchAction": "Switch Action via", + "ShowShapeshiftAnimations": "Show Shapeshift animations", + + "ShapeshifterBase_ShapeshiftCooldown": "Shapeshift Cooldown", + "ShapeshifterBase_ShapeshiftDuration": "Shapeshift Duration", + "ShapeshifterBase_LeaveShapeshiftingEvidence": "Leave Shapeshifting Evidence", + "PhantomBase_InvisCooldown": "Invis Cooldown", + "PhantomBase_InvisDuration": "Invis Duration", + "GuardianAngelBase_ProtectCooldown": "Protect Cooldown", + "GuardianAngelBase_ProtectionDuration": "Protection Duration", + "GuardianAngelBase_ImpostorsCanSeeProtect": "Protect Visible To Impostors", + "ScientistBase_BatteryCooldown": "Vitals Display Cooldown", + "ScientistBase_BatteryDuration": "Battery Duration", + "EngineerBase_VentCooldown": "Vent Cooldown", + "EngineerBase_InVentMaxTime": "Max Time In Vents", + "NoisemakerBase_ImpostorAlert": "Impostors Can Get Alert", + "NoisemakerBase_AlertDuration": "Alert Duration", + "TrackerBase_TrackingCooldown": "Tracking Cooldown", + "TrackerBase_TrackingDuration": "Tracking Duration", + "TrackerBase_TrackingDelay": "Tracking Delay", + + "KamikazeControversialSymbol": "Use controversial symbol", + "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", + "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", + "MechanicSkillLimit": "Initial repair use limit", + "MechanicFixesDoors": "Can open all doors in the same building", + "MechanicFixesReactors": "Can Fix Both Reactors Alone", + "MechanicFixesOxygens": "Can Fix Both O2 Alone", + "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", + "MechanicFixesElectrical": "Can Fix Lights With One Switch", + + "SheriffShowShotLimit": "Display Shot Limit next to Role Name", + "SheriffCanKill%role%": "Can Kill %role%", + "SheriffCanKillNeutrals": "Can Kill Neutrals", + "SheriffCanKillNeutralsMode": "Neutral Configuration", + "SheriffCanKillAll": "All ON", + "SheriffCanKillSeparately": "Individual Settings", + "In%team%": "(Team %team%)", + "SheriffMisfireKillsTarget": "Misfire Kills Target", + "SheriffShotLimit": "Max number of Kills", + "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", + "SheriffCanKillCharmed": "Can kill Charmed players", + "SheriffCanKillEgoist": "Can Kill Egoists", + "SheriffCanKillSidekick": "Can Kill Sidekicks", + "SheriffCanKillLovers": "Can Kill Lovers", + "SheriffCanKillMadmate": "Can Kill Madmates", + "SheriffCanKillInfected": "Can Kill Infected players", + "SheriffCanKillContagious": "Can Kill Contagious players", + "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", + "SheriffMadCanKillImp": "Can kill Impostors", + "SheriffMadCanKillNeutral": "Can kill Neutrals", + "SheriffMadCanKillCrew": "Can kill Crewmates", + + "ReverieIncreaseKillCooldown": "Increase kill cooldown", + "ReverieMaxKillCooldown": "Max kill cooldown", + "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", + "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", + "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", + + "VigilanteNotify": "You have become the very thing you swore to destroy", + + "DoctorTaskCompletedBatteryCharge": "Battery Duration", + "SnitchEnableTargetArrow": "See Arrow Towards Target", + "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", + "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", + "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", + "SnitchCanFindMadmate": "Can Find Madmates", + "SnitchRemainingTaskFound": "Remaining tasks to be known", + "MayorAdditionalVote": "Additional Votes Count", + "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", + "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", + "MeetingsNeededForWin": "Meetings needed to win", + "ExecutionerCanTargetImpostor": "Can Target Impostors", + "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", + "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", + "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", + "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", + "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", + "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", + "LawyerCanTargetImpostor": "Can Target Impostors", + "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", + "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "LawyerCanTargetCrewmate": "Can Target Crewmates", + "LawyerCanTargetJester": "Can Target Jester", + "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", + "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", + "LawyerTargetDeadInMeeting": "Your target was killed while meeting.\nYour role may change depending on the settings.", + + "MercenaryLimit": "Time Until Suicide", + "ArsonistDouseTime": "Douse Duration", + "CanTerroristSuicideWin": "Can Win By Suicide", + "FireworkerMaxCount": "Fireworker Count", + "FireworkerRadius": "Firework Explosion Radius", + "SniperCanKill": "Sniper can kill with bullets remaining", + "SniperBulletCount": "Ammo", + "SniperPrecisionShooting": "Precise Shooting", + "SniperAimAssist": "Aim Assist", + "SniperAimAssistOneshot": "One shot Assist", + + "PyroDouseCooldown": "Douse cooldown", + "PyroBurnCooldown": "Kill cooldown after killing a doused player", + + "UndertakerFreezeDuration": "Freeze Duration", + "NameDisplayAddons": "Display Add-Ons next to the role name", + "YourAddon": "Your Add-ons:", + "NoLimitAddonsNumMax": "Max Add-ons Per Player", + "LoverSpawnChances": "Spawn Chance of Lovers", + "AdditionRolesSpawnRate": "Spawn Chance", + "TorchVision": "Torch Vision", + "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", + "BewilderVision": "Bewilder Vision", + "JesterVision": "Jester Vision", + "LawyerVision": "Lawyer Vision", + "FlashSpeed": "Flash Speed", + "LoverSuicide": "Lovers die together", + "NumberOfLovers": "Number of Lover Pairs (x2 members)", + "LoverKnowRoles": "Lovers know the roles of each other", + "TrapperBlockMoveTime": "Freeze time", + "BecomeTrapperBlockMoveTime": "Freeze time", + "ImpCanBeTrapper": "Impostors can become Beartrap", + "CrewCanBeTrapper": "Crewmates can become Beartrap", + "NeutralCanBeTrapper": "Neutrals can become Beartrap", + "ImpCanBeGravestone": "Impostors can become Gravestone", + "CrewCanBeGravestone": "Crewmates can become Gravestone", + "NeutralCanBeGravestone": "Neutrals can become Gravestone", + "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", + "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", + "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", + "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", + "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", + "EvilTrackerTargetMode": "Can Set Target", + "EvilTrackerTargetMode.Never": "Never", + "EvilTrackerTargetMode.OnceInGame": "Once in-game", + "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", + "EvilTrackerTargetMode.Always": "Any time", + + "EvilHackerCanSeeDeadMark": "Can See The Location of Dead-bodies", + "EvilHackerCanSeeImpostorMark": "Can See The Location of Other Impostors", + "EvilHackerCanSeeKillFlash": "Can See Kill-Flash", + "EvilHackerCanSeeMurderRoom": "Can See The Murder Location", + "EvilHackerMurderNotify": "Murder In", + "EvilHackerLastAdminInfoTitle": "Last-minute admin information", + "EvilHackerDeadbody": "DEAD", + + "TraitorKnowMadmate": "Traitor Knows Madmates", + "NBareRed": "Neutral Benign can be red", + "NEareRed": "Neutral Evil can be red", + "NCareRed": "Neutral Chaos can be red", + "NAareRed": "Neutral Apocalypse can be red", + "CrewKillingRed": "Crewmate Killings can be red", + "PsychicCanSeeNum": "Max number of red names", + "PsychicFresh": "New red names every meeting", + "DetectiveCanknowKiller": "Can find the killer's role", + "EveryOneKnowSuperStar": "Everyone knows the Super Star", + "HackLimit": "Ability Use Count", + "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", + "NemesisCanKillNum": "Max number of revenges", + "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", + "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", + "VectorVentNumWin": "Number of Vents to win", + "CanCheckCamera": "Can track camera usage", + "DefaultKillCooldown": "Starting kill cooldown", + "ReduceKillCooldown": "Reduce kill cooldown by", + "MinKillCooldown": "Minimum kill cooldown", + "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", + "NotifyGodAlive": "Inform players at meetings that God is still alive", + "TransporterTeleportMax": "Max number of teleports", + "TriggerKill": "Kill", + "TriggerVent": "Vent", + "TriggerDouble": "Double Click", + "TimeManagerIncreaseMeetingTime": "Increase voting time by", + "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", + "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", + "AssignOnlyToCrewmate": "Assign only to Crewmates", + "WorkhorseNumLongTasks": "Additional Long Tasks", + "WorkhorseNumShortTasks": "Additional Short Tasks", + "SnitchCanBeWorkhorse": "Snitch can become Workhorse", + "InnocentCanWinByImp": "If their target was an Impostor then they win with them", + "ImpCanBeParanoia": "Impostors can become Paranoia", + "CrewCanBeParanoia": "Crewmates can become Paranoia", + "DualVotes": "Duplicate votes", + "VeteranSkillCooldown": "Alert Cooldown", + "VeteranSkillDuration": "Alert Duration", + "BodyguardProtectRadius": "Protect Radius", + "ImpCanBeEgoist": "An Impostor can become Egoist", + "CrewCanBeEgoist": "Crewmates can become Egoist", + "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", + "EgoistCountAsConverted": "Egoist count as converted neutral", + "ImpCanBeSeer": "Impostors can become Seer", + "CrewCanBeSeer": "Crewmates can become Seer", + "NeutralCanBeSeer": "Neutrals can become Seer", + "ImpCanBeGuesser": "Impostors can become Guesser", + "CrewCanBeGuesser": "Crewmates can become Guesser", + "NeutralCanBeGuesser": "Neutrals can become Guesser", + "ImpCanBeWatcher": "Impostors can become Watcher", + "CrewCanBeWatcher": "Crewmates can become Watcher", + "NeutralCanBeWatcher": "Neutrals can become Watcher", + "ImpCanBeBait": "Impostors can become Bait", + "CrewCanBeBait": "Crewmates can become Bait", + "NeutralCanBeBait": "Neutrals can become Bait", + "ImpCanBeRainbow": "Impostors can become Rainbow", + "NeutralCanBeRainbow": "Neutrals can become Rainbow", + "CrewCanBeRainbow": "Crewmates can become Rainbow", + "GuessRainbow": "He seems too obvious, doesn't he?", + "RainbowColorChangeCoolDown": "The cooldown for changing colors", + "RainbowInCamouflage": "Rainbow color changes during Camouflage", + "BaitDelayMin": "Minimum Report Delay", + "BaitDelayMax": "Maximum Report Delay", + "BaitDelayNotify": "Warn the killer about the upcoming self-report", + "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", + "BaitNotification": "Reveal Bait at the first meeting", + "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self-report.", + "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if a meeting is disabled during comms sabotage", + "DeceiverSkillCooldown": "Ability cooldown", + "DeceiverSkillLimitTimes": "Max number of uses", + "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", + "PursuerSkillCooldown": "Ability cooldown", + "PursuerSkillLimitTimes": "Max number of uses", + "AddictSuicideTimer": "Time Until Suicide", + "GrenadierSkillCooldown": "Grenade Cooldown", + "GrenadierSkillDuration": "Grenade Duration", + "GrenadierCauseVision": "Lowered vision", + "GrenadierCanAffectNeutral": "Can affect Neutrals", + "TicketsPerKill": "Votes Increase Amount Per Kill", + "GangsterRecruitCooldown": "Recruit cooldown", + "GangsterRecruitLimit": "Recruit limit", + "KamikazeMaxMarked": "Max Marked", + "RevolutionistDrawTime": "Tag Duration", + "RevolutionistCooldown": "Tag Cooldown", + "RevolutionistDrawCount": "Amount of Players needed to Tag", + "RevolutionistKillProbability": "Tagged player sacrifice probability", + "RevolutionistVentCountDown": "Time to Vent", + "PelicanKillCooldown": "Eat Cooldown", + "Pelican.TargetCannotBeEaten": "Target cannot be eaten", + "MadSnitchTasks": "Snitch Tasks", + "MedicWhoCanSeeProtect": "Who can see shield", + "MedicKnowShieldBroken": "Who sees kill attempt", + "Medic_SeeMedicAndTarget": "Medic+Shielded", + "Medic_SeeMedic": "Medic", + "Medic_SeeTarget": "Shielded", + "Medic_SeeNoOne": "Nothing", + "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", + "MedicShielDeactivationIsVisible": "Shield deactivation is visible", + "MedicShieldDeactivationIsVisible_Immediately": "Immediately", + "MedicShieldDeactivationIsVisible_AfterMeeting": "After Meeting", + "MedicShieldDeactivationIsVisible_OFF": "OFF", + "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", + "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", + "FortuneTellerSkillLimit": "Max number of ability uses", + "MadmateSpawnMode": "Madmate spawning mode", + "MadmateSpawnMode.Assign": "Assign", + "MadmateSpawnMode.FirstKill": "First Kill", + "MadmateSpawnMode.SelfVote": "Self Vote", + "MadmateCountMode": "Madmates count as", + "MadmateCountMode.None": "Nothing", + "MadmateCountMode.Imp": "Impostors", + "MadmateCountMode.Original": "Original Team", + + "SnatchesWin": "Snatches victory", + "DemonKillCooldown": "Attack Cooldown", + "DemonHealthMax": "Player max health", + "DemonDamage": "Damage ", + "DemonSelfHealthMax": "Demon max health", + "DemonSelfDamage": "Demon damage received", + "LightningConvertTime": "Duration of the transformation to Quantum Ghost", + "LightningKillCooldown": "Lightning Cooldown", + "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", + "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", + "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", + "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", + "WorkaholicCannotWinAtDeath": "Can't win after they died", + "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", + "WorkaholicGiveAdviceAlive": "Advice at the first meeting if alive, can win after death, ghost tasks ON", + "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", + "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", + "KillAttackerWhenAbilityRemaining": "Kill attacker when ability is remaining", + "JinxSpellTimes": "Amount of Jinx Spells", + "CollectorCollectAmount": "Required number of votes", + "GlitchCanVote": "Can vote", + "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", + "MeetingReserved": "Max Bullets reserved for a meeting", + "AccurateCheckMode": "Can know specific role when tasks are not done", + "RandomActiveRoles": "Show random active roles in Fortune Teller hints", + "CamouflageCooldown": "Camouflage Cooldown", + "CamouflageDuration": "Camouflage Duration", + "NinjaMarkCooldown": "Mark Cooldown", + "NinjaAssassinateCooldown": "Assassinate Cooldown", + "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", + "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", + "JudgeCanTrialNeutralB": "Can trial Neutral Benign", + "JudgeCanTrialNeutralK": "Can trial Neutral Killing", + "JudgeCanTrialNeutralE": "Can trial Neutral Evil", + "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", + "JudgeCanTrialNeutralA": "Can trial Neutral Apocalypse", + "JudgeCanTrialSidekick": "Can trial Sidekick", + "JudgeCanTrialInfected": "Can trial Infected", + "JudgeCanTrialContagious": "Can trial Contagious", + "JudgeTryHideMsg": "Hide Judge's commands", + "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", + "JudgeCanTrialMadmate": "Can trial Madmates", + "JudgeCanTrialCharmed": "Can trial Charmed players", + "JudgeDead": "Sorry, you can't trial players after death.", + "JudgeTrialMax": "\nNo more trials left!", + "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", + "Judge_TrialKill": "{0} was judged.", + "Judge_TrialKillTitle": "COURT", + "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Judge_TrialNull": "Please choose a living player for the trial", + "VeteranSkillMaxOfUseage": "Max number of Alerts", + "SwooperCooldown": "Swoop Cooldown", + "SwooperDuration": "Swoop Duration", + "WraithCooldown": "Vanish Cooldown", + "WraithDuration": "Vanish Duration", + "BastionNotify": "A bomb was set off", + "EnteredBombedVent": "That vent was bombed!", + "BastionVentButtonText": "Bomb", + "BombsClearAfterMeeting": "Bombs clear after meetings", + "BastionMaxBombs": "(Initial) Maximum bombs", + "VentBombSuccess": "Bomb has been planted", + "LowLoadMode": "Low Load Mode", + "ShowLobbyCode": "Show lobby code in Discord status", + "BKProtectDuration": "Protection Duration", + "FollowerMaxBetTimes": "Maximum Number of Follows", + "FollowerBetCooldown": "Follow Cooldown", + "FollowerMaxBetCooldown": "Maximum Follow Cooldown", + "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", + "FollowerKnowTargetRole": "Follower knows their target's role", + "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", + "FortuneTellerHideVote": "Hide Fortune Teller's Votes", + "CultistCharmCooldown": "Charm Cooldown", + "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", + "CultistCharmMax": "Maximum Number Of Charm", + "CultistKnowTargetRole": "Know Charmed Player's Role", + "CultistTargetKnowOtherTarget": "Charmed players know each other", + "CultistCanCharmNeutral": "Neutral Roles can be Charmed", + "InfectiousBiteCooldown": "Infect Cooldown", + "KnowTargetRole": "Knows role of target", + "TargetKnowsLawyer": "Target knows their Lawyer", + "InfectiousBiteMax": "Maximum Infections", + "InfectiousKnowTargetRole": "Know infected player's role", + "InfectiousTargetKnowOtherTarget": "Infected players know each other", + "DoubleClickKill": "Double click to kill the target", + + "VirusInfectMax": "Maximum Number Of Spreads", + "VirusKnowTargetRole": "Know Contagious Player's Role", + "VirusTargetKnowOtherTarget": "Contagious players know each other", + "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", + "Virus_ContagiousCountMode": "Contagious players count as", + "Virus_ContagiousCountMode_None": "Nothing", + "Virus_ContagiousCountMode_Virus": "Virus", + "Virus_ContagiousCountMode_Original": "Original Team", + "VirusNoticeTitle": "[ Infected Corpse! ]", + "VirusNoticeMessage": "The body you reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", + "VirusNoticeMessage2": "The body you reported was infected by the Virus! Vote the Virus out during this meeting, or you will die.", + + "Cultist_CharmedCountMode": "Charmed players count as", + "Cultist_CharmedCountMode_None": "Nothing", + "Cultist_CharmedCountMode_Cultist": "Cultist", + "Cultist_CharmedCountMode_Original": "Original Team", + + "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", + "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", + "JackalResetKillCooldownOn": "Kill Cooldown On Reset", + "JackalCanRecruitSidekick": "Can recruit Sidekick", + "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", + "Jackal_SidekickCountMode": "Sidekicks count as", + "Jackal_SidekickCountMode_None": "Nothing", + "Jackal_SidekickCountMode_Jackal": "Jackal", + "Jackal_SidekickCountMode_Original": "Original Team", + "Jackal_SidekickAssignMode": "Sidekick Assign Mode", + "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", + "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", + "Jackal_SidekickAssignMode_Recruit": "Recruit Only", + "JackalWinWithSidekick": "Jackal can win with Sidekick's team", + "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", + "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", + "JackalCanKillSidekick": "Jackal can kill Sidekick", + + "ImpCanBeNecroview": "Impostors can become Necroview", + "CrewCanBeNecroview": "Crewmates can become Necroview", + "NeutralCanBeNecroview": "Neutrals can become Necroview", + "ImpCanBeInLove": "Impostors can be in love", + "CrewCanBeInLove": "Crewmates can be in love", + "NeutralCanBeInLove": "Neutrals can be in love", + "ImpCanBeOblivious": "Impostors can become Oblivious", + "CrewCanBeOblivious": "Crewmates can become Oblivious", + "NeutralCanBeOblivious": "Neutrals can become Oblivious", + "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", + "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", + "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", + "HexesLookLikeSpells": "Hexes appear as spells", + "HexButtonText": "Hex", + "ObliviousBaitImmune": "Immune to Bait", + "ImpCanBeOnbound": "Impostors can become Onbound", + "CrewCanBeOnbound": "Crewmates can become Onbound", + "NeutralCanBeOnbound": "Neutrals can become Onbound", + + "ImpCanBeRebound": "Impostors can become Rebound", + "CrewCanBeRebound": "Crewmates can become Rebound", + "NeutralCanBeRebound": "Neutrals can become Rebound", + + "CrewCanBeMundane": "Crewmates can become Mundane", + "NeutralCanBeMundane": "Neutrals can become Mundane", + "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", + + "ImpCanBeUnreportable": "Impostors can become Disregarded", + "CrewCanBeUnreportable": "Crewmates can become Disregarded", + "NeutralCanBeUnreportable": "Neutrals can become Disregarded", + "PacifistCooldown": "Ability Cooldown", + "PacifistMaxOfUseage": "Max Number of Ability Uses", + "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", + "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", + + "PresidentAbilityUses": "Max Number of Ability Uses", + "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", + "HidePresidentEndCommand": "Hide President's commands", + "NeutralsSeePresident": "Neutrals can see revealed President", + "MadmatesSeePresident": "Madmates can see revealed President", + "ImpsSeePresident": "Impostors can see revealed President", + "PresidentDead": "Sorry, you can't force end the meeting after death.", + "PresidentEndMax": "No more force end meeting uses left!", + "PresidentRevealMax": "You have already revealed yourself...", + "PresidentRevealed": "[{0}] has chosen to reveal themselves as President!", + "GuessPresident": "President has revealed themselves. You can't guess them.", + "PresidentRevealTitle": "PRESIDENT REVEAL", + + "LuckyProbability": "Probability of surviving a kill", + "ImpCanBeLucky": "Impostors can become Lucky", + "CrewCanBeLucky": "Crewmates can become Lucky", + "NeutralCanBeLucky": "Neutrals can become Lucky", + "ImpCanBeFool": "Impostors can become Fool", + "CrewCanBeFool": "Crewmates can become Fool", + "NeutralCanBeFool": "Neutrals can become Fool", + "ImpCanBeDoubleShot": "Impostors can have Double Shot", + "CrewCanBeDoubleShot": "Crewmates can have Double Shot", + "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", + "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", + "DisableReportWhenCamouflageIsActive": "Disable body reporting when camouflage is active", + "CanUseCommsSabotage": "Can use comms sabotage", + "ModTag": "Moderator♥", + "ApplyModeratorList": "Apply Moderator List", + "VipTag": "VIP★", + "ApplyVipList": "Apply VIP List", + "AllowSayCommand": "Allow moderators to use /say command", + "KickCommandDisabled": "The kick command is currently disabled.", + "KickCommandNoAccess": "You do not have access to the kick command.", + "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reason]' to kick a player.\nExample :- /kick 5 not following rules", + "KickCommandKickHost": "You are not permitted to kick the host.", + "KickCommandKickMod": "You are not permitted to kick other moderators.", + "KickCommandKicked": "was kicked from the game by ", + "KickCommandKickedRole": "Their role was", + "BanCommandDisabled": "The ban command is currently disabled.", + "BanCommandNoAccess": "You do not have access to the ban command.", + "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", + "BanCommandBanHost": "You are not permitted to ban the host.", + "BanCommandBanMod": "You are not permitted to ban other moderators.", + "BanCommandBanned": "was banned from the game by ", + "BanCommandBannedRole": "Their role was", + "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", + "ColorCommandDisabled": "The modcolor command is currently disabled.", + "ColorCommandNoAccess": "You do not have access to the modcolor command.", + "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change the color of MODERATOR♥.\nExample :- /modcolor 33ccff", + "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", + "VipColorCommandDisabled": "The vipcolor command is currently disabled.", + "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", + "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change the color of VIP★.\nExample :- /vipcolor 33ccff", + "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", + "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change the color of your tag.\nExample :- /tagcolor ff00ff", + "midCommandDisabled": "The mid command is currently disabled.", + "midCommandNoAccess": "You do not have access to the mid command.", + "WarnCommandDisabled": "The warn command is currently disabled.", + "WarnCommandNoAccess": "You do not have access to the warn command.", + "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", + "WarnCommandWarnHost": "You are not permitted to warn the host.", + + "WarnCommandWarnMod": "You are not permitted to warn other moderators.", + "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", + "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", + "SayCommandDisabled": "The say command is currently disabled.", + "MessageFromModerator": "MODERATOR", + "DeathReason.Kill": "Kill", + "DeathReason.Vote": "Ejected", + "DeathReason.Suicide": "Suicide", + "DeathReason.Spell": "Spelled", + "DeathReason.Cursed": "Cursed", + "DeathReason.Hex": "Hexed", + "DeathReason.Bite": "Bitten", + "DeathReason.Poison": "Poisoned", + "DeathReason.Gambled": "Guessed", + "DeathReason.FollowingSuicide": "Heartbroken", + "DeathReason.Bombed": "Exploded", + "DeathReason.Misfire": "Misfire", + "DeathReason.Torched": "Burned", + "DeathReason.Sniped": "Sniped", + "DeathReason.Execution": "Executed", + "DeathReason.Fall": "Fall", + "DeathReason.Revenge": "Revenge", + "DeathReason.Eaten": "Eaten", + "DeathReason.Sacrifice": "Victim", + "DeathReason.Quantization": "Quantization", + "DeathReason.Overtired": "Overtired", + "DeathReason.Ashamed": "Ashamed", + "DeathReason.PissedOff": "Destroyed", + "DeathReason.Dismembered": "Dismembered", + "DeathReason.LossOfHead": "Strangled", + "DeathReason.Trialed": "Judged", + "DeathReason.Infected": "Infected", + "DeathReason.Jinx": "Jinxed", + "DeathReason.Pirate": "Plundered", + "DeathReason.Shrouded": "Shrouded", + "DeathReason.etc": "Other", + "DeathReason.Mauled": "Mauled", + "DeathReason.Hack": "Hacked", + "DeathReason.Curse": "Cursed", + "DeathReason.Drained": "Drained", + "DeathReason.Shattered": "Shattered", + "DeathReason.Trap": "Trapped", + "DeathReason.Targeted": "Targeted", + "DeathReason.Retribution": "Retribution", + "DeathReason.Slice": "Sliced", + "DeathReason.BloodLet": "Bleed", + "DeathReason.Armageddon": "Armageddon", + "DeathReason.Starved": "Starved", + "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", + "Alive": "Alive", + "Disconnected": "Disconnected", + "Win": " Wins!", + + "Last-": "Last ", + "Madmate-": "Madmate ", + "Recruit-": "Recruit ", + "Charmed-": "Charmed ", + "Soulless-": "Soulless ", + "Infected-": "Infected ", + "Contagious-": "Contagious ", + "Admired-": "Admired ", + + "DeputyHandcuffCooldown": "Handcuff Cooldown", + "DeputyHandcuffMax": "Maximum Handcuffs", + "DeputyHandcuffedPlayer": "Handcuffed target", + "HandcuffedByDeputy": "You were handcuffed!", + "DeputyInvalidTarget": "Target cannot be handcuffed", + "DeputyHandcuffText": "Handcuff", + "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", + + "RejectShapeshift.AbilityWasUsed": "Ability was used", + + "EscapisMtarkedPosition": "You marked self-position", + + "InvestigateCooldown": "Investigate Cooldown", + "InvestigateMax": "Maximum Investigations", + "InvestigateRoundMax": "Maximum Investigations in one round", + + "Color.Red": "Red", + "Color.Green": "Green", + "Color.Gray": "Gray", + "InvestigatorInvestigatedPlayer": "Player Investigated", + "InvestigatorInvalidTarget": "Can not investigate", + "InvestigatorButtonText": "Check", + + "Investigator.Suspicion": "Suspicion", + "Investigator.Role": "Role", + "SabotageCooldownControl": "Sabotage Cooldown Control", + "SabotageCooldown": "Sabotage Cooldown", + "SabotageTimeControl": "Sabotage Duration Control", + "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", + "SkeldO2TimeLimit": "The Skeld O2 Time Limit", + "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", + "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", + "PolusReactorTimeLimit": "Polus Reactor Time Limit", + "AirshipReactorTimeLimit": "Airship Reactor Time Limit", + "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", + "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", + "CommandList": "★ Command list:", + "Command.now": "→ Display active Settings", + "Command.roles": "[RoleName] → Display Role description", + "Command.myrole": "→ Displays a description of your role", + "Command.lastresult": "→ Display match results", + "Command.winner": "→ Display winners", + "CommandOtherList": "● Other commands:", + "Command.color": "[Color] → Change your color", + "Command.rename": "[Name] → Change Host Name", + "Command.quit": "→ I don't want to enter this lobby anymore", + "CommandHostList": "▲ Host Commands:", + "Command.say": "[Content] → Send message as Host", + "Command.mw": "[Seconds] → Set the message waiting duration", + "Command.solvecover": "→ Fix an issue where role names overlap the messages", + "Command.kill": "[Player ID] → Kill assigned player", + "Command.exe": "[Player ID] → Eject assigned player", + "Command.level": "[Level] → Change your in-game level", + "Command.idlist": "→ Display a list of player IDs", + "Command.qq": "→ Lobby will be posted on QQ website (China only)", + "Command.dump": "→ Output Log to Desktop", + "Command.death": "→ Display info on how you died", + "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Capitan to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.", + + "Command.iconinfo": "→ Display info on in-meeting icons", + "Command.iconhelp": "→ Display info on in-meeting icons to everyone", + "Command.Poll": "→ Start a poll with up-to 5 choices", + "IconsTitle": "Icon Meanings⚠", + "Remaining.ImpostorCount": "Impostors left: {0}", + "Remaining.MadmateCount": "Madmates left: {0}", + "Remaining.NeutralCount": "Neutral Killers left: {0}", + "Remaining.ApocalypseCount": "Neutral Apocalypse left: {0}", + "EnableKillerLeftCommand": "Enable use of /kcount command", + "ShowMadmatesInLeftCommand": "Show Madmates (including add-ons)", + "ShowApocalypseInLeftCommand": "Show Neutral Apocalypse", + "SeeEjectedRolesInMeeting": "See ejected roles in meetings", + + "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", + "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", + "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", + "NemesisKillDead": "Choose a living player to take revenge", + "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", + "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", + "NemesisKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", + + "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period!", + "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", + "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", + "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", + "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer. This death is most likely suicide.", + "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", + "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", + "GuessDead": "Sorry, but it's impossible to guess roles after your death", + "GuessSuperStar": "The Super Star can't be guessed... you thought it would be that easy, right?", + "GuessNotifiedBait": "Bait can't be guessed because it was announced. You thought it would be that easy, right?", + "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", + "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", + "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", + "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", + "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", + "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", + "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", + "GuessKill": "{0} was guessed", + "GuessNull": "Please select an ID of a living player to guess their role", + "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "GGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", + "EGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", + "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all their tasks are done? Nice try. You're not getting out of this that easily.", + "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot, so you get another chance!", + "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", + "GuessDisabled": "Sorry, the host restricted guessing for your role.", + "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", + "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", + "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", + "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", + "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", + "GuessShielded": "Sorry, you can't guess the player who the Medic shields", + "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", + "MimicDeadMsg": "Mimic's hint: ", + "FortuneTellerCheck": "According to your fortune...", + "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", + "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", + "FortuneTellerCheckReachLimit": "You've run out of fortunes.", + "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", + "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", + "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", + + "MediumContactLimit": "Max number of contacts (ability uses)", + "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", + "MediumTitle": "MEDIUM", + "MediumHelp": "/ms yes to agree\n/ms no to disagree", + "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", + "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", + "MediumDone": "You successfully responded to the Medium.", + "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", + "MediumNotifySelf": "You established contact with {0}. Please ask them questions and wait for them to respond.\n\nRemaining ability uses: {1}", + "MediumKnowPlayerDead": "Someone died somewhere", + + "ByBard": "by Bard", + "ByBardGetFailed": "Oops, I seem to be out of inspiration.", + "GangsterSuccessfullyRecruited": "You successfully recruited a player", + "GangsterRecruitmentFailure": "Target cannot be recruited", + "BeRecruitedByGangster": "The Gangster has recruited you", + "KamikazeHostage": "Can't hold target hostage", + "VeteranOnGuard": "Ability in use", + "VeteranOffGuard": "Ability expired, {0} uses remain", + "VeteranMaxUsage": "Ability use limit reached", + "GrenadierSkillInUse": "Ability in use", + "GrenadierSkillStop": "Ability expired", + "TicketsStealerGetTicket": "You've got {0} votes", + "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", + "CleanerCleanBody": "The body has been cleaned", + "QuickShooterStoraging": "Bullets stored successfully", + "PoisonerTargetDead": "Target died", + "BloodthirstAdded": "Your bloodthirst is now active!", + "WarlockNoTarget": "Manipulation failed due to no target", + "WarlockNoTargetYet": "You haven't marked a target.", + "WarlockTargetDead": "Manipulation failed due to target dead", + "WarlockControlKill": "Target died", + "OnCelebrityDead": "Warning: Celebrity death!", + "OnCyberDead": "Warning: Cyber died!", + "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", + "TeleportedByTransporter": "Swapping places with: {0}", + "ErrorTeleport": "Teleport failed", + "EraseLimit": "Max Erases", + "EraserHideVote": "Hide Eraser Votes", + "EraserEraseMsgTitle": "ERASER", + "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", + "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", + "EraserEraseSelf": "Unfortunately, you can't erase yourself... Wait, why would you do that in the first place?!", + "EraserTryingGuessErasedPlayer": "You can't guess the role of the player you erased, except add-ons", + "LostRoleByEraser": "You lost your role because of the Eraser", + "KilledByScavenger": "The Scavenger killed you and thus teleported off-map", + "SnitchDoneTasks": "Call a meeting to find the impostors", + "SwooperCanVent": "Vent to turn invisible", + "SwooperInvisState": "You're invisible", + "SwooperInvisStateOut": "You're now visible", + "SwooperInvisInCooldown": "Swoop cooldown isn't up yet. Swooping failed", + "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", + "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", + "WraithCanVent": "Vent to turn invisible", + "WraithInvisState": "You are invisible", + "WraithInvisStateOut": "You are visible again", + "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", + "WraithInvisStateCountdown": "Invisibility will expire in {0}s", + "WraithInvisCooldownRemain": "{0}s left in invisibility", + "WerewolfKillButtonText": "Maul", + "BKInProtect": "Currently immortal", + "BKProtectOut": "Shield expired", + "BKSkillTimeRemain": "You're immune for {0} seconds", + "BKSkillNotice": "Kill a player to enter immune status", + "BKOffsetKill": "Someone tried killing you", + "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", + "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", + "FollowerBetPlayer": "You're now following your target", + "FollowerBetOnYou": "The Follower is now following you", + "CultistCharmedPlayer": "You successfully charmed a player", + "CharmedByCultist": "You have been charmed by the Cultist", + "CultistInvalidTarget": "Target cannot be charmed", + "KillBaitNotify": "You'll self-report in {0}s", + "InfectiousInvalidTarget": "Target cannot be infected", + "BittenByInfectious": "The Infectious infected you!", + "InfectiousBittenPlayer": "You successfully infected a player", + "GuessNotAllowed": "Sorry, your role does not have access to guessing.", + "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", + "GuessSpecter": "You can't guess a Specter. That allows them to win!", + "PacifistOnGuard": "Ability used, {0} uses remain", + "PacifistMaxUsage": "Ability use limit reached", + "PacifistSkillNotify": "Pacifist reset your kill cooldown", + "BeRecruitedByJackal": "The Jackal has recruited you", + "CoronerTrackRecorded": "Track recorded", + "CoronerNoTrack": "Nothing to track", + "CoronerIsTrackingYou": "The Coroner is tracking you!", + "CoronerReportButtonText": "Track", + "MerchantAddonDelivered": "Add-on sold", + "MerchantAddonSell": "The Merchant sold you a new Add-on", + "MerchantAddonSellFail": "Could not sell an Add-on", + "BribedByMerchant": "The Merchant bribed you. You can't kill him", + "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", + "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", + "TrapTrapsterBody": "Trap Trapster's body", + "TrapConsecutiveBodies": "Trap consecutive bodies", + "HauntedByEvilSpirit": "Haunted by an Evil Spirit", + "MonarchKnightCooldown": "Knight Cooldown", + "MonarchKnightMax": "Maximum Knights", + "HideAdditionalVotesForKnighted": "Hide additional vote for Knighted players", + "MonarchKnightedPlayer": "You successfully knighted a player!", + "KnightedByMonarch": "A Monarch has knighted you!", + "MonarchInvalidTarget": "Target cannot be knighted", + "GhostTransformTitle": "Your Role Has Transformed!", + "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", + "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", + "OverseerRevealCooldown": "Reveal Cooldown", + "OverseerRevealTime": "Reveal Time", + "OverseerVision": "Overseer Vision", + "MerchantMaxSell": "Max number of Add-ons to sell", + "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", + "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", + "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", + "MerchantTargetCrew": "Can sell to Crewmates", + "MerchantTargetImpostor": "Can sell to Impostors", + + "MerchantTargetNeutral": "Can sell to Neutrals", + "MerchantSellHelpful": "Can sell Helpful Add-ons", + "MerchantSellHarmful": "Can sell Harmful Add-ons", + "MerchantSellMixed": "Can sell Mixed Add-ons", + "MerchantSellExperimental": "Can sell experimental Add-ons", + "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", + "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", + "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", + + "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", + "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", + "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", + "SpiritcallerProtectTime": "Evil Spirit ability protect time", + "SpiritcallerCauseVision": "Evil Spirit ability caused vision", + "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", + "Message.SetToSeconds": "Set to [{0}] seconds.", + "Message.MessageWaitHelp": "Specify the first argument in seconds.", + "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", + "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", + "Message.SyncButtonLeft": "There are {0} more emergency buttons left", + "Message.Executed": "{0} was executed", + "Message.HideGameSettings": "The host has hidden the game settings.", + "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", + "Message.NoDescription": "No description", + "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", + "Message.BannedByBanList": "{0} was banned because they were banned in the past.", + "Message.BannedByEACList": "{0} has been banned because he is in the EAC list of Banned people.", + "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", + "Message.DumpcmdUsed": "{0} used /dump command.", + "Message.KickedByInvalidFriendCode": "{0} was kicked because their friend code is invalid.", + "Message.TempBannedByInvalidFriendCode": "{0} was temporarily banned because their friend code is invalid.", + "Message.AddedPlayerToBanList": "Added {0} to the ban list", + "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", + "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", + "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", + "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", + "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", + "Message.NoticeByEAC": "[{0}]Detected:{1}", + "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", + "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", + "Message.KickedByWhiteList": "{0} kicked because their friendcode was not found in WhiteList.txt", + "Message.SetLevel": "Your game level is set to: {0}", + "Message.SetColor": "Your color is set to: {0}", + "Message.SetName": "Your name is set to: {0}", + "Message.AllowLevelRange": "The game level can be set in the range: 0-100", + "Message.AllowNameLength": "Nickname can be set length: 1-10", + "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", + "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", + "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", + "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", + "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", + "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", + "Message.HostLeftGameInGame": "★Warning★ Host left the game, and the game wouldn't start normally next time. Please exit the lobby or wait until the new Host opens a lobby.", + "Message.HostLeftGameInLobby": "★Warning★ Host left the game, and the game wouldn't start normally next time. If the new Host has TOHE, you need to re-enter the lobby to play normally.", + "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, start a game and end it immediately to reset the lobby!", + "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please exit the lobby or wait until the new Host opens a lobby.", + "Message.LobbyShared": "The lobby has successfully been shared!", + "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", + "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", + "Message.YTPlanSelected": "In the next game, your role will be {0}", + "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", + "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. If the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", + "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", + "Message.MaxPlayers": "Maximum players set to ", + "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", + "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", + "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", + "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", + "Message.MeCommandNoPermission": "You are not allowed to use /me command for others", + + "PollTitle": "〖 Poll 〗", + "PollResultTitle": "Poll Results", + "Poll.Result": "And... The winner was {0} with {1} votes!\n\nRunner ups:", + "Poll.Tied": "Uh oh, The vote was tied between {0}, all having {1} votes.", + "Poll.MissingPlayers": "You can't start a poll with yourself dummy ;3", + + "Poll.Begin": "You may vote using /pv {answer}, ps: a number also works.", + "Poll.TimeInfo": "The results will be final in 2 minuttes", + "Poll.OnlyInLobby": "<#ab4f75>Sorry, this command may only be used in lobby", + + "Poll.Inactive": "There isn't any active poll currently.", + "Poll.AlreadyVoted": "You already cast your vote, so it won't be counted.", + + "Poll.VotingInfo": "Use /pv {answer} to vote, answer can be a character or a number.", + "Poll.YouVoted": "You have voted for {0}, which has now {1} votes.", + "PollUsage": "To create a poll type \n/poll {Question}? {Answer A} {Answer B} \n{Answer C (Optional)} {Answer D (Optional)} {Answer E (Optional)}\nIt is important you end the question with a ? \n\nUse /poll Replay to replay the latest poll", + "Replay": "Replay", + + "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", + "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", + "WarningTitle": "Warning!", + "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", + "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map, the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", + + "AntiBlackoutProtectionTitle": "Anti Blackout", + "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normally", + "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in the last meeting.\n", + "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", + + + + "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE are installed.", + "Warning.NoModHost": "TOHE is not installed on the host", + "Warning.MismatchedVersion": "{0} has a different version of {1}", + "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", + "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", + "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", + "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", + "Error.InvalidColor": "Error: Only default colors are available", + "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors. Otherwise, it will result in a serious error", + "ErrorLevel1": "Bugs may occur.", + "ErrorLevel2": "This may be a bug.", + "ErrorLevel3": "This version shouldn't have been released.", + "TerminateCommand": "Abort Command", + "ERR-000-000-0": "No Error", + "ERR-000-900-0": "Test Error Lv.0", + "ERR-000-910-1": "Test Error Lv.1", + "ERR-000-920-2": "Test Error Lv.2", + "ERR-000-930-3": "Test Error Lv.3", + "ERR-000-804-1": "Sorry, TOHE temporarily not support the Vanilla HnS, so mod unloaded", + "ERR-001-000-3": "Main dictionary has duplicated keys.", + "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", + "DefaultSystemMessageTitle": "SYSTEM MESSAGE", + "MessageFromTheHost": "HOST MESSAGE", + "MessageFromEAC": "EAC", + "DetectiveNoticeTitle": "INVESTIGATION", + "SleuthNoticeTitle": "SLEUTH", + "GuessKillTitle": "GUESSING INFO", + "CelebrityNewsTitle": "CELEBRITY", + "CyberNewsTitle": "CYBER", + "GodAliveTitle": "GOD ", + "WorkaholicAliveTitle": "WORKAHOLIC", + "BaitAliveTitle": "BAIT", + "MessageFromKPD": "KARPED1EM ", + "MessageFromSponsor": "SPONSOR MESSAGE ", + "MessageFromDev": "DEVELOPER MESSAGE ", + "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", + "MimicMsgTitle": "MIMIC", + "MorticianCheckTitle": "CORPSE EXAMINATION", + "NemesisRevengeTitle": "NEMESIS", + "RetributionistRevengeTitle": "RETRIBUTIONIST", + "TabVanilla.GameSettings": "Game Settings", + "TabGroup.SystemSettings": "System Settings", + "TabGroup.ModSettings": "Mod Settings", + "TabGroup.ModifierSettings": "Game Modifiers", + "TabGroup.CrewmateRoles": "Crewmate Roles", + "TabGroup.NeutralRoles": "Neutral Roles", + "TabGroup.ImpostorRoles": "Impostor Roles", + "TabGroup.Addons": "Add-Ons", + "TabMenuDescription_General": "Here you can configure the functions that are in the mod", + "TabMenuDescription_Roles&AddOns": "Here you can add, remove and change the settings of all roles or add-ons in the mod", + "Experimental.Roles": "★ Experimental Roles (NOTICE: Use with caution, as these require testing)", + "ActiveRolesList": "Active Roles List", + "ForExample": "Example Use", + "updateButton": "Update", + "updatePleaseWait": "Please Wait...", + "updateManually": "Update failed.\nPlease try again or Update Manually.", + "updateInProgress": "Updating...", + "deletingFiles": "Deleting update files...", + "updateRestart": "Update Finished!\nPlease restart the game.", + "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease Update.", + "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", + "UnsupportedVersion": "Unsupported Among Us version.\nPlease Update Among Us", + "DisabledByProgram": "The program has disabled public rooms", + "EnterVentToWin": "Enter Vent to Win!!", + "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", + "FireworkerPutPhase": "{0} Fireworker Left", + "FireworkerWaitPhase": "Wait for it...", + "FireworkerReadyFirePhase": "Fire!", + "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", + "On": "ON", + "Off": "OFF", + "ColoredOn": "ON", + "ColoredOff": "OFF", + "CurrentActiveSettingsHelp": "Current Active Settings Help", + "WitchCurrentMode": "Current Mode", + "WitchModeKill": "Kill", + "WitchModeSpell": "Spell", + "HexMasterModeHex": "Hex", + "HexMasterModeKill": "Kill", + "PoisonerPoisonButtonText": "Poison", + "WitchModeDouble": "Double Click = Kill, Single Click = Spell", + "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", + "BountyCurrentTarget": "Current Target", + "Roles": "Roles", + "Settings": "Settings", + "Addons": "Add-Ons", + "LastResult": "★ Match Results", + "LastEndReason": "★ End Reason", + "KillLog": "Kill Log", + "Maximum": "Max", + "RoleRate": "ON", + "RoleOn": "ALWAYS", + "RoleOff": "OFF", + "Chance0": "0%", + "Chance5": "5%", + "Chance10": "10%", + "Chance15": "15%", + "Chance20": "20%", + "Chance25": "25%", + "Chance30": "30%", + "Chance35": "35%", + "Chance40": "40%", + "Chance45": "45%", + "Chance50": "50%", + "Chance55": "55%", + "Chance60": "60%", + "Chance65": "65%", + "Chance70": "70%", + "Chance75": "75%", + "Chance80": "80%", + "Chance85": "85%", + "Chance90": "90%", + "Chance95": "95%", + "Chance100": "100%", + "Preset": "Preset", + "Preset_1": "Preset 1", + "Preset_2": "Preset 2", + "Preset_3": "Preset 3", + "Preset_4": "Preset 4", + "Preset_5": "Preset 5", + "Standard": "Standard", + "GameMode": "Game Mode", + "PressTabToNextPage": "Press Tab or Number for Next Page...", + "RoleSummaryText": "Role Summary:", + "doOverride": "Override %role%'s Tasks", + "assignCommonTasks": "%role% has Common Tasks", + "roleLongTasksNum": "Amount of Long Tasks for %role%", + "roleShortTasksNum": "Amount of Short Tasks for %role%", + "Format.Players": "{0}", + "Format.Seconds": "{0}s", + "Format.Percent": "{0}%", + "Format.Times": "{0}", + "Format.Multiplier": "{0}x", + "Format.Votes": "{0}", + "Format.Pieces": "{0}", + "Format.Health": "{0}", + "Format.Level": "{0}", + "KillButtonText": "Kill", + "ReportButtonText": "Report", + "VentButtonText": "Vent", + "SabotageButtonText": "Sabotage", + "SniperSnipeButtonText": "Snipe", + "FireworkerExplosionButtonText": "Detonate", + "FireworkerInstallAtionButtonText": "Install", + "MercenarySuicideButtonText": "Suicide Timer", + "WarlockCurseButtonText": "Curse", + "NinjaShapeshiftText": "Kill", + "NinjaMarkButtonText": "Mark", + "WitchSpellButtonText": "Spell", + "VampireBiteButtonText": "Bite", + "MinerTeleButtonText": "Warp", + "ArsonistDouseButtonText": "Douse", + "PuppeteerOperateButtonText": "Manipulate", + "WarlockShapeshiftButtonText": "Spell", + "BountyHunterChangeButtonText": "Swap", + "EvilTrackerChangeButtonText": "Track", + "InnocentButtonText": "Frame", + "PelicanButtonText": "Eat", + "DeceiverButtonText": "Cheat", + "PursuerButtonText": "Trick", + "GangsterButtonText": "Recruit", + "RevolutionistDrawButtonText": "Win over", + "HaterButtonText": "Hatred", + "MedicalerButtonText": "Protect", + "DemonButtonText": "Attack", + "SoulCatcherButtonText": "Teleport", + "LightningButtonText": "Evaporate", + "ProvocateurButtonText": "Greet", + "ButcherButtonText": "Dismember", + "BomberShapeshiftText": "Explode", + "QuickShooterShapeshiftText": "Keep", + "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", + "CamouflagerShapeshiftTextAfterDisguise": "Duration", + "AnonymousShapeshiftText": "Hack", + "DefaultShapeshiftText": "Shift", + "CleanerReportButtonText": "Clean", + "SwooperVentButtonText": "Swoop", + "SwooperRevertVentButtonText": "Expose", + "WraithVentButtonText": "Vanish", + "WraithRevertVentButtonText": "Expose", + "VectorVentButtonText": "Hop", + "VeteranVentButtonText": "Alert", + "GrenadierVentButtonText": "Flash", + "MayorVentButtonText": "Button", + "SheriffKillButtonText": "Shoot", + "UndertakerButtonText": "Mark", + "ArsonistVentButtonText": "Ignite", + "RevolutionistVentButtonText": "Revolution", + "FollowerKillButtonText": "Follow", + "PacifistVentButtonText": "Reset", + "CultistKillButtonText": "Charm", + "InfectiousKillButtonText": "Infect", + "MonarchKillButtonText": "Knight", + "OverseerKillButtonText": "Reveal", + "DisabledBySettings": "Disabled by Settings", + "Disabled": "Disabled", + "FailToTrack": "Failed To Track", + "KillCount": "Kills: {0}", + "CantUse.lastroles": "Unable to use /lastroles during a game.", + "CantUse.killlog": "Unable to use /killlog during a game.", + "CantUse.lastresult": "Unable to use /lastresult during a game.", + "IllegalColor": "Please enter the correct color", + "DisableUseCommand": "The Host's settings do not allow this command to be used.", + "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", + "PlayerIdList": "List of player IDs: ", + "CancelStartCountDown": "The starting countdown was canceled", + "RestTOHESetting": "TOHE settings have been restored to default", + "FPSSetTo": "FPS Set To: {0}", + "HostKillSelfByCommand": "The lobby Host decided to commit suicide", + "SyncCustomSettingsRPC": "Synchronized RPC", + "Mode": "Mode", + "Target": "Target", + "PlayerInfo": "Player Info", + "NoInfoExists": "No Info Exists", + "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", + "PlayerLeftByError": "Game will auto-end to prevent black screens.", + "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", + "KickBecauseLowLevel": "{0} was kicked because their level was too low", + "TempBannedBecauseLowLevel": "{0} was temporarily banned because their level was too low", + "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", + + "FFADisplayScore": "Ranking: {0} Score: {1}", + "FFATimeRemain": "Time Remaining: {0} second(s)", + + "GameOver": "Game Over", + "TOHEOptions": "TOHE Options", + "Cancel": "Cancel", + "Back": "Back", + "Yes": "Yes", + "No": "No", + + "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent a black screen.", + "AntiBlackOutNotifyInLobby": "An error occurred to prevent a black screen. Do a «/dump» and send the logs to the discord server TOHE in «bug-reports» and we will try to fix it.", + + "EndWhenPlayerBug": "End the game when a modded player gets a critical error (While loading)", + "AntiBlackOutRequestHostToForceEnd": "You were the reason for the black screen. The game will end", + "AntiBlackOutHostRejectForceEnd": "You were the reason for the black screen, and the host is not going to end the game\nYou will be disconnected soon", + + "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred. To prevent a black screen, turn off [{1}] in settings.", + "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, the game will end to prevent a black screen.", + "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", + + "NextPage": "Next Page", + "PreviousPage": "Previous Page", + "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", + "PressF1ShowMainRoleDes": "Press F1: Show Role Description", + "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", + "PressF3ShowRoleSettings": "Press F3: Show Role Settings", + "PressF4ShowAddOnsSettings": "Press F4: Show Add-ons Settings", + "FakeTask": "Fake Tasks:", + "PVP.ATK": "Attack", + "PVP.DF": "Defend", + "PVP.RCO": "Recover", + "SettingsAreLoading": "Loading\nsettings...", + "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", + "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", + "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", + "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", + "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", + "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", + "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", + "IsGood": "{0} was a good guy", + "BelongTo": "{0} belongs to {1}", + "PlayerIsRole": "{0} was The {1}", + "PlayerExiled": "{0} was ejected", + "NoImpRemain": "0 Impostors remain", + "OneImpRemain": "1 Impostor remains", + "TwoImpRemain": "2 Impostors remain", + "ThreeImpRemain": "3 Impostors remain", + "ImpRemain": "{0} Impostors remaining", + "NeutralRemain": "\n{0} Neutral Killers remain", + "OneNeutralRemain": "\n{0} Neutral Killer remains", + "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", + "GameOverReason.HumansByTask": "The Crewmates completed all tasks", + "GameOverReason.HumansDisconnect": "Crewmates disconnected", + "GameOverReason.ImpostorByVote": "The Crewmates were ejected", + "GameOverReason.ImpostorByKill": "The Impostors killed everyone", + "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", + "GameOverReason.ImpostorDisconnect": "Impostors disconnected", + "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", + "DevAndSpnTitle": "TOHE family", + "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", + "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", + "SunnyboyChance": "Sunnyboy Chance", + "BardChance": "Bard Chance", + "SkeldChance": "Chance that the map is The Skeld", + "MiraChance": "Chance that the map is MIRA HQ", + "PolusChance": "Chance that the map is Polus", + "DleksChance": "Chance that the map is dlekS ehT", + "AirshipChance": "Chance that the map is Airship", + "FungleChance": "Chance that the map is The Fungle", + "UseMoreRandomMapSelection": "Use a more random map selection", + "CamouflageMode.Default": "Default", + "CamouflageMode.Host": "Host", + "CamouflageMode.Random": "Random", + "CamouflageMode.OnlyRandomColor": "Only Random Color", + "CamouflageMode.Karpe": "KARPED1EM", + "CamouflageMode.Lauryn": "Lauryn", + "CamouflageMode.Moe": "Moe", + "CamouflageMode.Pyro": "Pyro", + "CamouflageMode.ryuk": "ryuk", + "CamouflageMode.Gurge44": "Gurge44", + "CamouflageMode.TommyXL": "TommyXL", + "CamouflageMode.Sarha": "Sarha", + "DeathCmd.HeyPlayer": "Hey ", + "DeathCmd.YouAreRole": ", looks like you're the ", + "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", + "DeathCmd.KillerName": "You were killed by ", + "DeathCmd.KillerRole": "Their role is ", + "DeathCmd.DeathReason": "Your cause of death was ", + "DeathCmd.YourName": "You are ", + "DeathCmd.YourRole": "Your role is ", + "DeathCmd.Ejected": "You were ejected during a meeting", + "DeathCmd.Misfired": "You misfired.", + "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", + "DeathCmd.Lovers": "Your lover had died.", + + "RpsCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", + "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", + "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", + "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I have the world's worst luck algorithm.", + + "CoinFlipCommandInfo": "This Command can only be used when in the lobby or after you die.", + "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", + + "GNoCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", + "GNoLost": "Oh, you were so close! Just one more guess: you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", + "GNoLow": "Oh, you're really nailing this! It's so low. I almost need a shovel to dig it up!\nYou have {0} guesses left!", + "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high that I need a telescope to see it from here! \nYou have {0} guesses left!", + "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", + + "RandCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", + "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", + + "8BallTitle": "The Magic 8 Ball Reveals...", + "8BallYes": "Yes", + "8BallNo": "No", + "8BallMaybe": "Maybe", + "8BallTryAgainLater": "Ask again later", + "8BallCertain": "It is certain", + "8BallNotLikely": "Outlook not so good", + "8BallLikely": "Outlook good", + "8BallDontCount": "Don't count on it", + "8BallStop": "Stop using an 8Ball in an Among Us mod", + "8BallPossibly": "Possibly", + "8BallProbably": "Probably", + "8BallProbablyNot": "Probably not", + "8BallBetterNotTell": "Better not tell you now", + "8BallCantPredict": "Cannot predict now", + "8BallWithoutDoubt": "Without a doubt", + "8BallWithDoubt": "Very doubtful", + + "ChanceToMiss": "Chance to miss a kill", + + "SoulCollectorPointsToWin": "Required number of souls", + "SoulCollectorTarget": "You have predicted the death of {0}", + "SoulCollectorTitle": "SOUL COLLECTOR", + "SoulCollector_CollectOwnSoulOpt": "Can collect their own soul", + "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", + "SoulCollectorToDeath": "You have become Death!!!", + "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", + "GetPassiveSouls": "Gain a passive soul every round", + "PassiveSoulGained": "You have gained a passive soul from the underworld.", + "SoulCollectorTargetUsed": "You've already targeted someone this round!", + "SoulCollectorSoulGained": "Soul gained", + "SoulCollectorCanVent": "Soul Collector can Vent", + "DeathMeetingTimeIncrease": "Increased Meeting time when Death exists", + "SoulCollectorMeetingDeath": "Your target has died during the meeting. You have gained a soul.", + "SoulCollectorKillButtonText": "Predict", + + "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", + "ApocalypseImmune": "This player is immune because they are invincible!", + "BakerToFamine": "You have become Famine!!!", + "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", + "BakerAlreadyBreaded": "That player already has bread!", + "BakerBreadUsedAlready": "You've already given a player bread this round!", + "BakerBreaded": "Player given bread", + "BakerBreadNeededToTransform": "Required number of bread to become Famine", + "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", + "BakerKillButtonText": "Bread", + "BakerRevealBread": "Reveal", + "BakerRoleblockBread": "Roleblock", + "BakerBarrierBread": "Barrier", + "BakerCurrentBread": "Current Bread: ", + "BakerSwitchBread": "Bread Switched to: ", + "BakerCanVent": "Baker can Vent", + "BakerBreadGivesEffects": "Bread gives additional effects", + "FamineKillButtonText": "Starve", + "FamineStarveCooldown": "Famine starve cooldown", + "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", + "FamineAlreadyStarved": "That player has already been starved!", + "FamineStarved": "Player starved", + + "ChronomancerKillCooldown": "Ability Charge Time", + "ChronomancerDecreaseTime": "Slaughter Decrease Time (lower is faster)", + "ChronomancerStartMassacre": "SLAUGHTER: ACTIVATED", + "ChronomancerVisionMassacre": "Vision When In Slaughter", + + "ShamanButtonText": "Voodoo", + "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", + "Shaman_KillerCannotMurderChosenTarget": "The killer cannot murder chosen target", + "VoodooCooldown": "Voodoo Cooldown", + + "AdminWarning": "Admin Table in use!", + "VitalsWarning": "Vitals in use!", + "DoorlogWarning": "Doorlogs in use!", + "CameraWarning": "Cameras in use!", + "MinWaitAutoStart": "Minutes to wait before auto-starting", + "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", + "PlayerAutoStart": "Minimum Player Threshold to auto-start", + "AutoStartTimer": "Initial countdown for auto-starting", + "ImmediateAutoStart": "Immediately start the game when reaching certain conditions", + "ImmediateStartTimer": "Initial countdown for Immediate-starting", + "StartWhenPlayersReach": "Immediately Start when we have enough players above", + "StartWhenTimerLowerThan": "Immediately Start when Lobby Timer goes below", + "AutoPlayAgainCountdown": "Delay before re-entering lobby", + "AutoPlayAgain": "Auto Play Again", + "AutoRehost": "Auto Re-Host on Bad Disconnect", + "CountdownText": "Rejoining lobby in {0}s", + "TimeMasterSkillDuration": "Time Shield Duration", + "TimeMasterSkillCooldown": "Time Shield Cooldown", + "TimeMasterOnGuard": "Time Shield is active!", + "TimeMasterSkillStop": "Time Shield has ended!", + "TimeMasterVentButtonText": "Time Shield", + "BodyCannotBeReported": "Body could not be reported", + "BurstKillDelay": "Burst Kill Delay", + "BurstNotify": "That was a Burst! Get in a vent or die.", + "ImpCanBeBurst": "Impostors can become Burst", + "CrewCanBeBurst": "Crewmates can become Burst", + "NeutralCanBeBurst": "Neutrals can become Burst", + "BurstFailed": "Burst failed to bomb you", + "ShroudButtonText": "Shroud", + "ShroudCooldown": "Shroud Cooldown", + "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", + "LudopathRandomKillCD": "Maximum kill cooldown", + "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", + "GodfatherTargetCountMode": "Killer turns into", + "GodfatherCount_Refugee": "Refugee", + "GodfatherCount_Madmate": "Madmate", + "MissChance": "Chance To Miss", + "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", + "HawkMissed": "Missed!", + "HawkCanKillNum": "Max Slices", + "HawkKillMax": "You've run out of ability uses", + "HawkKillTooManyDead": "Too many people are dead", + "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", + "BloodMoonCanKillNum": "Max BloodLettings", + "BloodMoonTimeTilDie": "Time Until Death", + "DeathTimer": "Death In: {DeathTimer}s", + "BerserkerKillCooldown": "Berserker kill cooldown", + "BerserkerMax": "Max level that Berserker can reach", + "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", + "WarHasImpostorVision": "War Has Impostor Vision", + "BerserkerCanVent": "Berserker Can Vent", + "WarCanVent": "War Can Vent", + "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", + "BerserkerOneKillCooldown": "Kill cooldown after unlocking", + "BerserkerTwoCanScavenger": "Unlock scavenged kills", + "BerserkerThreeCanBomber": "Unlock bombed kills", + "BerserkerFourCanNotKill": "Become War", + "BerserkerMaxReached": "Maximum level reached!", + "BerserkerLevelChanged": "Increased level to {0}", + "BerserkerLevelRequirement": "Level requirement for unlock", + "KilledByBerserker": "Killed by Berserker", + "BerserkerToWar": "You have become War!!!", + "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", + "WarKillCooldown": "War kill cooldown", + + "ImpCanBeUnlucky": "Impostors can become Unlucky", + "CrewCanBeUnlucky": "Crewmates can become Unlucky", + "NeutralCanBeUnlucky": "Neutrals can become Unlucky", + "BlackmailerSkillCooldown": "Blackmail Cooldown", + "BlackmailerMax": "Maximum times blackmailed players may speak", + "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", + "BlackmaileKillTitle": "BLACKMAILER", + "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", + "UnluckyKillSuicideChance": "Chance to suicide from killing", + "UnluckyVentSuicideChance": "Chance to suicide from venting", + "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", + "UnluckyOpenDoorSuicideChance": "Chance to suicide from opening a door", + "ImpCanBeVoidBallot": "Impostors can become VoidBallot", + "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", + "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", + "ImpCanBeAware": "Impostors can become Aware", + "NeutralCanBeAware": "Neutrals can become Aware", + "CrewCanBeAware": "Crewmates can become Aware", + "AwareKnowRole": "Knows the role of the player", + "AwareInteracted": "{0} tried to reveal your role.", + "AwareTitle": "AWARE MESSAGE", + "LighterVentButtonText": "Light", + "LighterSkillCooldown": "Light Cooldown", + "LighterSkillDuration": "Light Duration", + "LighterVisionNormal": "Increased Vision", + "LighterVisionOnLightsOut": "Increased Vision During Lights Out", + "LighterSkillInUse": "Ability in use", + "LighterSkillStop": "Ability expired", + "StealthDarkened": "Darkened: {0}", + "StealthExcludeImpostors": "Ignore Impostors when Blinding", + "StealthDarkenDuration": "Blinding Duration", + "PenguinAbductTimerLimit": "Dragging Time", + "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", + "PenguinKillButtonText": "Drag", + "PenguinTimerText": "Drag Timer", + "PenguinTargetOnCheckMurder": "You are grabbed. Try to escape that first!", + "WitnessTime": "Max Time after killing where killer appears red", + "WitnessButtonText": "Examine", + "WitnessFoundInnocent": "✓", + "WitnessFoundKiller": "⚠", + "SwapperMax": "Maximum swaps", + "CanSwapSelfVotes": "Can exchange your own votes.", + "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", + "CantSwapSelf": "Can't exchange of one's own vote", + "SwapVote": "The votes of {0} and {1} were swapped!", + "SwapDead": "Sorry, you can't swap votes after death.", + "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", + "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", + "Swap1": "Swap target 1 selected", + "Swap2": "Swap target 2 selected", + "CancelSwap": "Cleared your previous swap!", + "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your targets is dead.", + "Swap1=Swap2": "The target you input is the same as Swap target 1.\nPls input a different one", + "SwapTitle": "SWAPPER", + "SwapperTryHideMsg": "Try to hide Swapper's command", + "SwapperPreResult": "Currently, you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", + "ImpCanBeFragile": "Impostors can become Fragile", + "NeutralCanBeFragile": "Neutrals can become Fragile", + "CrewCanBeFragile": "Crewmates can become Fragile", + "ImpCanKillFragile": "Impostors can force kill Fragile", + "NeutralCanKillFragile": "Neutrals can force kill Fragile", + "CrewCanKillFragile": "Crewmates can force kill Fragile", + "FragileKillerLunge": "Killer lunges on kill", + "CrusaderSkillLimit": "Maximum Crusades", + "CrusaderSkillCooldown": "Crusade Cooldown", + "CrusaderKillButtonText": "Crusade", + "JailorKillButtonText": "Jail", + "AgitaterKillButtonText": "Pass", + "HasSerialKillerBuddy": "Has Serial Killer buddy", + "ChanceToSpawn": "Chance to spawn", + "ChanceToSpawnAnother": "Chance to spawn another", + "BloodthirstKillCD": "Bloodthirst Kill Cooldown", + "BloodthirstPlayerCount": "Max players alive for Bloodthirst", + "ReflectHarmfulInteractions": "Reflect harmful interactions", + + "ImpCanBeDiseased": "Impostors can become Diseased", + "NeutralCanBeDiseased": "Neutrals can become Diseased", + "CrewCanBeDiseased": "Crewmates can become Diseased", + "DiseasedCDOpt": "Increase the cooldown by", + "DiseasedCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeAntidote": "Impostors can become Antidote", + "NeutralCanBeAntidote": "Neutrals can become Antidote", + "CrewCanBeAntidote": "Crewmates can become Antidote", + "AntidoteCDOpt": "Decrease the cooldown by", + "AntidoteCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeRadar": "Impostors can become Radar", + "NeutralCanBeRadar": "Neutrals can become Radar", + "CrewCanBeRadar": "Crewmates can become Radar", + + "ImpCanBeGlow": "Impostors can become Glow", + "NeutralCanBeGlow": "Neutrals can become Glow", + "CrewCanBeGlow": "Crewmates can become Glow", + "GlowRadius": "Glow Radius", + "GlowVisionOthers": "Vision Boost for nearby Players", + "GlowVisionSelf": "Vision Boost for Glow", + + "ImpCanBeStubborn": "Impostors can become Stubborn", + "NeutralCanBeStubborn": "Neutrals can become Stubborn", + "CrewCanBeStubborn": "Crewmates can become Stubborn", + + "ImpCanBeAvanger": "Impostors can become Avenger", + "NeutralCanBeAvanger": "Neutrals can become Avenger", + "CrewCanBeAvanger": "Crewmates can become Avenger", + "ImpCanBeSleuth": "Impostors can become Sleuth", + "CrewCanBeSleuth": "Crewmates can become Sleuth", + "NeutralCanBeSleuth": "Neutrals can become Sleuth", + "SleuthCanKnowKillerRole": "Can find the role of the killer", + "SleuthNoticeKiller": "\nThe killer's role is {0}.", + "SleuthNoticeVictim": "{0}'s role is {1}.", + "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", + "BomberDiesInExplosion": "Bomber dies in their explosion", + "ImpostorsSurviveBombs": "Impostors survive bombs", + + "PunchingBagKillMax": "Amount of attacks needed to win", + "GuessPunchingBag": "You just tried to guess a Punching Bag!\nThey're now one step closer to winning!", + "GuessPunchingBagAgain": "You just tried to guess a Punching Bag again!\n\nIt no longer counts your attacks by guessing", + "PunchingBagKill": "You were attacked!", + "SelfGuessPunchingBag": "You can't self-guess as a Punching Bag, you cheater!", + "GuessPunchingBagBlocked": "Punching Bag cannot guess due to self-guessing.", + "EradicatePunchingBag": "You just tried to terminate punching bag, that is not allowed.", + + "RememberCooldown": "Imitate Cooldown", + "RefugeeKillCD": "Refugee's Kill Cooldown", + "RememberedNeutralKiller": "You remembered you were a neutral killer!", + "RememberedMaverick": "You remembered you were a Maverick!", + "RememberedPursuer": "You remembered you were a Pursuer!", + "RememberedFollower": "You remembered you were a Follower!", + "RememberedAmnesiac": "You failed to remember your role.", + "RememberedImitator": "You remembered you were an Imitator.", + "RememberedImpostor": "You remembered you were an Impostor!", + "RememberedCrewmate": "You remembered you were a crewmate!", + "ImitatorImitated": "An Imitator imitated your role!", + "ImitatorInvalidTarget": "Imitation failed", + "RememberButtonText": "Remember", + "ImitatorKillButtonText": "Imitate", + "IncompatibleNeutralMode": "If neutral is incompatible, turn into", + "RememberedYourRole": "An Amnesiac remembered your role!", + "YouRememberedRole": "You remembered who you were!", + + "BanditStealMode": "Steal Mode", + "BanditStealMode_OnMeeting": "On Meeting", + "BanditStealMode_Instantly": "Instantly", + "BanditMaxSteals": "Maximum Steals", + "BanditCanStealBetrayalAddon": "Can Steal Betrayal Add-ons", + "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", + "Bandit_NoStealableAddons": "Could not steal add-on from the player", + "BanditStealCooldown": "Steal cooldown", + + "DoppelMaxSteals": "Maximum Steals", + "DoppelCurrentVictimCanSeeRolesAsDead": "Last victim can see role and add-on info of alive players as a ghost", + + "NecromancerRevengeTime": "Necromancy time", + "NecromancerRevenge": "You have {0}s to kill {1}", + "NecromancerSuccess": "Necromancy complete! You live to see another day.", + "NecromancerHide": "Venting is disabled, hide from the Necromancer!", + "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", + "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", + "RetributionistKillMax": "You've reached the maximum amount of kills. You can't kill anymore!", + "RetributionistKillDead": "Choose a living player to kill.", + "RetributionistKillSucceed": "{0} was killed by the Retributionist!", + "RetributionistKillDisable": "You can't retribute until your tasks are done.", + "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", + "RetributionistCanKillNum": "Max retributions", + "RetributionistKillTooManyDead": "Too many players are dead. You can't retribute.", + "MinimumPlayersAliveToRetri": "Minimum players alive to retribute", + "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", + "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", + + "TwisterCooldown": "Twist Cooldown", + "TwisterButtonText": "Twist", + "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", + "InstigatorAbilityLimit": "Ability Use Count", + "InstigatorKillsPerAbilityUse": "Kills per Ability use", + + "CrewCanFindCaptain": "Crewmates can find Captain", + "MadmateCanFindCaptain": "Madmates can find Captain", + "ReducedSpeed": "Reduced speed", + "ReducedSpeedTime": "Time duration for reduced speed", + "CaptainCanTargetNB": "Captain can target Neutral Benign", + "CaptainCanTargetNE": "Captain can target Neutral Evil", + "CaptainCanTargetNC": "Captain can target Neutral Chaos", + "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", + "CaptainCanTargetNK": "Captain can target Neutral Killer", + "CaptainSpeedReduced": "Captain reduced your speed", + "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", + "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", + + "InspectorTryHideMsg": "Hide Inspector's commands", + "MaxInspectCheckLimit": "Max inspections per game", + "InspectCheckLimitPerMeeting": "Max inspections per meeting", + "InspectCheckTargetKnow": "Targets know they were checked by Inspector", + "InspectCheckOtherTargetKnow": "Targets know who they were checked with", + "InspectorDead": "You can not use your power after death", + "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", + "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", + "InspectCheckSelf": "HA!! You thought it would be this easy. You can not check yourself", + "InspectCheckReveal": "HA! You thought it would be this easy. You can not check a role that is revealed", + "InspectCheckTitle": "INSPECTOR ", + "InspectCheckTrue": "{0} and {1} are in the same team!", + "InspectCheckFalse": "{0} and {1} are NOT in the same team!", + "InspectCheckTargetMsg": " were checked by Inspector.", + "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "InspectCheckNull": "Please select an ID of a living player to check their team", + "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", + "InspectCheckRevealTarget": "When tasks are done, the target knows the team of the other target", + "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", + + "EgoistCountMode.Original": "Original", + "EgoistCountMode.Neutral": "Neutral", + + "JailerJailCooldown": "Jail cooldown", + "JailerMaxExecution": "Maximum executions", + "JailerNBCanBeExe": "Can execute Neutral Benign", + "JailerNCCanBeExe": "Can execute Neutral Chaos", + "JailerNECanBeExe": "Can execute Neutral Evil", + "JailerNKCanBeExe": "Can execute Neutral Killing", + "JailerNACanBeExe": "Can execute Neutral Apocalypse", + "JailerCKCanBeExe": "Can execute Crew Killing", + "JailerTargetAlreadySelected": "You have already selected a target", + "SuccessfullyJailed": "Target successfully jailed", + "CantGuessJailed": "You can not guess the target", + "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", + "CanNotTrialJailed": "You can not trial the target.", + "notifyJailedOnMeeting": "Notify jailed player when a meeting starts", + "JailedNotifyMsg": "The Jailer has jailed you. No one can guess or judge you. You can only guess The Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", + "JailerTitle": "Jailer", + + "CopyCatCopyCooldown": "Copy cooldown", + "CopyCatRoleChange": "Your role has been changed to {0}", + "CopyCatCanNotCopy": "You can not copy the target's role", + "CopyButtonText": "Copy", + "CopyCrewVar": "Can copy evil variants of crew roles", + "CopyTeamChangingAddon": "Can copy team changing add-on", + + "MaxCleanserUses": "Max cleanses", + "CleansedCanGetAddon": "Cleansed player can get Add-on", + "CleanserTitle": "CLEANSER", + "CleanserRemoveSelf": "You can not cleanse yourself", + "CleanserCantRemove": "Oops! the player can not be cleansed.", + "CleanserRemovedRole": "{0} has been cleansed. All their Add-ons will be removed after the meeting.", + "LostAddonByCleanser": "The cleanser removed all your Add-ons", + + "MaxProtections": "Max protections", + "KeeperHideVote": "Hide Keeper's vote", + "KeeperProtect": "You chose to protect {0}, your vote has been returned", + "KeeperTitle": "Keeper", + + "MaulRadius": "Maul Radius", + "ImpCanBeAutopsy": "Impostors can become Autopsy", + "CrewCanBeAutopsy": "Crewmates can become Autopsy", + "NeutralCanBeAutopsy": "Neutrals can become Autopsy", + "ImpCanBeCyber": "Impostors can become Cyber", + "CrewCanBeCyber": "Crewmates can become Cyber", + "NeutralCanBeCyber": "Neutrals can become Cyber", + "ImpKnowCyberDead": "Impostors know if Cyber died", + "CrewKnowCyberDead": "Crewmates know if Cyber died", + "NeutralKnowCyberDead": "Neutrals know if Cyber died", + "CyberKnown": "Everyone can see Cyber", + "ImpCanBeInfluenced": "Impostors can become Influenced", + "CrewCanBeInfluenced": "Crewmates can become Influenced", + "NeutralCanBeInfluenced": "Neutrals can become Influenced", + "ImpCanBeBewilder": "Impostors can become Bewilder", + "CrewCanBeBewilder": "Crewmates can become Bewilder", + "NeutralCanBeBewilder": "Neutrals can become Bewilder", + "KillerGetBewilderVision": "Killer gets Bewilder's vision", + "ImpCanBeOiiai": "Impostors can be OIIAI", + "CrewCanBeOiiai": "Crewmates can be OIIAI", + "NeutralCanBeOiiai": "Neutrals can be OIIAI", + "OiiaiCanPassOn": "OIIAI can pass on to the killer", + "NeutralChangeRolesForOiiai": "Neutrals turns to ", + "LostRoleByOiiai": "You got erased by OIIAI!", + "ImpCanBeLoyal": "Impostors can become Loyal", + "CrewCanBeLoyal": "Crewmates can become Loyal", + "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", + "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", + "SheriffCanBeMadmate": "Sheriff can become Madmate", + "MayorCanBeMadmate": "Mayor can become Madmate", + "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", + "SnitchCanBeMadmate": "Snitch can become Madmate", + "JudgeCanBeMadmate": "Judge can become Madmate", + "MarshallCanBeMadmate": "Marshall can become Madmate", + "GanRetributionistCanBeMadmate": "Retributionist can be converted", + "RetributionistCanBeMadmate": "Retributionist can become Madmate", + "OverseerCanBeMadmate": "Overseer can become Madmate", + "GanSheriffCanBeMadmate": "Sheriff can be converted", + "GanMayorCanBeMadmate": "Mayor can be converted", + "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", + "GanJudgeCanBeMadmate": "Judge can be converted", + "GanMarshallCanBeMadmate": "Marshall can be converted", + "GanOverseerCanBeMadmate": "Overseer can be converted", + "RascalAppearAsMadmate": "Appear As Madmate On Ejection", + + "CouncillorDead": "Sorry, you can't murder from the dead.", + "CouncillorMurderMaxMeeting": "Sorry, you've reached the maximum amount of murders for the meeting.", + "CouncillorMurderMaxGame": "Sorry, you've reached the maximum amount of murders for the game.", + "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", + "Councillor_MurderKill": "{0} was murdered.", + "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Councillor_MurderNull": "Please choose a living player to murder.", + "Councillor_MurderKillTitle": "WICKED COURT ", + "CouncillorMakeEvilJudgeClear": "Show Trial as Councillor Murder", + "Councillor_CannotMurderImpTeam": "Sorry, you can not murder your teammate.", + "Councillor_SuicideForMurderImps": "You died because you are trying to murder your team members.", + "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", + "CouncillorMurderLimitPerGame": "Maximum Kills Per Game", + "CouncillorCanMurderMadmate": "Can Murder Madmates", + "CouncillorCanMurderImpostor": "Can Murder Impostors", + "CouncillorSuicideOnJudgeImpTeam": "Suicide when judge Impostors Team Wrongly", + "CouncillorCanMurderTaskDoneSnitch": "Can Murder Snitch with All Tasks Done", + "CouncillorTryHideMsg": "Try to hide Councillor's commands", + + "DazzlerDazzled": "You were dazzled by the Dazzler!", + "DazzlerCauseVision": "Reduced vision", + "DazzlerDazzleLimit": "Max number of players affected by reduced vision", + "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", + "DazzleCooldown": "Dazzle Cooldown", + "DazzleButtonText": "Dazzle", + + "MoleVentButtonText": "Dig", + "MoleVentCooldown": "Dig cooldown", + + "AddictVentButtonText": "Get Fix", + "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", + "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerable", + + "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", + "AlchemistShieldDur": "Resistance Potion Duration", + "AlchemistInvisDur": "Invisibility Potion Duration", + "AlchemistVision": "Night Vision", + "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", + "AlchemistVisionDur": "Night Vision Potion Duration", + "AlchemistSpeed": "Speed Potion Boost", + "AlchemistVentButtonText": "Drink", + "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", + "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", + "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", + "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", + "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", + "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", + "AlchemistGotBloodthirstPotion": "Potion of Harming: Kill the next player you touch", + "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", + "NoPotion": "You have no potions", + + "StoreShield": "Potion of Resistance", + "StoreSuicide": "Potion of Poison", + "StoreTP": "Potion of Warping", + "StoreSP": "Potion Of Speed", + "StoreQF": "Potion of Fixing", + "StoreBL": "Potion of Harming", + "StoreNS": "Potion of Night Vision", + "StoreINV": "Potion of Invisibility", + "StoreNull": "None", + "PotionStore": "Potion in store: ", + "WaitQFPotion": "\nPotion of Fixing waiting for use", + + "AlchemistShielded": "Potion of Resistance started", + "AlchemistHasVision": "Potion of Night Vision started", + "AlchemistShieldOut": "Potion of Resistance ended", + "AlchemistVisionOut": "Potion of Night Vision ended", + "AlchemistPotionBloodthirst": "You gained bloodthirst", + "AlchemistHasSpeed": "Potion Of Speed started", + "AlchemistSpeedOut": "Potion Of Speed ended", + + "DeathpactDuration": "Death Pact duration", + "DeathPactCooldown": "Death Pact Assign Cooldown", + "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", + "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", + "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", + "DeathpactVisionWhileInPact": "Vision for players in Death Pact", + "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", + "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", + "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", + "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", + "DeathpactComplete": "Death Pact was concluded.", + "DeathpactExecuted": "Death Pact was executed.", + "DeathpactAverted": "Death Pact was averted.", + "DeathpactButtonText": "Assign", + "DevourerHideNameConsumed": "Hide the names of consumed players", + "DevourCooldown": "Devour Cooldown", + "DevourerButtonText": "Devour", + "DollMasterPossessionButtonText": "Possess", + "DollMasterUnPossessionButtonText": "UnPossess", + "DollMaster_PossessedTarget": "Possessed target", + "DollMaster_CannotPossessImpTeammate": "Unable to possess teammate", + "DollMaster_CouldNotSwapWithTarget": "Unable to possess player", + "DollMaster_CanNotSwapWithDeadTarget": "Possesing a dead player isn't possible", + "DollMaster_MainBody": "Main Body", + "DollMaster_Doll": "Doll", + "DollMaster_UnableToUseAbility": "Unable to use your ability on player", + "Doppelganger_RoleInfo": "Spoofed Role: {0}", + "EatenByDevourer": "The Devourer ate your skin", + "DevourerEatenSkin": "Target skin is eaten", + "DevouredName": "Devoured", + "PitfallTrapCooldown": "Trap Cooldown", + "PitfallMaxTrapCount": "Number of Traps that can be set", + "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", + "PitfallTrapDuration": "Time the Trap remains active", + "PitfallTrapRadius": "Trap Radius", + "PitfallTrapFreezeTime": "Trap freeze time", + "PitfallTrapCauseVision": "Trap caused vision", + "PitfallTrapCauseVisionTime": "Trap caused vision time", + "PitfallTrap": "You have fallen into a trap!", + "ConsigliereDivinationMaxCount": "Maximum Reveals", + "RitualMaxCount": "Maximum Reveals", + "CleanserHideVote": "Hide Cleanser's vote", + "OracleSkillLimit": "Maximum Uses", + "OracleHideVote": "Hide Oracle's vote", + "OracleCheckReachLimit": "You're out of uses!", + "OracleCheckSelfMsg": "You can't even trust yourself, huh?", + "OracleCheckLimit": "Reminder: You have {0} uses left", + "OracleCheckMsgTitle": "ORACLE ", + "OracleCheck.NotCrewmate": "Appears not to be a crewmate", + "OracleCheck.Crewmate": "Appears to be a crewmate", + "OracleCheck.Neutral": "Appears to be a neutral", + "OracleCheck.Impostor": "Appears to be an Impostor", + "OracleCheck": "Target Results:", + "FailChance": "Chance of showing incorrect result", + "OracleCheckAddons": "Oracle checks add-ons", + "ChameleonCanVent": "Vent to disguise", + "ChameleonInvisState": "You are disguising!", + "ChameleonInvisStateOut": "Your disguise ended", + "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", + "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", + "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", + "ChameleonCooldown": "Disguise Cooldown", + "ChameleonDuration": "Disguise Duration", + "ChameleonRevertDisguise": "Expose", + "ChameleonDisguise": "Disguise", + "KillCooldownAfterCleaning": "Kill Cooldown On Clean", + "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", + "MedusaStoneBody": "Body stoned", + "MedusaReportButtonText": "Stone", + + "CursedSoulCurseCooldown": "Soul Snatch Cooldown", + "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", + "CursedSoulCurseMax": "Maximum Soul Snatches", + "CursedSoulKnowTargetRole": "Know the roles of Soulless players", + "CursedSoulCanCurseNeutral": "Neutral roles have souls", + "CursedSoulKillButtonText": "Snatch", + "SoullessByCursedSoul": "A Cursed Soul snatched your soul", + "CursedSoulSoullessPlayer": "Soul snatched", + "CursedSoulInvalidTarget": "No soul found", + + "AdmireCooldown": "Admire Cooldown", + "AdmirerKnowTargetRole": "Know the roles of Admired players", + "AdmirerSkillLimit": "Skill Limit", + "AdmireButtonText": "Admire", + "AdmirerAdmired": "The Admirer admired you!", + "AdmiredPlayer": "Player admired", + "AdmirerInvalidTarget": "Target cannot be admired", + + "SpiritualistNoticeTitle": "SPIRITUALIST ", + "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", + "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", + "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", + "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", + "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", + "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", + "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", + "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", + "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", + "EnigmaClueHat1": "The Killer wears a Hat!", + "EnigmaClueHat2": "The Killer does not wear a Hat!", + "EnigmaClueHat3": "The Killer wears {0} as a Hat!", + "EnigmaClueSkin1": "The Killer wears a Skin!", + "EnigmaClueSkin2": "The Killer does not wear a Skin!", + "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", + "EnigmaClueVisor1": "The Killer wears a Visor!", + "EnigmaClueVisor2": "The Killer does not wear a Visor!", + "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", + "EnigmaCluePet1": "The Killer does have a Pet!", + "EnigmaCluePet2": "The Killer does not have a Pet!", + "EnigmaCluePet3": "The Killer has {0} as a Pet!", + "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", + "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", + "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", + "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", + "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", + "EnigmaClueColor1": "The Killer has a light color!", + "EnigmaClueColor2": "The Killer has a dark color!", + "EnigmaClueColor3": "The Killer's color is {0}!", + "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", + "EnigmaClueStatus1": "The Killer is currently inside a Vent!", + "EnigmaClueStatus2": "The Killer is currently on a Ladder!", + "EnigmaClueStatus3": "The Killer is already Dead!", + "EnigmaClueStatus4": "The Killer is still Alive!", + "EnigmaClueRole1": "The Killer is an Impostor!", + "EnigmaClueRole2": "The Killer is a Neutral!", + "EnigmaClueRole3": "The Killer is a Crewmate!", + "EnigmaClueRole4": "The Killer's Role is {0}!", + "EnigmaClueLevel1": "The Killer's Level is above 50!", + "EnigmaClueLevel2": "The Killer's Level is below 50!", + "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", + "EnigmaClueLevel4": "The Killer's Level is {0}!", + "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", + "EnigmaClueHatTitle": "Enigma Hat Clue!", + "EnigmaClueVisorTitle": "Enigma Visor Clue!", + "EnigmaClueSkinTitle": "Enigma Skin Clue!", + "EnigmaCluePetTitle": "Enigma Pet Clue!", + "EnigmaClueNameTitle": "Enigma Name Clue!", + "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", + "EnigmaClueColorTitle": "Enigma Color Clue!", + "EnigmaClueLocationTitle": "Enigma Location Clue!", + "EnigmaClueStatusTitle": "Enigma Status Clue!", + "EnigmaClueRoleTitle": "Enigma Role Clue!", + "EnigmaClueLevelTitle": "Enigma Level Clue!", + "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", + + "VotesPerKill": "Votes gained for each kill", + "PickpocketGetVote": "You've got {0} votes", + "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "VultureNumberOfReportsToWin": "Bodies needed to win", + "VultureReportBody": "Body eaten!", + "VultureEatButtonText": "Consume", + "VultureReportCooldown": "Eat Cooldown", + "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", + "VultureCooldownUp": "Eat Cooldown finished", + + "GhastlyPossessCD": "Possess Cooldown", + "GhastlyMaxPossessions": "Max Possessions", + "GhastlyPossessionDuration": "Possession Duration", + "GhastlySpeed": "Ghastly Speed", + "GhastlyKillAllies": "Ghastly cannot possess allies", + "GhastlyCannotPossessTarget": "Couldn't Possess Target", + "GhastlyChooseTarget": "Now: Choose Target", + "GhastlyNoMorePossess": "You've run out of possessions!'", + "GhastlyNotUrTarget": "That is not your target", + "GhastlyYouvePosses": "You've Been Possessed!", + "GhastlyPossessedUser": "You have possessed: {0}", + "GhastlyExpired": "{0} is no longer possessed", + + "TasksMarkPerRound": "Number of tasks that can be marked in one round", + "TaskinatorBombPlanted": "Bomb has been planted", + + "ShieldDuration": "Shield duration", + "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", + "BenefactorTaskMarked": "Task marked successfully", + "BenefactorTargetGotShield": "You got a shield by Benefactor", + + "PirateTryHideMsg": "Hide Pirate's commands", + "SuccessfulDuelsToWin": "Number of successful duels needed to win", + "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the Duel if you choose the same option as the target", + "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", + "PirateTitle": "PIRATE ", + "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", + "PirateDead": "Ye be dead. Ye cannot duel anymore.", + "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", + "DuelDone": "Ye 'ave chosen yer option fer the Duel.\nWait fer the meetin' to end to see the result.", + "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", + "PirateDuelButtonText": "Duel", + "DuelCooldown": "Duel Cooldown", + "Rock": "Rock", + "Paper": "Paper", + "Scissors": "Scissors", + "Heads": "Heads", + "Tails": "Tails", + "SpyRedNameDur": "Colored Name Duration", + "SpyInteractionBlocked": "Block kill button interaction", + "AgitaterBombCooldown": "Agitator bomb cooldown", + "AgitaterPassCooldown": "Bomb pass cooldown", + "BombExplodeCooldown": "Bomb explode cooldown", + "AgitaterPassNotify": "Bomb successfully passed", + "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", + "AgitaterCanGetBombed": "Agitator can get bomb", + "AgitaterAutoReportBait": "Agitator Auto Report Bait", + + "SeekerPointsToWin": "Number of points required to win", + "SeekerTagCooldown": "Tag Cooldown", + "SeekerNotify": "Your target is {0}", + "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", + "SeekerKillButtonText": "Tag", + + "PixiePointsToWin": "Number of points required to win", + "MaxTargets": "Maximum number of targets per round", + "MarkCooldown": "Mark cooldown", + "PixieSuicide": "Pixie suicides if the target is not voted out", + "PixieMaxTargetReached": "You have already selected all the targets this round", + "PixieTargetAlreadySelected": "Target is already selected", + "PixieButtonText": "Mark", + + "PlagueBearerCooldown": "Plague cooldown", + "PestilenceCooldown": "Pestilence Kill cooldown", + "PestilenceCanVent": "Pestilence Can Vent", + "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", + "PlagueBearerAlreadyPlagued": "Player has already been plagued", + "PlagueBearerToPestilence": "You have turned into Pestilence!!", + "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", + "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", + "RomanticBetCooldown": "Pick Partner Cooldown", + "RomanticProtectCooldown": "Protect Cooldown", + "RomanticBetPlayer": "You picked your partner", + "RomanticBetOnYou": "The Romantic chose you as their Partner!", + "VengefulKCD": "Vengeful Romantic Kill Cooldown", + "VengefulCanVent": "Vengeful Romantic Can Vent", + "RuthlessKCD": "Ruthless Romantic Kill Cooldown", + "RuthlessCanVent": "Ruthless Romantic Can Vent", + "RomanticProtectPartner": "Your partner is under protection", + "RomanticIsProtectingYou": "The Romantic is protecting you", + "ProtectingOver": "Shield expired", + "RomanticProtectDuration": "Protect Duration", + "RomanticKnowTargetRole": "Romantic knows their target's role", + "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", + "RomanticPartnerButtonText": "Pick Partner", + "RomanticProtectButtonText": "Protect", + + "GuessMasterMisguess": "{0} misguessed", + "GuessMasterTargetRole": "Someone tried to guess {0}", + "GuessMasterTitle": "Guess Master ", + + "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", + "DCanGuessImpostors": "Can Guess Impostors", + "DCanGuessCrewmates": "Can Guess Crewmates", + "DCanGuessNeutrals": "Can Guess Neutrals", + "DCanGuessAdt": "Can Guess Add-Ons", + "DoomsayerAdvancedSettings": "Advanced Settings", + "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", + "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", + "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", + "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", + "DoomsayerTryHideMsg": "Hide Doomsayer's commands", + "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", + "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", + "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", + "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", + "DoomsayerGuessCountTitle": "DOOMSAYER", + "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", + + "EveryoneCanKnowMini": "Everyone can see the Mini", + "CanBeEvil": "Mini can be an Impostor", + "EvilMiniSpawnChances": "Probability of Mini being an Impostor", + "GuessMini": "Sorry, you can't hurt a kid Mini.", + "GrowUpDuration": "Time required to grow (s)", + "MajorCooldown": "Kill Cooldown when over 18", + "UpDateAge": "Display age change in real-time", + "Cantkillkid": "You can't kill a Mini that hasn't grown up.", + "CantEat": "You can't eat a Mini that hasn't grown up", + "CantShroud": "You can't control a Mini that hasn't grown up.", + "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", + "CantRecruit": "You can't recruit a Mini that hasn't grown up.", + "CantDuel": "You can't duel a Mini that hasn't grown up.", + "CantMark": "You can't mark a Mini that hasn't grown up.", + "CantBlood": "You can't blood a Mini that hasn't grown up.", + "CantPosses": "You can't possess a Mini that hasn't grown up.", + "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", + "MiniUp": "You're a year older!", + "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilt while you can no longer guess.\nYou can guess again after you have grown up.", + "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", + "CountMeetingTime": "Meeting time can continue to grow", + "YouKillRandomizer1": "You kill Randomizer, Self-report!", + "YouKillRandomizer2": "You kill Randomizer, Cannot move!", + "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", + "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", + "MadmateCanBeHurried": "Madmate can be Hurried on game start", + "TaskBasedCrewCanBeHurried": "Task-based Crews can be Hurried", + "HurriedCanBeConverted": "Hurried can be recruited in the game (excludes madmate)", + "Developer": "Developer", + "Sponsor": "Sponsor", + "Booster": "Server Booster", + "Translator": "Translator", + "NoAccess": "Unauthorized Access!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", + "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", + "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", + "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", + "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", + "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", + "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", + "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", + "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", + "DCNotify.Inactivity": "The lobby closed due to inactivity.", + "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", + "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", + "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", + "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", + "RoleType.VanillaRoles": "★ Vanilla Roles", + "RoleType.ImpKilling": "★ Impostor Killing Roles", + "RoleType.ImpSupport": "★ Impostor Support Roles", + "RoleType.ImpConcealing": "★ Impostor Concealing Roles", + "RoleType.ImpHindering": "★ Impostor Hindering Roles", + "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", + "RoleType.Madmate": "★ Madmate Roles", + "RoleType.CrewSupport": "★ Crewmate Support Roles", + "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", + "RoleType.CrewPower": "★ Crewmate Power Roles", + "RoleType.CrewKilling": "★ Crewmate Killing Roles", + "RoleType.CrewBasic": "★ Crewmate Basic Roles", + "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", + "RoleType.NeutralEvil": "★ Neutral Evil Roles", + "RoleType.NeutralBenign": "★ Neutral Benign Roles", + "RoleType.NeutralChaos": "★ Neutral Chaos Roles", + "RoleType.NeutralKilling": "★ Neutral Killing Roles", + "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", + "RoleType.Harmful": "★ Harmful Add-ons", + "RoleType.Support": "★ Supportive Add-ons", + "RoleType.Helpful": "★ Helpful Add-ons", + "RoleType.Mixed": "★ Mixed Add-ons", + "RoleType.Misc": "★ Miscellaneous Add-ons", + "RoleType.Impostor": "★ Impostor Add-ons", + "RoleType.Neut": "★ Neutral Add-ons", + "SubType.Impostor": "★ Impostors", + "SubType.Shapeshifter": "★ Shapeshifters", + "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", + "SubType.Madmate": "★ Madmates", + "SubType.CrewmateKilling": "★ Crewmate Killings", + "SubType.Crewmate": "★ Regular Crewmates", + "SubType.New": "★ New!", + "CrewmateRoles": "★ Crewmate Roles ★", + "ImpostorRoles": "★ Impostor Roles ★", + "NeutralRoles": "★ Neutral Roles ★", + "AddonRoles": "★ Add-ons ★", + "WinnerRoleText.Impostor": "Impostors Win!", + "WinnerRoleText.Crewmate": "Crewmates Win!", + "WinnerRoleText.Apocalypse": "Apocalypse Wins!", + "WinnerRoleText.Terrorist": "Terrorist Wins!", + "WinnerRoleText.Jester": "Jester Wins!", + "WinnerRoleText.Lovers": "Lovers Win!", + "WinnerRoleText.Executioner": "Executioner Wins!", + "WinnerRoleText.Arsonist": "Arsonist Wins!", + "WinnerRoleText.Revolutionist": "Revolutionist Wins!", + "WinnerRoleText.Jackal": "Jackals Win!", + "WinnerRoleText.God": "God Wins!", + "WinnerRoleText.Vector": "Vector Wins!", + "WinnerRoleText.Innocent": "Innocent Wins!", + "WinnerRoleText.Pelican": "Pelican Wins!", + "WinnerRoleText.Youtuber": "YouTuber Wins!", + "WinnerRoleText.Necromancer": "Necromancer Wins!", + "WinnerRoleText.Egoist": "Egoists Win!", + "WinnerRoleText.Demon": "Demon Wins!", + "WinnerRoleText.Stalker": "Stalker Wins!", + "WinnerRoleText.Workaholic": "Workaholic Wins!", + "WinnerRoleText.Collector": "Collector Wins!", + "WinnerRoleText.BloodKnight": "Blood Knight Wins!", + "WinnerRoleText.Poisoner": "Poisoner Wins!", + "WinnerRoleText.Huntsman": "Huntsman Wins!", + "WinnerRoleText.HexMaster": "Hex Master Wins!", + "WinnerRoleText.Cultist": "Cultist Wins!", + "WinnerRoleText.Wraith": "Wraith Wins!", + "WinnerRoleText.SerialKiller": "Serial Killers Win!", + "WinnerRoleText.Juggernaut": "Juggernaut Wins!", + "WinnerRoleText.Infectious": "Infectious Wins!", + "WinnerRoleText.Virus": "Virus Wins!", + "WinnerRoleText.Specter": "Specter Wins!", + "WinnerRoleText.Jinx": "Jinx Wins!", + "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", + "WinnerRoleText.PotionMaster": "Potion Master Wins!", + "WinnerRoleText.Pickpocket": "Pickpocket Wins!", + "WinnerRoleText.Traitor": "Traitor Wins!", + "WinnerRoleText.Vulture": "Vulture Wins!", + "WinnerRoleText.Medusa": "Medusa Wins!", + "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", + "WinnerRoleText.Glitch": "Glitch Wins!", + "WinnerRoleText.Pestilence": "Pestilence Wins!", + "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", + "WinnerRoleText.PunchingBag": "Punching Bag Wins!", + "WinnerRoleText.Doomsayer": "Doomsayer Wins!", + "WinnerRoleText.Pirate": "Pirate Wins!", + "WinnerRoleText.Shroud": "Shroud Wins!", + "WinnerRoleText.Werewolf": "Werewolf Wins!", + "WinnerRoleText.Seeker": "Seeker Wins!", + "WinnerRoleText.Occultist": "Occultist Wins!", + "WinnerRoleText.SoulCollector": "Soul Collector Wins!", + "WinnerRoleText.NiceMini": "Nice Mini Wins!", + "WinnerRoleText.Mini": "Nice Mini was killed", + "WinnerRoleText.Bandit": "Bandit Wins!", + "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", + "WinnerRoleText.Solsticer": "Solsticer Wins!", + "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", + "WinnerRoleText.Doppelganger": "Doppelganger Wins!", + "WinnerRoleText.Quizmaster": "Quizmaster Wins!", + "WinnerRoleText.Agitater": "Agitator Wins!", + "AdditionalWinnerRoleText.Sidekick": "Sidekick", + "AdditionalWinnerRoleText.Taskinator": "Taskinator", + "AdditionalWinnerRoleText.Opportunist": "Opportunist", + "AdditionalWinnerRoleText.Lawyer": "Lawyer", + "AdditionalWinnerRoleText.Hater": "Hater", + "AdditionalWinnerRoleText.Provocateur": "Provocateur", + "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", + "AdditionalWinnerRoleText.Follower": "Follower", + "AdditionalWinnerRoleText.Pursuer": "Pursuer", + "AdditionalWinnerRoleText.Jester": "Jester", + "AdditionalWinnerRoleText.Lovers": "Lovers", + "AdditionalWinnerRoleText.Executioner": "Executioner", + "AdditionalWinnerRoleText.Specter": "Specter", + "AdditionalWinnerRoleText.Maverick": "Maverick", + "AdditionalWinnerRoleText.Shaman": "Shaman", + "AdditionalWinnerRoleText.Pixie": "Pixie", + "AdditionalWinnerRoleText.NiceMini": "Nice Mini", + "AdditionalWinnerRoleText.Romantic": "Romantic", + "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", + "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", + "ErrorEndText": "An error occurred", + "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", + "ForceEnd": "Aborted", + "EveryoneDied": "Everyone died", + "ForceEndText": "Host has aborted the game", + "NiceMiniDied": "Nice Mini was killed", + "HaterMisFireKillTarget": "Hater kills target when misfiring", + "HaterChooseConverted": "Select add-ons that Hater can kill", + "HaterCanKillMadmate": "Can kill madmate", + "HaterCanKillCharmed": "Can kill charmed", + "HaterCanKillLovers": "Can kill lovers", + "HaterCanKillSidekick": "Can kill jackal team", + "HaterCanKillEgoist": "Can kill egoist", + "HaterCanKillInfected": "Can kill infected team", + "HaterCanKillContagious": "Can kill virus team", + "HaterCanKillAdmired": "Can kill admirer", + "HorseMode": "Enable to become a horse", + "LongMode": "Enable to have a long neck", + "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", + + + "FFA": "Free For All", + "ModeFFA": "Gamemode: FFA", + "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", + "KillerInfoLong": "In the FFA (Free For All) game mode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", + "FFA_GameTime": "Maximum Game Length", + "FFA_KCD": "Kill Cooldown", + "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", + "FFA_EnableRandomAbilities": "Enable Random Events", + "FFA_ShieldDuration": "Shield Duration", + "FFA_IncreasedSpeed": "Increased Speed", + "FFA_DecreasedSpeed": "Decreased Speed", + "FFA_ModifiedSpeedDuration": "Modified Speed Duration", + "FFA_LowerVision": "Lowered Vision", + "FFA_ModifiedVisionDuration": "Lowered Vision Duration", + "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", + "FFA-Event-GetShield": "You have a temporary shield!", + "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", + "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", + "FFA-Event-GetHighKCD": "You got a higher kill cooldown", + "FFA-Event-GetLowVision": "You have lower vision temporarily", + "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", + "FFA-Event-GetTP": "You got teleported to a random vent!", + "FFA-Event-RandomTP": "Everyone was swapped with someone", + "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", + "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", + "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", + "FFA_TargetIsShielded": "The player you tried to kill is shielded!", + "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", + "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", + "Killer": "FREE FOR ALL", + "KillerInfo": "Kill Everyone to Win", + + "Hide&SeekTOHE": "Hide & Seek", + "MenuTitle.Hide&Seek": "Hide & Seek Settings", + "NumImpostorsHnS": "Num Impostors", + + "EveryOneKnowSolsticer": "Every One Know who is Solsticer", + "SolsticerKnowItsKiller": "Solsticer knows the role of whom used the kill button on it", + "SolsticerSpeed": "Movement speed of Solsticer", + "SolsticerRemainingTaskWarned": "Remaining tasks to be known", + "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", + "SolsticerMurdered": "{0} attempted to murder you!", + "MurderSolsticer": "You stopped Solsticer this round!", + "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", + "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", + "SolsticerTitle": "Solsticer", + "GuessSolsticer": "Sorry, but you can not guess Solsticer!", + "VoteSolsticer": "Sorry, but you can not vote Solsticer!", + "SolsticerTasksReset": "Your tasks get reset!", + "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", + "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", + + "VoteDead": "The player you voted for was exiled before the meeting concluded. Your vote was rescinded.", + + "ImpCanBeSilent": "Impostors can become Silent", + "CrewCanBeSilent": "Crewmates can become Silent", + "NeutralCanBeSilent": "Neutrals can become Silent", + "LastMessageReplay": "Last System Message Replay", + "Contributor": "Contributor", + + "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", + "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", + + "ImpCanBeSusceptible": "Impostors can become Susceptible", + "CrewCanBeSusceptible": "Crewmates can become Susceptible", + "NeutralCanBeSusceptible": "Neutrals can become Susceptible", + + "Quizmaster": "Quizmaster", + "QuizmasterInfo": "Quiz people to kill them in meetings", + "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will have \"?!\" next to their name. The player will die if they answer the question wrong or doesn't answer. The player will live if the Quizmaster is killed/ejected in the same meeting.\nThe Quizmaster cannot mark multiple people in the same round", + "QuizmasterKillButtonText": "Quiz", + + "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", + "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", + "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", + "QuizmasterChat.CorrectTarget": "Correct", + "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", + "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", + "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", + "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", + "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", + "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die\n\nQuestion for {QMTARGET} => {QMQUESTION}", + "QuizmasterChat.Title": "Quizmaster Information", + "QuizmasterChat.CantAnswer": "As the quizmaster, you can't answer questions", + "QuizmasterChat.AnswerNotValid": "Your answer must be A, B, or C", + "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", + + "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", + "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", + "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", + "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", + "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", + + "Quizmaster.None": "None", + + "QuizmasterSabotages.Lights": "Lights", + "QuizmasterSabotages.Reactor": "Reactor", + "QuizmasterSabotages.Communications": "Communications", + "QuizmasterSabotages.O2": "O2", + "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", + "QuizmasterAnswers.One": "One", + "QuizmasterAnswers.Two": "Two", + "QuizmasterAnswers.Three": "Three", + "QuizmasterAnswers.Four": "Four", + "QuizmasterAnswers.Five": "Five", + "QuizmasterAnswers.Pacifist": "Pacifist", + "QuizmasterAnswers.Vampire": "Vampire", + "QuizmasterAnswers.Snitch": "Snitch", + "QuizmasterAnswers.Vigilante": "Vigilante", + "QuizmasterAnswers.Jackal": "Jackal", + "QuizmasterAnswers.Mole": "Mole", + "QuizmasterAnswers.Sniper": "Sniper", + "QuizmasterAnswers.Coven": "Coven", + "QuizmasterAnswers.Sabotuer": "Saboteur", + "QuizmasterAnswers.Sorcerers": "Sorcerers", + "QuizmasterAnswers.Killer": "Killer", + "QuizmasterAnswers.Edition": "Edition", + "QuizmasterAnswers.Experimental": "Experimental", + "QuizmasterAnswers.Enhanced": "Enhanced", + "QuizmasterAnswers.Edited": "Edited", + + "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", + "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", + "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", + "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", + "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called the last meeting before this meeting?", + "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", + "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", + "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", + "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", + "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed in an update later?", + "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", + "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", + "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", + "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", + "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", + "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", + "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", + + "DeathReason.WrongAnswer": "Wrong Quiz Answer", + + "TPCooldown": "Teleport Cooldown", + "RiftsTooClose": "Location too close to the first rift", + "RiftCreated": "Rift made successfully", + "RiftsDestroyed": "All rifts Destroyed", + "RiftRadius": "Rift Radius", + + "TiredVision": "Vision When Tired", + "TiredSpeed": "Speed When Tired", + "TiredDur": "Tired Duration", + "ImpCanBeTired": "Impostors can become Tired", + "CrewCanBeTired": "Crewmates can become Tired", + "NeutralCanBeTired": "Neutrals can become Tired", + + "TiredNotify": "Zzz..", + + "PlagueDoctorInfectLimit": "Infect Limit", + "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", + "PlagueDoctorInfectTime": "Infect Time", + "PlagueDoctorInfectDistance": "Infect Distance", + "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", + "PlagueDoctorCanInfectSelf": "Can Infect Self", + "PlagueDoctorCanInfectVent": "Can Infect While In Vent", + "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", + + "StatueSlow": "Statue Slowness", + "StatuePeopleToSlow": "People Needed To Slow", + + "ImpCanBeStatue": "Impostors can become Statue", + "CrewCanBeStatue": "Crewmates can become Statue", + "NeutralCanBeStatue": "Neutrals can become Statue", + + "WardenIncreaseSpeed": "Increase Speed By", + "WardenWarn": "DANGER! RUN!", + + "MinionAbilityTime": "Ability Duration", + "Minion_Blind": "blinded" } diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 7120952ca2..87c250b22c 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -86,10 +86,10 @@ "PlagueBearer": "#e5f6b4", "Pestilence": "#343136", "SoulCollector": "#A675A1", - "Death": "#ff174f", + "Death": "#644661", "Baker": "#bf9f7a", "Famine": "#83461c", - "Berserker": "#FF0055", + "Berserker": "#cc0044", "War": "#2B0804", "Jester": "#ec62a5", "Terrorist": "#00e600", diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index ca91a1ee01..8db1a72d3d 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -163,19 +163,24 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } public override void OnEnterVent(PlayerControl pc, Vent vent) { - if (BTOS2Baker.GetBool()) { + if (BTOS2Baker.GetBool()) { + var sb = new StringBuilder(); switch (BreadID) // 0 = Reveal, 1 = Roleblock, 2 = Barrier { case 0: // Switch to Roleblock BreadID = 1; + sb.Append(GetString("BakerSwitchBread") + GetString("BakerRoleblockBread")); break; case 1: // Switch to Barrier BreadID = 2; + sb.Append(GetString("BakerSwitchBread") + GetString("BakerBarrierBread")); break; case 2: // Switch to Reveal BreadID = 0; + sb.Append(GetString("BakerSwitchBread") + GetString("BakerRevealBread")); break; } + pc.Notify(sb.ToString()); } } public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) From 9bbccc29c44c0565282e6df6419410824d7d8c9a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:27:18 +0200 Subject: [PATCH 140/778] add canceling ability possibility --- Modules/Utils.cs | 16 +++++++++++++++- Patches/MeetingHudPatch.cs | 13 ++++++++++++- Resources/Lang/en_US.json | 1 + Roles/Core/RoleBase.cs | 1 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f848ee6678..093bb0d537 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1695,6 +1695,17 @@ public static IEnumerable GetRoleBasesByType () where t : RoleBase return null; } + public static bool IsMethodOverridden(this RoleBase roleInstance, string methodName) + { + Type baseType = typeof(RoleBase); + Type derivedType = roleInstance.GetType(); + + MethodInfo baseMethod = baseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + MethodInfo derivedMethod = derivedType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + + return baseMethod.DeclaringType != derivedMethod.DeclaringType; + } + public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == PlayerId); private static readonly StringBuilder SelfSuffix = new(); @@ -2192,7 +2203,10 @@ public static void AfterMeetingTasks() foreach (var playerState in Main.PlayerStates.Values.ToArray()) { - playerState.RoleClass?.AfterMeetingTasks(); + if (playerState.RoleClass == null) continue; + + playerState.RoleClass.AfterMeetingTasks(); + playerState.RoleClass.HasVoted = true; } //Set kill timer diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 49de5b4241..85b92c3e93 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -8,6 +8,7 @@ using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; using UnityEngine; +using static TOHE.Utils; using static TOHE.Translator; namespace TOHE; @@ -638,6 +639,16 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } //Vote a disconnect player + // Return vote to player if uses checkvote and wants to vote normal without using his abilities. + if (suspectPlayerId == 253 && voter.GetRoleClass()?.IsMethodOverridden("CheckVote") == true) + { + if (!voter.GetRoleClass().HasVoted) + { + voter.GetRoleClass().HasVoted = true; + return false; + } + } + if (target != null && suspectPlayerId < 253) { if (!target.IsAlive() || target.Data.Disconnected) @@ -649,7 +660,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl } - if (!voter.GetRoleClass().CheckVote(voter, target)) + if (!voter.GetRoleClass().HasVoted && !voter.GetRoleClass().CheckVote(voter, target)) { Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); __instance.RpcClearVote(voter.GetClientId()); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index bb2e9848e2..c2eeb4a75d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1128,6 +1128,7 @@ "Settings:": "Settings:", "VoteAbilityUsed": "Used {0} Ability", "VoteHasReturned": "Your vote has been returned! (Meaning you can cast a vote normally)", + "VoteNotUseAbility": "You chose not to use your ability, and now may choose to skip or vote someone.", "PlayerCanSetColor": "Players can use the /color command", "PlayerCanSetName": "Players can use the /rn command", "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 17fcd94c27..889d0f6d5d 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -16,6 +16,7 @@ public abstract class RoleBase public float AbilityLimit { get; set; } = -100; public virtual bool IsEnable { get; set; } = false; + public bool HasVoted = false; public void OnInit() // CustomRoleManager.RoleClass executes this { IsEnable = false; From b7ad23e60e5df59ce516342693b3272a8991eced Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:01:00 +0200 Subject: [PATCH 141/778] bruh --- Patches/MeetingHudPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 85b92c3e93..83c3d81ee2 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -645,6 +645,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (!voter.GetRoleClass().HasVoted) { voter.GetRoleClass().HasVoted = true; + Utils.SendMessage("VoteNotUseAbility", voter.PlayerId); return false; } } From a3f024a2bd5081ad7138090444816760675ccc19 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:04:03 +0200 Subject: [PATCH 142/778] fuck --- Modules/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 093bb0d537..650154d180 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2206,7 +2206,7 @@ public static void AfterMeetingTasks() if (playerState.RoleClass == null) continue; playerState.RoleClass.AfterMeetingTasks(); - playerState.RoleClass.HasVoted = true; + playerState.RoleClass.HasVoted = false; } //Set kill timer From 7910611d4783ac3c027a238f95d120ec5fde6644 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 30 Jul 2024 15:13:46 +0800 Subject: [PATCH 143/778] Check IsModHost --- Patches/TextBoxPatch.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index ffc1979db8..aedf90dfdb 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -9,6 +9,8 @@ class TextBoxTMPSetTextPatch { public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string input, [HarmonyArgument(1)] string inputCompo = "") { + if (!GameStates.IsModHost) return true; + bool flag = false; char ch = ' '; __instance.tempTxt.Clear(); From 4216c69bf0a926a11db7d09bf4b97407b8819fe5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:18:14 +0200 Subject: [PATCH 144/778] nothing --- Patches/GameSettingMenuPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index c6453a0046..8542cf180c 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -279,7 +279,7 @@ static void SearchForOptions(FreeChatInputField textField) && !GetString($"{x.Name}").ToLower().Contains(text) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); HiddenBySearch = Result; var SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsHiddenOn(Options.CurrentGameMode) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - if (!SearchWinners.Any() || !ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out var settingsTab)) + if (!SearchWinners.Any() || !ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out var settingsTab) || settingsTab == null) { HiddenBySearch.Clear(); Logger.SendInGame(GetString("SearchNoResult")); // okay so showpopup nor this will overlay the menu, but I use this just for the sound lol @@ -299,7 +299,7 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ if (HiddenBySearch.Any()) { HiddenBySearch.Do(x => x.SetHidden(false)); - if (ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out var GameSettingsTab)) + if (ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out var GameSettingsTab) && GameSettingsTab != null) GameOptionsMenuPatch.ReCreateSettings(GameSettingsTab); HiddenBySearch.Clear(); From 693a0fde3085dc6eadd9101a9932d7bcdd0e5ab2 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 30 Jul 2024 16:33:26 +0800 Subject: [PATCH 145/778] Monarch ability limit is over, do cd 300 --- Roles/Crewmate/Monarch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/Monarch.cs b/Roles/Crewmate/Monarch.cs index 5637cf4f5c..e53c810aeb 100644 --- a/Roles/Crewmate/Monarch.cs +++ b/Roles/Crewmate/Monarch.cs @@ -40,7 +40,7 @@ public override void Add(byte playerId) Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); - public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KnightCooldown.GetFloat(); + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = AbilityLimit > 0 ? KnightCooldown.GetFloat() : 300f; public override bool CanUseKillButton(PlayerControl player) => AbilityLimit > 0; public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) From 91241bf53cda790e58fa5f2108ffd89f1ee47508 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:33:59 +0200 Subject: [PATCH 146/778] lol this is much better --- Modules/OptionItem/OptionItem.cs | 15 +-------------- Patches/GameOptionsMenuPatch.cs | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index f976541331..ff38d57a90 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -181,20 +181,7 @@ public virtual string GetString() // Deprecated IsHidden function public virtual bool IsHiddenOn(CustomGameMode mode) { - return CheckHidden() || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); - } - private bool CheckHidden() - { - var LastParent = this.Id; - - - for (var i = 0; i < 5; i++) - { - if (AllOptions.First(x => x.Id == LastParent).Parent == null) break; - LastParent = AllOptions.First(x => x.Id == LastParent).Parent.Id; - } - - return this.IsHidden || this.Parent?.IsHidden == true || AllOptions.First(x => x.Id == LastParent).IsHidden; + return IsHidden || this.Parent?.IsHiddenOn(Options.CurrentGameMode) == true || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); } public string ApplyFormat(string value) { diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 7dd5425282..5954ee56e7 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -178,7 +178,7 @@ float CalculateScrollBarYBoundsMax() { if (option.Tab != (TabGroup)(ModGameOptionsMenu.TabIndex - 3)) continue; - var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); + var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && option.Parent?.GetBool() is null or true; if (option is TextOptionItem) num -= 0.63f; else if (enabled) @@ -311,7 +311,7 @@ public static void ReCreateSettings(GameOptionsMenu __instance) var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); + var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && option.Parent?.GetBool() is null or true; if (ModGameOptionsMenu.CategoryHeaderList.TryGetValue(index, out var categoryHeaderMasked)) { From c79cef01680bf1ea8d6df87b1943e891c871f920 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:36:27 +0200 Subject: [PATCH 147/778] forgot --- Patches/GameOptionsMenuPatch.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 5954ee56e7..d0796790b4 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -72,8 +72,7 @@ System.Collections.IEnumerator CoRoutine() var option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - var enabled = !option.IsHiddenOn(Options.CurrentGameMode) - && (option.Parent == null || (!option.Parent.IsHiddenOn(Options.CurrentGameMode) && option.Parent.GetBool())); + var enabled = !option.IsHiddenOn(Options.CurrentGameMode) && option.Parent?.GetBool() is null or true; if (option is TextOptionItem) { From b912351c5351a8ecd83b5350605e5cfb7daec2b5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:41:37 +0200 Subject: [PATCH 148/778] add comment --- Patches/ControlPatch.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index c8f28fbafd..88f0be38f1 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -212,13 +212,14 @@ public static void Postfix(/*ControllerManager __instance*/) } } + //Search Bar In Menu "Press Enter" alternative function if (GetKeysDown(KeyCode.Return) && GameSettingMenuPatch.Instance != null && GameSettingMenuPatch.Instance.isActiveAndEnabled == true) { GameSettingMenuPatch._SearchForOptions?.Invoke(); } - // Forse start/end meeting - if (GetKeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) + // Force start/end meeting + if (GetKeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) { if (GameStates.IsHideNSeek) return; From c605e5a6cdbd80791f340f097ed0c3b3284926f9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 5 Aug 2024 00:49:28 +0800 Subject: [PATCH 149/778] Version Dev 1 --- main.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.cs b/main.cs index 0964232eda..7fbc97e55e 100644 --- a/main.cs +++ b/main.cs @@ -39,14 +39,14 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0804.202.9999"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.0.2"; + public const string PluginVersion = "2024.0804.210.00010"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Dev 1"; public const string SupportedVersionAU = "2024.6.18"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = false; // Latest: V2.0.0 Dev 25 + public static readonly bool devRelease = true; // Latest: V2.1.0 Dev 1 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 - public static readonly bool fullRelease = true; // Latest: V2.0.2 + public static readonly bool fullRelease = false; // Latest: V2.0.2 public static bool hasAccess = true; From 371780c38311fc285ad16ddfccbf29d26557f322 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 4 Aug 2024 12:57:53 -0400 Subject: [PATCH 150/778] tommy --- Patches/CheckGameEndPatch.cs | 9 ++++--- Patches/MeetingHudPatch.cs | 46 ++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index b3b078b3ae..ec983358e7 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -344,10 +344,13 @@ public static bool Prefix() break; } } - foreach (var pc in Main.AllPlayerControls.Where(x => x.IsNeutralApocalypse() && Main.AllAlivePlayerControls.All(p => p.IsNeutralApocalypse()))) + if (Main.AllAlivePlayerControls.All(p => p.IsNeutralApocalypse())) { - if (!WinnerIds.Contains(pc.PlayerId)) - WinnerIds.Add(pc.PlayerId); + foreach (var pc in Main.AllPlayerControls.Where(x => x.IsNeutralApocalypse())) + { + if (!WinnerIds.Contains(pc.PlayerId)) + WinnerIds.Add(pc.PlayerId); + } } if (WinnerTeam is CustomWinner.Youtuber) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index e84f333a16..79672865f7 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -864,34 +864,28 @@ public static void NotifyRoleSkillOnMeetingStart() string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); } - // Apocalypse Notify - if (CustomRoles.Pestilence.RoleExist()) + // Apocalypse Notify, thanks tommy + var transformRoles = new CustomRoles[] { CustomRoles.Pestilence, CustomRoles.War, CustomRoles.Famine, CustomRoles.Death }; + foreach (var role in transformRoles) { - _ = new LateTask(() => - { - AddMsg(string.Format(GetString("PestilenceTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Pestilence), GetString("ApocalypseIsNigh"))); - }, 3f, "Pestilence Apocalypse Notify"); - } - if (CustomRoles.War.RoleExist()) - { - _ = new LateTask(() => + if (role.RoleExist()) { - AddMsg(string.Format(GetString("BerserkerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.War), GetString("ApocalypseIsNigh"))); - }, 3f, "War Apocalypse Notify"); - } - if (CustomRoles.Famine.RoleExist()) - { - _ = new LateTask(() => - { - AddMsg(string.Format(GetString("BakerTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Famine), GetString("ApocalypseIsNigh"))); - }, 3f, "Famine Apocalypse Notify"); - } - if (CustomRoles.Death.RoleExist()) - { - _ = new LateTask(() => - { - AddMsg(string.Format(GetString("SoulCollectorTransform")), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Death), GetString("ApocalypseIsNigh"))); - }, 3f, "Death Apocalypse Notify"); + _ = new LateTask(() => + { + var roleMessage = role switch + { + CustomRoles.Pestilence => GetString("PestilenceTransform"), + CustomRoles.War => GetString("BerserkerTransform"), + CustomRoles.Famine => GetString("BakerTransform"), + CustomRoles.Death => GetString("SoulCollectorTransform"), + _ => "", + }; + + if (roleMessage != "") + AddMsg(roleMessage, 255, Utils.ColorString(Utils.GetRoleColor(role), GetString("ApocalypseIsNigh"))); + + }, 3f, $"{role} Apocalypse Notify"); + } } string MimicMsg = ""; From 05c1e6d622618e33c865809b2c8908199517a7b5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:03:35 -0400 Subject: [PATCH 151/778] Update en_US.json --- Resources/Lang/en_US.json | 7148 ++++++++++++++++++------------------- 1 file changed, 3574 insertions(+), 3574 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9915e750ab..24e50db1fd 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1,1173 +1,1173 @@ { - "LanguageID": "0", - "HostText": "Host", - "HostColor": "#902efd", - "IconColor": "#4bf4ff", - "Icon": "♥", - "NameColor": "#ffc0cb", - - "HideHostText": "Hide 'Host♥' Text", - "HideAllTagsAndText": "Hide All Tags (for «AutoMuteUs»)", - - "SupportUs": "Support Us", - "update": "Update", - "GitHub": "GitHub", - "Discord": "Discord", - "Website": "Website", - "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", - - "HostIconInMeeting": "HOST: {0}", - - "SubText.Crewmate": "Find and exile the Impostors", - "SubText.Impostor": "Sabotage and kill everyone", - "SubText.Neutral": "Work alone to achieve your victory", - "SubText.Apocalypse": "Become unstoppable with your team", - "SubText.Madmate": "Help the Impostors", - - "TypeImpostor": "Impostors", - "TypeCrewmate": "Crewmates", - "TypeNeutral": "Neutrals", - "TypeAddon": "Add-ons", - "GuesserMode": "Guesser Mode", - - "TeamImpostor": "Impostor", - "TeamNeutral": "Neutral", - "TeamCrewmate": "Crewmate", - "TeamMadmate": "Madmate", - - "YouAreCrewmate": "You are a Crewmate", - "YouAreImpostor": "You are an Impostor", - "YouAreNeutral": "You are a Neutral", - "YouAreMadmate": "You are a Madmate", - - - "Role_Crewmate": "Crewmate", - "Role_Jester": "Jester", - "Role_Opportunist": "Opportunist", - "Role_Celebrity": "Celebrity", - "Role_Bodyguard": "Bodyguard", - "Role_Dictator": "Dictator", - "Role_Mayor": "Mayor", - "Role_Doctor": "Doctor", - "Role_Maverick": "Maverick", - "Role_Pursuer": "Pursuer", - "Role_Follower": "Follower", - "Role_Amnesiac": "Amnesiac", - "Role_Imitator": "Imitator", - "Role_Sheriff": "Sheriff", - "Role_Knight": "Knight", - "Role_Deputy": "Deputy", - "Role_NoChange": "Don't change the role", - - "CrewmatesCanGuess": "Crewmates can guess", - "ImpostorsCanGuess": "Impostors can guess", - "NeutralKillersCanGuess": "Neutral Killers can guess", - "NeutralApocalypseCanGuess": "Neutral Apocalypse can guess", - "PassiveNeutralsCanGuess": "Passive Neutrals can guess", - - "CanGuessAddons": "Can Guess Add-ons", - "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", - "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", - "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", - "GuessImmune": "Sorry, but target is immune to being guessed!", - - - "GM": "Game Master", - "Sunnyboy": "Sunnyboy", - "Bard": "Bard", - "Crewmate": "Crewmate", - "CrewmateTOHE": "Crewmate", - "Engineer": "Engineer", - "EngineerTOHE": "Engineer", - "Scientist": "Scientist", - "ScientistTOHE": "Scientist", - "Noisemaker": "Noisemaker", - "NoisemakerTOHE": "Noisemaker", - "Tracker": "Tracker", - "TrackerTOHE": "Tracker", - "GuardianAngel": "Guardian Angel", - "GuardianAngelTOHE": "Guardian Angel", - "Impostor": "Impostor", - "ImpostorTOHE": "Impostor", - "Shapeshifter": "Shapeshifter", - "ShapeshifterTOHE": "Shapeshifter", - "Phantom": "Phantom", - "PhantomTOHE": "Phantom", - - "BountyHunter": "Bounty Hunter", - "Fireworker": "Fireworker", - "Mercenary": "Mercenary", - "ShapeMaster": "Shapemaster", - "Vampire": "Vampire", - "Warlock": "Warlock", - "Ninja": "Ninja", - "Zombie": "Zombie", - "Anonymous": "Anonymous", - "Miner": "Miner", - "KillingMachine": "Killing Machine", - "Escapist": "Escapist", - "Witch": "Witch", - "Nemesis": "Nemesis", - "Bloodmoon": "Bloodmoon", - "Puppeteer": "Puppeteer", - "Mastermind": "Mastermind", - "TimeThief": "Time Thief", - "Sniper": "Sniper", - "Undertaker": "Undertaker", - "RiftMaker": "Rift Maker", - "EvilTracker": "Evil Tracker", - "EvilHacker": "Evil Hacker", - "EvilGuesser": "Evil Guesser", - "AntiAdminer": "Anti Adminer", - "Arrogance": "Arrogance", - "Bomber": "Bomber", - "Scavenger": "Scavenger", - "Trapster": "Trapster", - "Gangster": "Gangster", - "Cleaner": "Cleaner", - "Lightning": "Lightning", - "Greedy": "Greedy", - "CursedWolf": "Cursed Wolf", - "SoulCatcher": "Soul Catcher", - "QuickShooter": "Quick Shooter", - "Camouflager": "Camouflager", - "Eraser": "Eraser", - "Butcher": "Butcher", - "Hangman": "Hangman", - "Swooper": "Swooper", - "Crewpostor": "Crewpostor", - "Wildling": "Wildling", - "Trickster": "Trickster", - "Vindicator": "Vindicator", - "Parasite": "Parasite", - "Disperser": "Disperser", - "Inhibitor": "Inhibitor", - "Saboteur": "Saboteur", - "Councillor": "Councillor", - "Dazzler": "Dazzler", - "Deathpact": "Deathpact", - "Devourer": "Devourer", - "Consigliere": "Consigliere", - "Morphling": "Morphling", - "Twister": "Twister", - "Lurker": "Lurker", - "Visionary": "Visionary", - "Refugee": "Refugee", - "Underdog": "Underdog", - "Ludopath": "Ludopath", - "Godfather": "Godfather", - "Chronomancer": "Chronomancer", - "Pitfall": "Pitfall", - "EvilMini": "Evil Mini", - "Blackmailer": "Blackmailer", - "Instigator": "Instigator", - "LazyGuy": "Lazy Guy", - "SuperStar": "Super Star", - "Celebrity": "Celebrity", - "Cleanser": "Cleanser", - "Keeper": "Keeper", - "Knight": "Knight", - "Mayor": "Mayor", - "Psychic": "Psychic", - "Mechanic": "Mechanic", - "Sheriff": "Sheriff", - "Vigilante": "Vigilante", - "Jailer": "Jailer", - "CopyCat": "Copycat", - "Snitch": "Snitch", - "Marshall": "Marshall", - "Doctor": "Doctor", - "Dictator": "Dictator", - "Detective": "Detective", - "NiceGuesser": "Nice Guesser", - "GuessMaster": "Guess Master", - "Transporter": "Transporter", - "TimeManager": "Time Manager", - "Veteran": "Veteran", - "Bastion": "Bastion", - "Bodyguard": "Bodyguard", - "Deceiver": "Deceiver", - "Grenadier": "Grenadier", - "Medic": "Medic", - "FortuneTeller": "Fortune Teller", - "Judge": "Judge", - "Mortician": "Mortician", - "Medium": "Medium", - "Pacifist": "Pacifist", - "Observer": "Observer", - "Monarch": "Monarch", - "Overseer": "Overseer", - "Coroner": "Coroner", - "Merchant": "Merchant", - "President": "President", - "Hawk": "Hawk", - "Retributionist": "Retributionist", - "Deputy": "Deputy", - "Investigator": "Investigator", - "Guardian": "Guardian", - "Addict": "Addict", - "Mole": "Mole", - "Alchemist": "Alchemist", - "Tracefinder": "Tracefinder", - "Oracle": "Oracle", - "Spiritualist": "Spiritualist", - "Chameleon": "Chameleon", - "Inspector": "Inspector", - "Captain": "Captain", - "Admirer": "Admirer", - "TimeMaster": "Time Master", - "Crusader": "Crusader", - "Reverie": "Reverie", - "Lookout": "Lookout", - "Telecommunication": "Telecommunication", - "Lighter": "Lighter", - "TaskManager": "Task Manager", - "Witness": "Witness", - "Swapper": "Swapper", - "NiceMini": "Nice Mini", - "Mini": "Mini", - "Spy": "Spy", - "Randomizer": "Randomizer", - "Enigma": "Enigma", - "Jester": "Jester", - "Arsonist": "Arsonist", - "Pyromaniac": "Pyromaniac", - "Kamikaze": "Kamikaze", - "Huntsman": "Huntsman", - "Terrorist": "Terrorist", - "Executioner": "Executioner", - "Lawyer": "Lawyer", - "Opportunist": "Opportunist", - "Vector": "Vector", - "Jackal": "Jackal", - "God": "God", - "Innocent": "Innocent", - "Stealth": "Stealth", - "Penguin": "Penguin", - "Pelican": "Pelican", - "PlagueDoctor": "Plague Scientist", - "Revolutionist": "Revolutionist", - "Hater": "Hater", - "Demon": "Demon", - "Stalker": "Stalker", - "Workaholic": "Workaholic", - "Solsticer": "Solsticer", - "Collector": "Collector", - "Provocateur": "Provocateur", - "BloodKnight": "Blood Knight", - "Apocalypse": "Apocalypse", - "PlagueBearer": "Plaguebearer", - "Pestilence": "Pestilence", - "SoulCollector": "Soul Collector", - "Death": "Death", - "Baker": "Baker", - "Famine": "Famine", - "Berserker": "Berserker", - "War": "War", - "Glitch": "Glitch", - "Sidekick": "Sidekick", - "Follower": "Follower", - "Cultist": "Cultist", - "SerialKiller": "Serial Killer", - "Juggernaut": "Juggernaut", - "Infectious": "Infectious", - "Virus": "Virus", - "Pursuer": "Pursuer", - "Specter": "Specter", - "Pirate": "Pirate", - "Agitater": "Agitator", - "Maverick": "Maverick", - "CursedSoul": "Cursed Soul", - "Pickpocket": "Pickpocket", - "Traitor": "Traitor", - "Vulture": "Vulture", - "Taskinator": "Taskinator", - "Benefactor": "Benefactor", - "Medusa": "Medusa", - "Spiritcaller": "Spiritcaller", - "Amnesiac": "Amnesiac", - "Imitator": "Imitator", - "Bandit": "Bandit", - "Doppelganger": "Doppelganger", - "PunchingBag": "Punching Bag", - "Doomsayer": "Doomsayer", - "Shroud": "Shroud", - "Werewolf": "Werewolf", - "Shaman": "Shaman", - "Seeker": "Seeker", - "Pixie": "Pixie", - "Occultist": "Occultist", - "SchrodingersCat": "Schrodingers Cat", - "Romantic": "Romantic", - "VengefulRomantic": "Vengeful Romantic", - "RuthlessRomantic": "Ruthless Romantic", - "Poisoner": "Poisoner", - "HexMaster": "Hex Master", - "Wraith": "Wraith", - "Jinx": "Jinx", - "PotionMaster": "Potion Master", - "Necromancer": "Necromancer", - "Warden": "Warden", - "Minion": "Minion", - "Ghastly": "Ghastly", - "LastImpostor": "Last Impostor", - "Overclocked": "Overclocked", - "Lovers": "Lovers", - "Madmate": "Madmate", - "Watcher": "Watcher", - "Flash": "Flash", - "Torch": "Torch", - "Seer": "Seer", - "Tiebreaker": "Tiebreaker", - "Oblivious": "Oblivious", - "Bewilder": "Bewilder", - "Workhorse": "Workhorse", - "Fool": "Fool", - "Avanger": "Avenger", - "Youtuber": "YouTuber", - "Egoist": "Egoist", - "TicketsStealer": "Stealer", - "Paranoia": "Paranoia", - "Mimic": "Mimic", - "Guesser": "Guesser", - "Necroview": "Necroview", - "Reach": "Reach", - "Charmed": "Charmed", - "Cleansed": "Cleansed", - "Bait": "Bait", - "Trapper": "Beartrap", - "Infected": "Infected", - "Onbound": "Onbound", - "Rebound": "Rebound", - "Mundane": "Mundane", - "Knighted": "Knighted", - "Unreportable": "Disregarded", - "Contagious": "Contagious", - "Lucky": "Lucky", - "Unlucky": "Unlucky", - "VoidBallot": "Void Ballot", - "Aware": "Aware", - "Fragile": "Fragile", - "DoubleShot": "Double Shot", - "Rascal": "Rascal", - "Soulless": "Soulless", - "Gravestone": "Gravestone", - "Lazy": "Lazy", - "Autopsy": "Autopsy", - "Loyal": "Loyal", - "EvilSpirit": "Evil Spirit", - "Recruit": "Recruit", - "Admired": "Admired", - "Glow": "Glow", - "Radar": "Radar", - "Diseased": "Diseased", - "Antidote": "Antidote", - "Stubborn": "Stubborn", - "Swift": "Swift", - "Ghoul": "Ghoul", - "Bloodthirst": "Bloodthirst", - "Mare": "Mare", - "Burst": "Burst", - "Sleuth": "Sleuth", - "Clumsy": "Clumsy", - "Nimble": "Nimble", - "Circumvent": "Circumvent", - "Cyber": "Cyber", - "Hurried": "Hurried", - "Oiiai": "OIIAI", - "Influenced": "Influenced", - "Silent": "Silent", - "Susceptible": "Susceptible", - "Tricky": "Tricky", - "Rainbow": "Rainbow", - "Tired": "Tired", - "Statue": "Statue", - "DollMaster": "Dollmaster", - "BracketAddons": "Add Brackets To Add-ons", - "EngineerTOHEInfo": "Use the vents to catch the Impostors", - "ScientistTOHEInfo": "Access portable vitals from anywhere", - "NoisemakerTOHEInfo": "Send out an alert when killed", - "TrackerTOHEInfo": "Track a players with your map", - "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", - "PhantomTOHEInfo": "Turn invisible", - "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", - "ImpostorTOHEInfo": "Kill and sabotage", - "CrewmateTOHEInfo": "Search for the Impostors", - "BountyHunterInfo": "Eliminate your target", - "FireworkerInfo": "Go out with a BANG", - "MercenaryInfo": "Keep killing, else you suicide", - "ShapeMasterInfo": "Swiftly kill with no shift cooldown", - "VampireInfo": "Your kills are delayed", - "WarlockInfo": "Curse crewmates then shift to make them kill", - "NinjaInfo": "Mark a target, then shift to kill", - "ZombieInfo": "You are very slow", - "AnonymousInfo": "Force a player to report a body", - "MinerInfo": "Warp to your last used vent by shifting", - "KillingMachineInfo": "You can ONLY kill, but low cooldown", - "EscapistInfo": "Shift to mark places and warp back to them", - "WitchInfo": "Spell crewmates to kill them in meetings", - "NemesisInfo": "Kill when you're the last Impostor", - "BeforeNemesisInfo": "You can't kill yet", - "AfterNemesisInfo": "Now start killing", - "BloodmoonInfo": "Seek havoc upon the crewmates", - "PuppeteerInfo": "Make players kill for you", - "MastermindInfo": "Make others kill for you", - "TimeThiefInfo": "Lower meeting time by killing", - "SniperInfo": "Snipe players from a distance by shifting", - "UndertakerInfo": "Teleport dead body to a marked location", - "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", - "EvilTrackerInfo": "Track players by shifting", - "EvilHackerInfo": "Hack systems", - "AntiAdminerInfo": "Know when players are near devices", - "ArroganceInfo": "With each kill you make, your cooldown decreases", - "BomberInfo": "Shapeshift to explode", - "TrapsterInfo": "Trap your kills", - "ScavengerInfo": "Your kills are unreportable", - "EvilGuesserInfo": "Guess crew roles in meetings to kill", - "GangsterInfo": "Convert players to your side", - "CleanerInfo": "Report bodies to make them unreportable", - "LightningInfo": "Convert players to Quantum Ghosts", - "GreedyInfo": "Your kill cooldown shifts", - "CursedWolfInfo": "You survive a few kill attempts", - "SoulCatcherInfo": "You swap places with your shift target", - "QuickShooterInfo": "Store ammo to offset kill cooldown", - "CamouflagerInfo": "Camouflage everyone for easy kills", - "EraserInfo": "Erase the role of your vote target", - "ButcherInfo": "Enjoy my beautiful work", - "HangmanInfo": "I will decide when your life will end", - "SwooperInfo": "Turn invisible temporarily", - "CrewpostorInfo": "Kill by completing tasks", - "WildlingInfo": "Kill with strength and disguise", - "TricksterInfo": "Kill and trick the crew", - "VindicatorInfo": "Use your extra votes to kill everyone", - "ParasiteInfo": "Help the Impostors kill the crew", - "DisperserInfo": "Teleport everyone to random vents", - "InhibitorInfo": "You cannot kill during sabotages", - "SaboteurInfo": "You can only kill during sabotages", - "CouncillorInfo": "Kill off crewmates during meetings", - "DazzlerInfo": "Reduce the vision of the crew", - "DeathpactInfo": "Assign players to a death pact", - "DevourerInfo": "Consume the skin of the crew", - "ConsigliereInfo": "Discover the roles of other players", - "MorphlingInfo": "You can only kill while shapeshifted", - "TwisterInfo": "Swap all player positions", - "LurkerInfo": "Reduce your kill cooldown by venting", - "ConvictInfo": "Your target died, now help the Impostors", - "VisionaryInfo": "You see the alignments of the living", - "RefugeeInfo": "Help the Impostors kill off the crew", - "UnderdogInfo": "Start killing on a low player count", - "LudopathInfo": "Your kill cooldown is random", - "GodfatherInfo": "Convert players to Refugees by voting", - "ChronomancerInfo": "Kill in bursts", - "PitfallInfo": "Setup traps around the map", - "EvilMiniInfo": "No one can hurt you until you grow up", - "BlackmailerInfo": "Silence other players", - "InstigatorInfo": "Sow discord among the crewmates", - "LazyGuyInfo": "You're too lazy", - "SuperStarInfo": "Everyone knows you", - "CleanserInfo": "Erase All Add-ons of your vote target", - "KeeperInfo": "Reject the Eject, Keeper Protect!", - "MayorInfo": "Your vote counts multiple times", - "PsychicInfo": "One of the red names are evil", - "MechanicInfo": "Vent around and fix sabotages", - "SheriffInfo": "Shoot the Impostors", - "VigilanteInfo": "Not the hero we deserved but the hero we needed", - "JailerInfo": "Jail suspicious players", - "CopyCatInfo": "Use kill button to copy target's role", - "SnitchInfo": "Finish your tasks to find the Impostors", - "MarshallInfo": "Finish your tasks to prove your innocence", - "DoctorInfo": "Know how each player died", - "DictatorInfo": "Exile a player based on your own judgment", - "DetectiveInfo": "Gain extra info from your body reports", - "UndercoverInfo": "Impostors see you as their partner", - "KnightInfo": "You can kill 1 player", - "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", - "GuessMasterInfo": "Whispers heard, every guessed word.", - "TransporterInfo": "Do tasks to swap two players' locations", - "TimeManagerInfo": "Increase meeting time by doing tasks", - "VeteranInfo": "Alert to kill anyone who interacts with you", - "BastionInfo": "Bomb vents", - "BodyguardInfo": "Prevent nearby kills", - "DeceiverInfo": "Try to fool the players", - "GrenadierInfo": "Reduce Impostors' vision by venting", - "MedicInfo": "Cast a shield onto a player", - "FortuneTellerInfo": "Get clues to people's roles", - "JudgeInfo": "Silence in the courtroom!", - "MorticianInfo": "Locate dead bodies", - "MediumInfo": "Talk with ghosts", - "ObserverInfo": "You can see all shield-animations", - "PacifistInfo": "Vent to reset kill cooldowns", - "MonarchInfo": "Give your crew extra voting power!", - "StealthInfo": "Killing Blinds Everyone in the Room", - "PenguinInfo": "Drag your victims", - "OverseerInfo": "Reveal roles of other players", - "CoronerInfo": "Find corpses and their killers", - "PresidentInfo": "You are in charge of the meeting", - "MerchantInfo": "Sell add-ons and bribe killers", - "RetributionistInfo": "Help the crew after you die", - "HawkInfo": "Seek murdering the bad guys!", - "DeputyInfo": "Handcuff killers to increase their cooldowns", - "InvestigatorInfo": "Find potential evils", - "GuardianInfo": "Complete your tasks to become immortal", - "AddictInfo": "Vent to become invulnerable, or you'll die", - "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", - "AlchemistInfo": "Brew potions by completing tasks", - "TracefinderInfo": "Sense the location of dead bodies", - "OracleInfo": "Vote a player to see their alignment", - "SpiritualistInfo": "Be guided by the ghostly life", - "ChameleonInfo": "Vent to disguise into your surroundings", - "InspectorInfo": "Validate the alignments of two players", - "CaptainInfo": "Sail with the Captain, lest add-ons be abandoned.", - "AdmirerInfo": "Choose a player to side with you", - "TimeMasterInfo": "Rewind time!", - "CrusaderInfo": "Kill a player's attacker", - "ReverieInfo": "With each kill, your cooldown decreases", - "LookoutInfo": "See through disguises", - "TelecommunicationInfo": "Track device usage", - "LighterInfo": "Catch killers with your enhanced vision", - "TaskManagerInfo": "See the total tasks completed in real-time", - "WitnessInfo": "Find out if someone killed recently", - "GhastlyInfo": "Control somebody!", - "SwapperInfo": "Swap the votes of two players", - "NiceMiniInfo": "No one can hurt you until you grow up.", - "ArsonistInfo": "Douse everyone and ignite", - "PyromaniacInfo": "Douse and kill everyone", - "HuntsmanInfo": "Kill your targets for a low cooldown", - "SpyInfo": "You know who interacts with you", - "RandomizerInfo": "You're going to be someone's burden when you die?", - "EnigmaInfo": "Get Clues about Killers", - "JesterInfo": "Get voted out", - "OpportunistInfo": "Stay alive until the end", - "TerroristInfo": "Finish your tasks, THEN die", - "ExecutionerInfo": "Get your target voted out", - "LawyerInfo": "Help your target win!", - "VectorInfo": "Jump in! Jump out!", - "JackalInfo": "Murder everyone", - "GodInfo": "Everything is under your control", - "InnocentInfo": "Get someone ejected by making them kill you", - "PelicanInfo": "Eat all players", - "RevolutionistInfo": "Recruit players to win with you", - "HaterInfo": "Kill Lovers and Neptunes", - "DemonInfo": "Consume blood volumes", - "StalkerInfo": "Descend into the darkness, release fear!", - "WorkaholicInfo": "Finish all tasks to win solo!", - "SolsticerInfo": "Speed run all your tasks!", - "CollectorInfo": "Collect votes from players", - "ProvocateurInfo": "Victory with help target", - "BloodKnightInfo": "Killing gives you a temporary shield", - "PlagueBearerInfo": "Plague everyone to turn into Pestilence", - "PestilenceInfo": "Obliterate everyone!", - "SoulCollectorInfo": "Predict deaths to collect souls", - "DeathInfo": "Enact Armageddon", - "BakerInfo": "Feed Players Bread to become Famine", - "FamineInfo": "Starve Everyone", - "BerserkerInfo": "Kill to increase your level", - "WarInfo": "Destroy everything", - "GlitchInfo": "Hack and kill everyone", - "SidekickInfo": "Help the Jackal kill everyone", - "FollowerInfo": "Follow a player and help them", - "CultistInfo": "Charm everyone", - "SerialKillerInfo": "Kill off everyone to win!", - "JuggernautInfo": "With each kill, your cooldown decreases", - "InfectiousInfo": "Infect everyone", - "VirusInfo": "Kill and infect everyone", - "PursuerInfo": "Protect yourself and live to the end!", - "PlagueDoctorInfo": "Spread the infection!", - "SpecterInfo": "Get killed and finish your tasks to win!", - "PirateInfo": "Successfully plunder players to win", - "AgitaterInfo": "Pass a Bomb onto others", - "MaverickInfo": "Kill and survive to the end", - "CursedSoulInfo": "Snatch souls and steal the win", - "PickpocketInfo": "Steal votes from your kills", - "TraitorInfo": "Eliminate the Impostors, then win", - "VultureInfo": "Eat bodies by reporting to win", - "TaskinatorInfo": "Silent tasks, deadly blasts", - "BenefactorInfo": "Task complete, shield elite!", - "MedusaInfo": "Stone bodies by reporting them", - "SpiritcallerInfo": "Turn Players into Evil Spirits", - "AmnesiacInfo": "Remember the role of a dead body", - "ImitatorInfo": "Imitate a player's role", - "BanditInfo": "Rob a player's add-on", - "DoppelgangerInfo": "Steal your target's identity", - "PunchingBagInfo": "Get attacked a few times to win!", - "KamikazeInfo": "Kill players with a suicidal mission", - "DoomsayerInfo": "Successfully guess players to win", - "ShroudInfo": "Shroud players to make them kill", - "WerewolfInfo": "Kill crewmates in groups", - "ShamanInfo": "Deflect all the attacks on Voodoo doll", - "SeekerInfo": "Play Hide and Seek with your target", - "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", - "OccultistInfo": "Kill and curse your enemies", - "SchrodingersCatInfo": "The cat is both alive and dead until observed.", - "RomanticInfo": "Protect your partner to win together", - "VengefulRomanticInfo": "Revenge your partner to win together", - "RuthlessRomanticInfo": "Kill everyone to win with your partner", - "PoisonerInfo": "Kill everyone with delayed kills", - "HexMasterInfo": "Hex players to kill them in meetings", - "WraithInfo": "Vent to go invisible temporarily", - "JinxInfo": "Reflect attacks onto your attackers", - "PotionMasterInfo": "Use your potions to your advantage", - "NecromancerInfo": "Kill your killer to defy death", - "WardenInfo": "(Ghost) Alert about danger", - "MinionInfo": "(Ghost) Blind enemies", - "LoversInfo": "Stay alive and win together", - "MadmateInfo": "Help the Impostors", - "WatcherInfo": "You see all the colors of votes", - "LastImpostorInfo": "Lower kill cooldown", - "OverclockedInfo": "Lower cooldown", - "FlashInfo": "You're faster", - "TorchInfo": "You have enhanced vision!", - "SeerInfo": "You are alerted when somebody has died", - "TiebreakerInfo": "Break tied votes", - "ObliviousInfo": "You can't report bodies", - "BewilderInfo": "A twist of vision, a web of confusion", - "WorkhorseInfo": "Be the first to complete all tasks and get more", - "FoolInfo": "You can't fix sabotages", - "AvangerInfo": "You take someone with you upon death", - "YoutuberInfo": "Get killed first to win", - "CelebrityInfo": "Everyone knows when you die", - "EgoistInfo": "Win on your own", - "TicketsStealerInfo": "Gain votes with kills", - "ParanoiaInfo": "You're dead and alive simultaneously", - "MimicInfo": "Reveal killed players' roles to impostors upon death", - "GuesserInfo": "Guess roles of players in meetings to kill", - "NecroviewInfo": "See the team of the dead", - "ReachInfo": "You have a longer kill range", - "BaitInfo": "Your killer self-reports your body", - "TrapperInfo": "Freeze your killer for a few seconds", - "OnboundInfo": "You can't be guessed", - "ReboundInfo": "Guess me right, and face your plight!", - "MundaneInfo": "Tasks all done, guessing's begun.", - "UnreportableInfo": "Your body can't be reported", - "LuckyInfo": "Dodge attackers", - "DoubleShotInfo": "You have an extra life when guessing", - "RascalInfo": "You appear evil in some cases", - "SoullessInfo": "You have no soul", - "GravestoneInfo": "Your role is revealed when you die", - "LazyInfo": "You're too lazy", - "AutopsyInfo": "You see how others died", - "LoyalInfo": "You cannot be recruited", - "EvilSpiritInfo": "You are an evil Spirit", - "RecruitInfo": "Help the Jackal", - "AdmiredInfo": "The Admirer chose you as their love", - "GlowInfo": "You glow in the dark", - "RadarInfo": "Arrow's hue, closest to you!", - "DiseasedInfo": "Increase the cooldown of the player who interacts with you", - "AntidoteInfo": "Decrease the cooldown of the player who interacts with you", - "StubbornInfo": "Protect your role and add-ons", - "SwiftInfo": "Your kills don't cause a lunge", - "UnluckyInfo": "Doing things has a chance to kill you", - "VoidBallotInfo": "Your vote count is 0", - "AwareInfo": "Know who revealed your role", - "FragileInfo": "Die instantly if someone uses the kill button on you", - "GhoulInfo": "Kill your killer after dying", - "BloodthirstInfo": "Become bloodthirsty and kill", - "MareInfo": "Kill in the darkness", - "BurstInfo": "Make your killer burst!", - "SleuthInfo": "Gain info from dead bodies", - "ClumsyInfo": "You have a chance to miss your kill", - "NimbleInfo": "You can vent!", - "CircumventInfo": "You can no longer vent", - "OiiaiInfo": "OIIAIOIIIAI", - "CyberInfo": "You're popular!", - "HurriedInfo": "God, I got too much stuff!", - "InfluencedInfo": "You lack decisiveness!", - "SilentInfo": "Vote like a Ghost!", - "SusceptibleInfo": "Death-reason lotto!", - "TrickyInfo": "Tricky slays, in mysterious ways.", - "TiredInfo": "Labor makes you rest Zzz..", - "StatueInfo": "You're still as a rock nearby people", - "GMInfo": "Spectate the chaos!", - "NotAssignedInfo": "No assigned role", - "SunnyboyInfo": "Shine, shine my sunshine!", - "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", - "RainbowInfo": "Colorful melodies! You don't even know your own color.", - "DollMasterInfo": "Take control of players actions!", - "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", - "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", - "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", - "TrackerTOHEInfoLong": "(Crewmates):\nAs the Tracker, press your tracker button on a player to track their location via the map for a limited amount of time.", - "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", - "PhantomTOHEInfoLong": "(Impostors):\nAs the Phantom, you can press your vanish button to go invisible to escape a kill. You can click your appear button if you want to become visible before the timer runs out or not.\nNote: You will make a smoke cloud whenever you go invisible and become visible. So make sure you are in a safe area where no one will see you.", - "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", - "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", - "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", - "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased. The Target swaps after a certain amount of time.", - "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworks up to the maximum amount the host sets.\nWhen you are the last Impostor and all Fireworks have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworks, it's considered an Impostor victory.", - "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline, as shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", - "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", - "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. This means that even if a meeting is called first, your target still dies. However, if you bite a Bait, you kill normally and report the body. Depending on the settings, you can use double trigger (bite players - single click, kill normally - double click).", - "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", - "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown but moves very slowly and has very little vision. Zombie can not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", - "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark a target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", - "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", - "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", - "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown with tiny vision. However, you cannot vent, sabotage, report, nor call emergency meetings.\n\nNote: You will bypass any shields, killing bait and beartrap won't take any effect", - "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport; be careful).", - "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", - "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", - "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", - "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", - "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", - "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", - "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player, you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting, your marked location will reset.\n\nAfter every teleported kill, you will freeze for a configurable amount of time.", - "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker, you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out, therefore destroying all the rifts.\n\nNote: Up to two rifts can be placed at a time; if you try to place a third, it removes the first one.", - "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other players, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", - "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", - "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", - "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", - "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", - "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", - "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", - "TrapsterInfoLong": "(Impostors):\nThe Trapster has a unique method of killing. By initiating a body report, the Trapster can eliminate the player attempting to report the body the Trapster killed.\nNote: If Trapster kills the Bait, the Trapster will die immediately.", - "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the Host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", - "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned-up body cannot be reported (including bait).", - "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they encounter to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", - "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always odd.", - "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the max of times you can counterattack)", - "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", - "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the kill cooldown; he will kill even if it's still on cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", - "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shapeshifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", - "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA crew target can only be erased once (include Oiiai)", - "ButcherInfoLong": "(Impostors):\nThe Butcher's kills, including passive ones, leave multiple dead bodies on targets, which can be a bit confusing when reporting. Here's the rule: the killed target must repeatedly display the animation of being killed, which cannot be skipped, and they cannot participate in the meeting normally during this period. And if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", - "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", - "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", - "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you finish a task.", - "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but cannot vent.\nWhen you kill, you temporarily become immune to attacks.", - "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear as a crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", - "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", - "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", - "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", - "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", - "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not teleport after they shapeshift and players who are in the vent will not teleport.", - "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Lights or Reactor), you cannot kill.", - "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Comms or O2), then you can kill.", - "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nDepending on the settings, Councillor will suicide when he judge his teammates.\nConverted Councillor can judge freely.", - "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", - "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, You shapeshift to mark your targets for a deathpact.\nIf you have enough players marked for a death pact, they must meet within a specific period; if they fail to do so, they die.\nIf a marked player dies before the death pact becomes complete, the pact is withdrawn.", - "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to change the appearance of the target of the shapeshift permanently. Additionally, when each player's appearance changes, you will have your kill cooldown reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", - "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshifted.", - "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shapeshifting to swap the position of all players randomly. The swap happens twice, once when you start your shapeshift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone, and players in vents will not teleport.", - "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown resets to its original value.", - "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following information will be displayed on the players:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in the range of the infected player becomes infected themselves.\nInfection progress is cumulative and does not reset with distance or after meetings.", - "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", - "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", - "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", - "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", - "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round, if someone kills the target, the killer will turn into a Refugee.", - "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you have a charge bar which indicates when the slaughter is ready. When it is at 100% the next time you kill someone, you go into slaughter mode, meaning you can kill indefinitely until your bar runs out of charge. Otherwise, you have a normal KCD.", - "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized quickly, and their vision will be affected.", - "EvilMiniInfoLong": "(Impostors):\nAs the Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which gets drastically shortened as you grow up.", - "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", - "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate gets voted out in a meeting, if you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The Host determines the number of additional players dying.", - "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task. In addition, the Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for Anonymous, being marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", - "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only die when the Murderer is alone with the Super Star (regular kills only). In addition, the Guessers can't guess the Super Star. ", - "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", - "CleanserInfoLong": "(Crewmates):\nAs The Cleanser, you can vote to erase the add-ons of any target at the meeting. This erasure takes effect after the meeting ends. Depending on the settings, the cleansed player may never receive add-ons again.", - "KeeperInfoLong": "(Crewmates):\nAs keeper, you can vote for someone to protect them from being ejected. You can only do this a configurable number of times.", - "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. Depending on the settings, players can't see your extra votes, you can vent to call a meeting at any time, or you can have yourself revealed as Mayor upon task completion.", - "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting; at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", - "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, and Communications using only one side. You can fix Lights by flicking only one switch. Opening a door will open all doors in the map.", - "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", - "VigilanteInfoLong": "(Crewmates):\nAs the Vigilante, you are tasked with eliminating potential threats to the Crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster cannot convert Vigilante into madmate.", - "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or get voted (the vote count will be 0). The Jailer may choose to execute the prisoner by voting for them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote: Jailed players cannot be guessed or judged, and jailed players can only guess Jailer.", - "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see the Impostor's names displayed in red on the meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", - "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the Crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", - "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, the Doctor can access vitals wherever you are while he still has battery left.", - "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes for someone, the meeting will end on the spot, and the player they voted for will be ejected from the meeting. The moment the Dictator votes someone out, the Dictator will also die.", - "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the Host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", - "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", - "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", - "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role the guesser tried to guess, and you will also be notified in case of a misguess.", - "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill anyone but only do it once the whole game.", - "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", - "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", - "VeteranInfoLong": "(Crewmates):\nAs the Veteran, you can enter the alert state by venting. If a player tries to kill the Veteran in the alert state, the Veteran will kill the murderer instead. Veteran will see a shield animation on their body and a text above their head as a reminder when they enter and exit the alert state.", - "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though; crewmates can also be killed with the bombs.", - "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy the target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil with a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to Copycat after every meeting.", - "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate, and the murderer is an Impostor, the Bodyguard will not activate the skill.", - "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the killing ability has the counterfeit, he will commit suicide when he tries to kill someone next time.", - "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", - "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game. Depending on the settings, the target's shield can or cannot deactivate when the Medic dies. The Medic can also see if someone is trying to break the target's shield.\nDepending on the Host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", - "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote: If the setting to give random active players as a hint is on, you cannot check the same player multiple times.", - "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the Host). If it is wrong, the Judge commits suicide.\nCommand for judgment: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", - "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body, they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", - "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after someone reports a dead body. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question, which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", - "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. The shied animations typically indicate a role ability, so look out for this.", - "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has multiple votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exiled.", - "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", - "OverseerInfoLong": "(Crewmates):\nAs The Overseer, you have minimal vision, but you can use your kill button to reveal the role of a nearby player. A 「○」 will be displayed next to the revealed target after you use the kill button on them, and you will also be scanning them (only you can see this). Stay near the target for a defined time to reveal his role; if you move too far away, the reveal will cancel.", - "CoronerInfoLong": "(Crewmates):\nAs a Coroner, you can't report corpses; instead, after trying to report the corpse, you will see an arrow leading you to the killer. If someone calls a meeting, the arrows disappear. Depending on the settings, players can't report the body you found.", - "PresidentInfoLong": "(Crewmates):\nThe President has two abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President, and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", - "MerchantInfoLong": "(Crewmates):\nAs a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can prevent the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The money used is lost and not available for additional bribes.", - "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", - "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk, you can kill a limited amount of players decided by the host, though there's a chance you miss, slicing someone multiple times increases the chances.", - "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", - "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when someone calls a meeting.", - "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon task completion. Guessers can't even guess you in meetings.", - "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires, you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is 0 seconds, you still have a short time to vent.\nIf you don't make it, you die; if you make it, the suicide timer is reset.\nAlso after you vent, no one can interact with you for a defined period.\nAfter; the period is over, and you are immobilized for another defined period, and cannot report any bodies.", - "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you exit the vent, you will spawn near a random vent in the map (Except the one you used).", - "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", - "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double-click to kill normally. When you die, all marked also die, with death reason Targeted.", - "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by the Host.", - "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", - "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", - "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", - "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message if they are on the same team or a denial message if they are not on the same team.\n\nAll neutrals and converted players are counted in the same team. Trickster counts as Crew, and Rascal counts as Impostor.\nChecking command: /cmp [player id 1] [player id 2].", - "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non-crew role. Crewmates can see ☆besides Captain's name.\n\nIf anyone betrays the Captain's trust by voting Captain out, they will lose an add-on.", - "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", - "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", - "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", - "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", - "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", - "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", - "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", - "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real-time.", - "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings).", - "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", - "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, your survival is crucial. You can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses. This unique role adds a new dynamic to the game, where your survival is not just for your benefit but for the entire Crew's success.", - "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the kill button interaction is blocked, the player's cooldown will reset to 10s'", - "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player.", - "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the kill button on the player and following them for a few seconds. When the dousing starts, and it's successful, a shield animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", - "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting. You may have to report the body to receive a clue, depending on the settings. The more tasks you complete, the more precise the clues get.", - "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", - "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your kill cooldown goes down permanently by the set amount. However, if you kill someone who is not one of your targets, your kill cooldown permanently increases by the set amount. A colored name indicates your targets.", - "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini has two roles. A Nice or Evil Mini is chosen.\n\nUse'/r nice mini' and '/r evil mini' respectively for more details.", - "JesterInfoLong": "(Neutrals):\nIf the Jester gets voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", - "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", - "ExecutionerInfoLong": "(Neutrals):\nThe Executioner is a role with an execution target, indicated by a diamond symbol「♦」next to their name. If the execution target is killed, the Executioner's role will change to either Crewmate, Jester, or Opportunist, depending on the game settings. However, if the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", - "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", - "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", - "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", - "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill people normally (i.e., don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", - "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you steal the win, i.e., everyone else loses, and you win.", - "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target gets voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", - "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", - "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by the Host, to kill players (though they are still recruited). When the required number of players are recruited (displayed next to your name), you must vent within the specified time to win the game immediately with all your recruits. If you do not vent in time, you lose and die.", - "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, depending on the settings, you can only kill Lovers and other recruiting roles and add-ons. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", - "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", - "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause a Lights sabotage (if Lights sabotage is already active, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the Host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker wins alone.", - "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on the Host's settings, you can only win if you are alive and or revealed to everyone at the beginning (these settings are rarely both on).", - "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting finishes, your tasks reset, and you need to start all over again.\nVotes on the Solsticer will be directly canceled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in-game.", - "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required votes, the game ends, and you win alone, even if you voted a Jester or Executioner's target out.", - "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", - "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", - "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", - "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", - "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", - "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone at the meeting after you transform.", - "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after meeting)", - "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", - "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", - "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", - "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain the majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", - "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive.", - "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", - "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you can outnumber the Crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", - "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", - "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, survive to the end of the game.", - "SpecterInfoLong": "(Neutrals):\nAs the Specter, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", - "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both the Pirate and the target choose the same number, the Pirate wins.\nAdditionally, if the Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command: /duel X (where X can be 0, 1, or 2)\n\nYou win after winning a certain number of duels set by the Host.\n\nNote: The kill would not count towards pirate victory if the target did not participate in the duel.", - "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", - "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", - "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", - "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", - "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", - "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", - "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", - "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", - "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", - "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", - "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", - "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", - "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button one time to steal a player's addon and twice to kill. You may instantly steal the addon or after the meeting starts, depending on the settings. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable addons on the target or the target is stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can vent is on, Nimble will become unstealable.", - "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", - "PunchingBagInfoLong": "(Neutrals):\nAs the Punching Bag, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", - "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings), then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", - "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themselves after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", - "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", - "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.\nNote: If the killer cannot kill the chosen target, murder is canceled, but if the killer rechecks the Shaman, the killer will kill the Shaman.", - "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If the seeker tags the wrong player, a point is deducted, and if the seeker tags the correct player, a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target.\n\nThe seeker needs to collect a certain number of points set by the Host to win.", - "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark up to x amount of targets each round by using the kill button on them. You must have one of the marked targets ejected when the meeting starts. If unsuccessful, you will commit suicide, except if you didn't mark any targets or all the targets are dead. The selected targets reset to 0 after the meeting ends. If you succeed, you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the Host.", - "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use its kill button on you, the interaction is not blocked, and you will die.", - "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield that protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote: If your role changes, your win condition will be changed accordingly", - "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", - "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", - "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", - "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", - "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", - "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three different potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons default to killing.", - "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally when someone tries to kill you, you will block the kill, and you will teleport to a random vent. You will have a limited time to kill your killer. If you succeed in doing so, you live. If the time runs out before you kill your killer, you die permanently. If you try to kill someone else other than your killer, you will die.", - "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", - "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nThis feature is only assigned to roles with a kill button.", - "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when they are the last ones standing, and their victory is shared. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the Host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", - "MadmateInfoLong": "(Add-ons):\nOnly Crewmates can become Madmate. Madmate's task is to help the Impostors win the game. Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors, and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone, including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone\nPacifist => Their ability only works on Crewmates.", - "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", - "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the Host)", - "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", - "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", - "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreakers choose different tie targets simultaneously, the skills of the Tiebreaker will not take effect.", - "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. The Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still report automatically, and Oblivious can still be used as a scapegoat for Anonymous.", - "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder has died, the murderer's vision may become the same as the Bewilder's, depending on the settings.", - "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, and Workhorse will give the player extra tasks. The Host sets the number of additional tasks.", - "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", - "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out, and irregular kills will not count), the Avenger will revenge a random player.", - "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to die in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc., will not trigger the skills of the YouTuber.", - "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", - "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the Host sets the vote number, and the decimal is rounded down).\nAlso, extra votes from the Stealer are hidden during the meeting depending on the options.", - "ParanoiaInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Paranoia, you will be considered as two players in the game to determine when the game ends due to killers having the majority. Additionally, this grants you an extra vote, depending on options.", - "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called. This message will include information on roles which the Mimic killed.", - "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess the roles of players in meetings to kill them.\nGuessing the incorrect role kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", - "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", - "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. Unlike everyone else, you have the longest kill range possible in the game.", - "BaitInfoLong": "(Add-ons):\nWhen the Bait dies, the murderer who killed the Bait will self-report the Bait's body. However, this won't happen when a Scavenger, Cleaner, Swooper, Wraith, Medusa, or Killing Machine kills the Bait. The report may have a delay according to the Host's settings.", - "TrapperInfoLong": "(Add-ons):\nWhen Beartrap dies, Beartrap immobilizes killer for a configurable amount of time.", - "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", - "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", - "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", - "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", - "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", - "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess once you complete all of your tasks.", - "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", - "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse will be unreportable.", - "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", - "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on, there is a probability for you to evade the kill; the Host sets the specific probability. The killer will see the shield animation when the evasion takes effect, but you will not know anything.", - "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", - "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", - "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul steals your soul, you get this add-on.\n\nYou are not counted as alive.", - "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", - "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", - "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", - "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", - "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", - "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", - "GlowInfoLong": "(Add-ons):\nDuring lights out, you and players nearby you will receive a vision boost.", - "RadarInfoLong": "(Add-ons):\nAs Radar, you have arrows pointing towards the closest person at all times.", - "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be increased by a configurable amount of time.", - "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be decreased by a configurable amount of time.", - "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add-on, Eraser can't erase your role, Cleanser can't cleanse you, Bandit can't steal from you, and Monarch can't knight you.\nAdditionally, you can't gain any new add-ons from the Merchant.", - "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.\nNote: Swift also ignores Bait", - "UnluckyInfoLong": "(Add-ons):\nAs Unlucky, when you complete tasks, kill, venting, or open door, you have a chance to die.", - "VoidBallotInfoLong": "(Add-ons):\nHolder of this add-on will have 0 vote count.", - "AwareInfoLong": "(Add-ons):\nAs the Aware, you get a notification in the next meeting if a revealing role had interacted with you.", - "FragileInfoLong": "(Add-ons):\nAs Fragile, you will instantly die if someone tries to use the kill button on you (even if the role cannot directly kill).", - "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on task completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nThis is only assigned to crewmates, and not crewmates with no tasks or are task-based.", - "BloodthirstInfoLong": "(Add-ons):\nAs the Bloodthirst, doing tasks allows you to become bloodthirsty and kill players.\nWhen you finish a task, the next player you come in contact with dies.\n\nYour Bloodthirst remains after a meeting.\nUpon making a kill, your Bloodthirst clears till the next task you complete.\nBloodthirsts do not stack.\n\nOnly assigned to crewmates with tasks.", - "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", - "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", - "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", - "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset, and the target remains untouched.\n\nOnly assigned to killers.", - "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", - "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", - "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced, then the vote result won't shift\nCollector cannot become influenced.", - "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", - "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", - "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", - "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", - "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", - "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", - "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", - "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", - "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy.", - "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence does not affect the game, and all players know who the Game Master is. The Game Master role will be assigned to the Host, who will automatically become a ghost at the start of the game.", - "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. The game will not end when you are alive due to killers gaining the majority.\nAdditionally, you have access to portable vitals.", - "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown will be permanently halved.", - "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", - "GhastlyInfoLong": "(Crewmates [Ghost]):\nAs the Ghastly, possess an unsuspecting person, after that choose a target for them, now they'll only be able to use their kill (or kill ability) on target until you possess someone else or possess time runs out.", - "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", - "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", - "ShowTextOverlay": "Text Overlay", - "Overlay.GuesserMode": "Guesser Mode", - "Overlay.NoGameEnd": "No Game End", - "Overlay.DebugMode": "Debug Mode", - "Overlay.LowLoadMode": "Low Load Mode", - "Overlay.AllowConsole": "Console", - "DisableShieldAnimations": "Disable Unnecessary Shield Animations", - "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", - "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", - "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", - "AbilityUseLimit": "Initial Ability Use Limit", - "ShowArrows": "Has Arrows pointing toward bodies", - "ArrowDelayMin": "Minimum Arrow show-up delay", - "ArrowDelayMax": "Maximum Arrow show-up delay", - "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", - "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", - - "AbilityCD": "Ability Cooldown", - "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", - "ShowSpecificRole": "Know specific roles on Task Completion", - "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", - "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", - "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", - "DisableMeeting": "Disable Meetings", - "DisableCloseDoor": "Disable Doors Sabotage", - "DisableSabotage": "Disable Sabotages", - "NoGameEnd": "No Game End", - "AllowConsole": "BepInEx Console", - "DebugMode": "Debug Mode", - "SyncButtonMode": "Limit Meeting Times", - "RandomMapsMode": "Random Maps Mode", - "SyncedButtonCount": "Max Number of Emergency Meetings per Game", - "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", - "HHFailureKCDIncrease": "Kill cooldown increase on killing others", - "HHNumOfTargets": "Number of targets", - "Targets": "Targets: ", - "HHMaxKCD": "Maximum kill cooldown", - "HHMinKCD": "Minimum kill cooldown", - "AllAliveMeeting": "Meeting When No One is Dead", - "AllAliveMeetingTime": "Meeting Time When No One is Dead", - "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", - "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", - "AdditionalEmergencyCooldownTime": "Additional Cooldown", - "LadderDeath": "Fall From Ladders", - "LadderDeathChance": "Fall To Death Chance", - "EveryoneCanSeeDeathReason": "Everyone Can See Death Reason", - "DisableSwipeCardTask": "Disable Swipe Card Task", - "DisableSubmitScanTask": "Disable Submit Scan Task", - "DisableUnlockSafeTask": "Disable Unlock Safe Task", - "DisableUploadDataTask": "Disable Upload Data Task", - "DisableStartReactorTask": "Disable Start Reactor Task", - "DisableResetBreakerTask": "Disable Reset Breakers Task", - "DisableShortTasks": "Disable Short Tasks", - "DisableCleanVent": "Disable Clean Vent Task", - "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", - "DisableChartCourse": "Disable Chart Course Task", - "DisableStabilizeSteering": "Disable Stabilize Steering Task", - "DisableCleanO2Filter": "Disable Clean O2 Filter Task", - "DisableUnlockManifolds": "Disable Unlock Manifolds Task", - "DisablePrimeShields": "Disable Prime Shields Task", - "DisableMeasureWeather": "Disable Measure Weather", - "DisableBuyBeverage": "Disable Buy Beverage", - "DisableAssembleArtifact": "Disable Assemble Artifact Task", - "DisableSortSamples": "Disable Sort Samples Task", - "DisableProcessData": "Disable Process Data Task", - "DisableRunDiagnostics": "Disable Run Diagnostics Task", - "DisableRepairDrill": "Disable Repair Drill Task", - "DisableAlignTelescope": "Disable Align Telescope Task", - "DisableRecordTemperature": "Disable Record Temperature Task", - "DisableFillCanisters": "Disable Fill Canisters Task", - "DisableMonitorTree": "Disable Monitor Tree Task", - "DisableStoreArtifacts": "Disable Store Artifacts Task", - "DisablePutAwayPistols": "Disable Put Away Pistols Task", - "DisablePutAwayRifles": "Disable Put Away Rifles Task", - "DisableMakeBurger": "Disable Make Burger Task", - "DisableCleanToilet": "Disable Clean Toilet Task", - "DisableDecontaminate": "Disable Decontaminate Task", - "DisableSortRecords": "Disable Sort Records Task", - "DisableFixShower": "Disable Fix Shower Task", - "DisablePickUpTowels": "Disable Pick Up Towels Task", - "DisablePolishRuby": "Disable Polish Ruby Task", - "DisableDressMannequin": "Disable Dress Mannequin Task", - "DisableCommonTasks": "Disable Common Tasks", - "DisableFixWiring": "Disable Fix Wiring Task", - "DisableEnterIdCode": "Disable Enter ID Code Task", - "DisableInsertKeys": "Disable Insert Keys Task", - "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", - "DisableLongTasks": "Disable Long Tasks", - "DisableAlignEngineOutput": "Disable Align Engine Output Task", - "DisableInspectSample": "Disable Inspect Sample Task", - "DisableEmptyChute": "Disable Empty Chute Task", - "DisableClearAsteroids": "Disable Clear Asteroids Task", - "DisableWaterPlants": "Disable Water Plants Task", - "DisableOpenWaterways": "Disable Open Waterways Task", - "DisableReplaceWaterJug": "Disable Replace Water Jug Task", - "DisableRebootWifi": "Disable Reboot Wifi Task", - "DisableDevelopPhotos": "Disable Develop Photos Task", - "DisableRewindTapes": "Disable Rewind Tapes Task", - "DisableStartFans": "Disable Start Fans Task", - "DisableOtherTasks": "Disable Situational Tasks", - "DisableEmptyGarbage": "Disable Empty Garbage Task", - "DisableFuelEngines": "Disable Fuel Engines Task", - "DisableDivertPower": "Disable Divert Power Task", - "DisableActivateWeatherNodes": "Disable Weather Nodes Task", - "DisableRoastMarshmallow": "Disable Roast Marshmallow", - "DisableCollectSamples": "Disable Collect Samples", - "DisableReplaceParts": "Disable Replace Parts", - "DisableCollectVegetables": "Disable Collect Vegetables", - "DisableMineOres": "Disable Mine Ores", - "DisableExtractFuel": "Disable Extract Fuel", - "DisableCatchFish": "Disable Catch Fish", - "DisablePolishGem": "Disable Polish Gem", - "DisableHelpCritter": "Disable Help Critter", - "DisableHoistSupplies": "Disable Hoist Supplies", - "DisableFixAntenna": "Disable Fix Antenna", - "DisableBuildSandcastle": "Disable Build Sandcastle", - "DisableCrankGenerator": "Disable Crank Generator", - "DisableMonitorMushroom": "Disable Monitor Mushroom", - "DisablePlayVideoGame": "Disable Play Video Game", - "DisableFindSignal": "Disable Find Signal", - "DisableThrowFisbee": "Disable Throw Frisbee", - "DisableLiftWeights": "Disable Lift Weights", - "DisableCollectShells": "Disable Collect Shells", - "SuffixMode": "Suffix", - "SuffixMode.None": "None", - "SuffixMode.Version": "Version", - "SuffixMode.Streaming": "Streaming", - "SuffixMode.Recording": "Recording", - "SuffixMode.RoomHost": "Room Host", - "SuffixMode.OriginalName": "Original Name", - "SuffixMode.DoNotKillMe": "Don't kill me", - "SuffixMode.NoAndroidPlz": "No phones", - "SuffixMode.AutoHost": "Auto-Host", - "SuffixModeText.DoNotKillMe": "Don't kill me", - "SuffixModeText.NoAndroidPlz": "No phones please", - "SuffixModeText.AutoHost": "Auto-hosting", - "FormatNameMode": "Player Name Mode", - "FormatNameModes.None": "Disable", - "FormatNameModes.Color": "Color", - "FormatNameModes.Snacks": "Random", - "DisableEmojiName": "Disable Emoji in names", - "FixFirstKillCooldown": "Override Starting Kill Cooldown", - "FixKillCooldownValue": "Starting Kill Cooldown", - "OverclockedReduction": "Kill Cooldown Reduction", - "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", - "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", - "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", - "GhostIgnoreTasks": "Ghosts Exempt From Tasks", - "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", - "MaxImpGhostRole": "Max Impostor Ghost-Roles", - "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", - "DefaultAngelCooldown": "Default Ability Cooldown", - "DisableTaskWin": "Disable Task Win", - "DisableTaskWinIfAllCrewsAreDead": "Disable Task Win If All <#8cffff>Crewmates Are Dead", - "DisableTaskWinIfAllCrewsAreConverted": "Disable Task Win If All <#8cffff>Crewmates Are <#ffab1b>Converted", - "HideGameSettings": "Hide Game Settings", - "DIYGameSettings": "Enable only custom /n messages", - "Settings:": "Settings:", - "PlayerCanSetColor": "Players can use the /color command", - "PlayerCanSetName": "Players can use the /rn command", - "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", - "PlayerCanUseTP": "Players can use the /tpin and /tpout command", - "CanPlayMiniGames": "Players can play mini-games", - "KPDCamouflageMode": "Camouflage Appearance", - "RoleOptions": "Role Options", - "DarkTheme": "Enable Dark Theme", - "DisableLobbyMusic": "Disable Lobby Music", - "AutoStart": "Auto start", - "EnableCustomButton": "Enable Custom Button Images", - "EnableCustomSoundEffect": "Enable Custom Sound Effects", - "EnableCustomDecorations": "Enable Custom Map Decorations", - "SwitchVanilla": "Switch Vanilla", - "UnlockFPS": "Unlock FPS", - "ForceOwnLanguage": "Force mod to use your language if possible", - "ForceOwnLanguageRoleName": "Force role names in your language if possible", - "VersionCheat": "Bypass version synchronization check", - "GodMode": "God Mode", - - "AutoDisplayKillLog": "Display Kill-log", - "AutoDisplayLastRoles": "Display Last Roles", - "AutoDisplayLastResult": "Auto Display Last Result", - "RevertOldKillLog": "Revert to old kill-log", - - "HideExileChat": "Hide exile (lava) chat", - "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", + "LanguageID": "0", + "HostText": "Host", + "HostColor": "#902efd", + "IconColor": "#4bf4ff", + "Icon": "♥", + "NameColor": "#ffc0cb", + + "HideHostText": "Hide 'Host♥' Text", + "HideAllTagsAndText": "Hide All Tags (for «AutoMuteUs»)", + + "SupportUs": "Support Us", + "update": "Update", + "GitHub": "GitHub", + "Discord": "Discord", + "Website": "Website", + "PlayerNameForRoleInfo": "Hi {0}, your role is:- \n", + + "HostIconInMeeting": "HOST: {0}", + + "SubText.Crewmate": "Find and exile the Impostors", + "SubText.Impostor": "Sabotage and kill everyone", + "SubText.Neutral": "Work alone to achieve your victory", + "SubText.Apocalypse": "Become unstoppable with your team", + "SubText.Madmate": "Help the Impostors", + + "TypeImpostor": "Impostors", + "TypeCrewmate": "Crewmates", + "TypeNeutral": "Neutrals", + "TypeAddon": "Add-ons", + "GuesserMode": "Guesser Mode", + + "TeamImpostor": "Impostor", + "TeamNeutral": "Neutral", + "TeamCrewmate": "Crewmate", + "TeamMadmate": "Madmate", + + "YouAreCrewmate": "You are a Crewmate", + "YouAreImpostor": "You are an Impostor", + "YouAreNeutral": "You are a Neutral", + "YouAreMadmate": "You are a Madmate", + + + "Role_Crewmate": "Crewmate", + "Role_Jester": "Jester", + "Role_Opportunist": "Opportunist", + "Role_Celebrity": "Celebrity", + "Role_Bodyguard": "Bodyguard", + "Role_Dictator": "Dictator", + "Role_Mayor": "Mayor", + "Role_Doctor": "Doctor", + "Role_Maverick": "Maverick", + "Role_Pursuer": "Pursuer", + "Role_Follower": "Follower", + "Role_Amnesiac": "Amnesiac", + "Role_Imitator": "Imitator", + "Role_Sheriff": "Sheriff", + "Role_Knight": "Knight", + "Role_Deputy": "Deputy", + "Role_NoChange": "Don't change the role", + + "CrewmatesCanGuess": "Crewmates can guess", + "ImpostorsCanGuess": "Impostors can guess", + "NeutralKillersCanGuess": "Neutral Killers can guess", + "NeutralApocalypseCanGuess": "Neutral Apocalypse can guess", + "PassiveNeutralsCanGuess": "Passive Neutrals can guess", + + "CanGuessAddons": "Can Guess Add-ons", + "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", + "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", + "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", + "GuessImmune": "Sorry, but target is immune to being guessed!", + + + "GM": "Game Master", + "Sunnyboy": "Sunnyboy", + "Bard": "Bard", + "Crewmate": "Crewmate", + "CrewmateTOHE": "Crewmate", + "Engineer": "Engineer", + "EngineerTOHE": "Engineer", + "Scientist": "Scientist", + "ScientistTOHE": "Scientist", + "Noisemaker": "Noisemaker", + "NoisemakerTOHE": "Noisemaker", + "Tracker": "Tracker", + "TrackerTOHE": "Tracker", + "GuardianAngel": "Guardian Angel", + "GuardianAngelTOHE": "Guardian Angel", + "Impostor": "Impostor", + "ImpostorTOHE": "Impostor", + "Shapeshifter": "Shapeshifter", + "ShapeshifterTOHE": "Shapeshifter", + "Phantom": "Phantom", + "PhantomTOHE": "Phantom", + + "BountyHunter": "Bounty Hunter", + "Fireworker": "Fireworker", + "Mercenary": "Mercenary", + "ShapeMaster": "Shapemaster", + "Vampire": "Vampire", + "Warlock": "Warlock", + "Ninja": "Ninja", + "Zombie": "Zombie", + "Anonymous": "Anonymous", + "Miner": "Miner", + "KillingMachine": "Killing Machine", + "Escapist": "Escapist", + "Witch": "Witch", + "Nemesis": "Nemesis", + "Bloodmoon": "Bloodmoon", + "Puppeteer": "Puppeteer", + "Mastermind": "Mastermind", + "TimeThief": "Time Thief", + "Sniper": "Sniper", + "Undertaker": "Undertaker", + "RiftMaker": "Rift Maker", + "EvilTracker": "Evil Tracker", + "EvilHacker": "Evil Hacker", + "EvilGuesser": "Evil Guesser", + "AntiAdminer": "Anti Adminer", + "Arrogance": "Arrogance", + "Bomber": "Bomber", + "Scavenger": "Scavenger", + "Trapster": "Trapster", + "Gangster": "Gangster", + "Cleaner": "Cleaner", + "Lightning": "Lightning", + "Greedy": "Greedy", + "CursedWolf": "Cursed Wolf", + "SoulCatcher": "Soul Catcher", + "QuickShooter": "Quick Shooter", + "Camouflager": "Camouflager", + "Eraser": "Eraser", + "Butcher": "Butcher", + "Hangman": "Hangman", + "Swooper": "Swooper", + "Crewpostor": "Crewpostor", + "Wildling": "Wildling", + "Trickster": "Trickster", + "Vindicator": "Vindicator", + "Parasite": "Parasite", + "Disperser": "Disperser", + "Inhibitor": "Inhibitor", + "Saboteur": "Saboteur", + "Councillor": "Councillor", + "Dazzler": "Dazzler", + "Deathpact": "Deathpact", + "Devourer": "Devourer", + "Consigliere": "Consigliere", + "Morphling": "Morphling", + "Twister": "Twister", + "Lurker": "Lurker", + "Visionary": "Visionary", + "Refugee": "Refugee", + "Underdog": "Underdog", + "Ludopath": "Ludopath", + "Godfather": "Godfather", + "Chronomancer": "Chronomancer", + "Pitfall": "Pitfall", + "EvilMini": "Evil Mini", + "Blackmailer": "Blackmailer", + "Instigator": "Instigator", + "LazyGuy": "Lazy Guy", + "SuperStar": "Super Star", + "Celebrity": "Celebrity", + "Cleanser": "Cleanser", + "Keeper": "Keeper", + "Knight": "Knight", + "Mayor": "Mayor", + "Psychic": "Psychic", + "Mechanic": "Mechanic", + "Sheriff": "Sheriff", + "Vigilante": "Vigilante", + "Jailer": "Jailer", + "CopyCat": "Copycat", + "Snitch": "Snitch", + "Marshall": "Marshall", + "Doctor": "Doctor", + "Dictator": "Dictator", + "Detective": "Detective", + "NiceGuesser": "Nice Guesser", + "GuessMaster": "Guess Master", + "Transporter": "Transporter", + "TimeManager": "Time Manager", + "Veteran": "Veteran", + "Bastion": "Bastion", + "Bodyguard": "Bodyguard", + "Deceiver": "Deceiver", + "Grenadier": "Grenadier", + "Medic": "Medic", + "FortuneTeller": "Fortune Teller", + "Judge": "Judge", + "Mortician": "Mortician", + "Medium": "Medium", + "Pacifist": "Pacifist", + "Observer": "Observer", + "Monarch": "Monarch", + "Overseer": "Overseer", + "Coroner": "Coroner", + "Merchant": "Merchant", + "President": "President", + "Hawk": "Hawk", + "Retributionist": "Retributionist", + "Deputy": "Deputy", + "Investigator": "Investigator", + "Guardian": "Guardian", + "Addict": "Addict", + "Mole": "Mole", + "Alchemist": "Alchemist", + "Tracefinder": "Tracefinder", + "Oracle": "Oracle", + "Spiritualist": "Spiritualist", + "Chameleon": "Chameleon", + "Inspector": "Inspector", + "Captain": "Captain", + "Admirer": "Admirer", + "TimeMaster": "Time Master", + "Crusader": "Crusader", + "Reverie": "Reverie", + "Lookout": "Lookout", + "Telecommunication": "Telecommunication", + "Lighter": "Lighter", + "TaskManager": "Task Manager", + "Witness": "Witness", + "Swapper": "Swapper", + "NiceMini": "Nice Mini", + "Mini": "Mini", + "Spy": "Spy", + "Randomizer": "Randomizer", + "Enigma": "Enigma", + "Jester": "Jester", + "Arsonist": "Arsonist", + "Pyromaniac": "Pyromaniac", + "Kamikaze": "Kamikaze", + "Huntsman": "Huntsman", + "Terrorist": "Terrorist", + "Executioner": "Executioner", + "Lawyer": "Lawyer", + "Opportunist": "Opportunist", + "Vector": "Vector", + "Jackal": "Jackal", + "God": "God", + "Innocent": "Innocent", + "Stealth": "Stealth", + "Penguin": "Penguin", + "Pelican": "Pelican", + "PlagueDoctor": "Plague Scientist", + "Revolutionist": "Revolutionist", + "Hater": "Hater", + "Demon": "Demon", + "Stalker": "Stalker", + "Workaholic": "Workaholic", + "Solsticer": "Solsticer", + "Collector": "Collector", + "Provocateur": "Provocateur", + "BloodKnight": "Blood Knight", + "Apocalypse": "Apocalypse", + "PlagueBearer": "Plaguebearer", + "Pestilence": "Pestilence", + "SoulCollector": "Soul Collector", + "Death": "Death", + "Baker": "Baker", + "Famine": "Famine", + "Berserker": "Berserker", + "War": "War", + "Glitch": "Glitch", + "Sidekick": "Sidekick", + "Follower": "Follower", + "Cultist": "Cultist", + "SerialKiller": "Serial Killer", + "Juggernaut": "Juggernaut", + "Infectious": "Infectious", + "Virus": "Virus", + "Pursuer": "Pursuer", + "Specter": "Specter", + "Pirate": "Pirate", + "Agitater": "Agitator", + "Maverick": "Maverick", + "CursedSoul": "Cursed Soul", + "Pickpocket": "Pickpocket", + "Traitor": "Traitor", + "Vulture": "Vulture", + "Taskinator": "Taskinator", + "Benefactor": "Benefactor", + "Medusa": "Medusa", + "Spiritcaller": "Spiritcaller", + "Amnesiac": "Amnesiac", + "Imitator": "Imitator", + "Bandit": "Bandit", + "Doppelganger": "Doppelganger", + "PunchingBag": "Punching Bag", + "Doomsayer": "Doomsayer", + "Shroud": "Shroud", + "Werewolf": "Werewolf", + "Shaman": "Shaman", + "Seeker": "Seeker", + "Pixie": "Pixie", + "Occultist": "Occultist", + "SchrodingersCat": "Schrodingers Cat", + "Romantic": "Romantic", + "VengefulRomantic": "Vengeful Romantic", + "RuthlessRomantic": "Ruthless Romantic", + "Poisoner": "Poisoner", + "HexMaster": "Hex Master", + "Wraith": "Wraith", + "Jinx": "Jinx", + "PotionMaster": "Potion Master", + "Necromancer": "Necromancer", + "Warden": "Warden", + "Minion": "Minion", + "Ghastly": "Ghastly", + "LastImpostor": "Last Impostor", + "Overclocked": "Overclocked", + "Lovers": "Lovers", + "Madmate": "Madmate", + "Watcher": "Watcher", + "Flash": "Flash", + "Torch": "Torch", + "Seer": "Seer", + "Tiebreaker": "Tiebreaker", + "Oblivious": "Oblivious", + "Bewilder": "Bewilder", + "Workhorse": "Workhorse", + "Fool": "Fool", + "Avanger": "Avenger", + "Youtuber": "YouTuber", + "Egoist": "Egoist", + "TicketsStealer": "Stealer", + "Paranoia": "Paranoia", + "Mimic": "Mimic", + "Guesser": "Guesser", + "Necroview": "Necroview", + "Reach": "Reach", + "Charmed": "Charmed", + "Cleansed": "Cleansed", + "Bait": "Bait", + "Trapper": "Beartrap", + "Infected": "Infected", + "Onbound": "Onbound", + "Rebound": "Rebound", + "Mundane": "Mundane", + "Knighted": "Knighted", + "Unreportable": "Disregarded", + "Contagious": "Contagious", + "Lucky": "Lucky", + "Unlucky": "Unlucky", + "VoidBallot": "Void Ballot", + "Aware": "Aware", + "Fragile": "Fragile", + "DoubleShot": "Double Shot", + "Rascal": "Rascal", + "Soulless": "Soulless", + "Gravestone": "Gravestone", + "Lazy": "Lazy", + "Autopsy": "Autopsy", + "Loyal": "Loyal", + "EvilSpirit": "Evil Spirit", + "Recruit": "Recruit", + "Admired": "Admired", + "Glow": "Glow", + "Radar": "Radar", + "Diseased": "Diseased", + "Antidote": "Antidote", + "Stubborn": "Stubborn", + "Swift": "Swift", + "Ghoul": "Ghoul", + "Bloodthirst": "Bloodthirst", + "Mare": "Mare", + "Burst": "Burst", + "Sleuth": "Sleuth", + "Clumsy": "Clumsy", + "Nimble": "Nimble", + "Circumvent": "Circumvent", + "Cyber": "Cyber", + "Hurried": "Hurried", + "Oiiai": "OIIAI", + "Influenced": "Influenced", + "Silent": "Silent", + "Susceptible": "Susceptible", + "Tricky": "Tricky", + "Rainbow": "Rainbow", + "Tired": "Tired", + "Statue": "Statue", + "DollMaster": "Dollmaster", + "BracketAddons": "Add Brackets To Add-ons", + "EngineerTOHEInfo": "Use the vents to catch the Impostors", + "ScientistTOHEInfo": "Access portable vitals from anywhere", + "NoisemakerTOHEInfo": "Send out an alert when killed", + "TrackerTOHEInfo": "Track a players with your map", + "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", + "PhantomTOHEInfo": "Turn invisible", + "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", + "ImpostorTOHEInfo": "Kill and sabotage", + "CrewmateTOHEInfo": "Search for the Impostors", + "BountyHunterInfo": "Eliminate your target", + "FireworkerInfo": "Go out with a BANG", + "MercenaryInfo": "Keep killing, else you suicide", + "ShapeMasterInfo": "Swiftly kill with no shift cooldown", + "VampireInfo": "Your kills are delayed", + "WarlockInfo": "Curse crewmates then shift to make them kill", + "NinjaInfo": "Mark a target, then shift to kill", + "ZombieInfo": "You are very slow", + "AnonymousInfo": "Force a player to report a body", + "MinerInfo": "Warp to your last used vent by shifting", + "KillingMachineInfo": "You can ONLY kill, but low cooldown", + "EscapistInfo": "Shift to mark places and warp back to them", + "WitchInfo": "Spell crewmates to kill them in meetings", + "NemesisInfo": "Kill when you're the last Impostor", + "BeforeNemesisInfo": "You can't kill yet", + "AfterNemesisInfo": "Now start killing", + "BloodmoonInfo": "Seek havoc upon the crewmates", + "PuppeteerInfo": "Make players kill for you", + "MastermindInfo": "Make others kill for you", + "TimeThiefInfo": "Lower meeting time by killing", + "SniperInfo": "Snipe players from a distance by shifting", + "UndertakerInfo": "Teleport dead body to a marked location", + "RiftMakerInfo": "Two rifts I trace, touch 'em to warp space", + "EvilTrackerInfo": "Track players by shifting", + "EvilHackerInfo": "Hack systems", + "AntiAdminerInfo": "Know when players are near devices", + "ArroganceInfo": "With each kill you make, your cooldown decreases", + "BomberInfo": "Shapeshift to explode", + "TrapsterInfo": "Trap your kills", + "ScavengerInfo": "Your kills are unreportable", + "EvilGuesserInfo": "Guess crew roles in meetings to kill", + "GangsterInfo": "Convert players to your side", + "CleanerInfo": "Report bodies to make them unreportable", + "LightningInfo": "Convert players to Quantum Ghosts", + "GreedyInfo": "Your kill cooldown shifts", + "CursedWolfInfo": "You survive a few kill attempts", + "SoulCatcherInfo": "You swap places with your shift target", + "QuickShooterInfo": "Store ammo to offset kill cooldown", + "CamouflagerInfo": "Camouflage everyone for easy kills", + "EraserInfo": "Erase the role of your vote target", + "ButcherInfo": "Enjoy my beautiful work", + "HangmanInfo": "I will decide when your life will end", + "SwooperInfo": "Turn invisible temporarily", + "CrewpostorInfo": "Kill by completing tasks", + "WildlingInfo": "Kill with strength and disguise", + "TricksterInfo": "Kill and trick the crew", + "VindicatorInfo": "Use your extra votes to kill everyone", + "ParasiteInfo": "Help the Impostors kill the crew", + "DisperserInfo": "Teleport everyone to random vents", + "InhibitorInfo": "You cannot kill during sabotages", + "SaboteurInfo": "You can only kill during sabotages", + "CouncillorInfo": "Kill off crewmates during meetings", + "DazzlerInfo": "Reduce the vision of the crew", + "DeathpactInfo": "Assign players to a death pact", + "DevourerInfo": "Consume the skin of the crew", + "ConsigliereInfo": "Discover the roles of other players", + "MorphlingInfo": "You can only kill while shapeshifted", + "TwisterInfo": "Swap all player positions", + "LurkerInfo": "Reduce your kill cooldown by venting", + "ConvictInfo": "Your target died, now help the Impostors", + "VisionaryInfo": "You see the alignments of the living", + "RefugeeInfo": "Help the Impostors kill off the crew", + "UnderdogInfo": "Start killing on a low player count", + "LudopathInfo": "Your kill cooldown is random", + "GodfatherInfo": "Convert players to Refugees by voting", + "ChronomancerInfo": "Kill in bursts", + "PitfallInfo": "Setup traps around the map", + "EvilMiniInfo": "No one can hurt you until you grow up", + "BlackmailerInfo": "Silence other players", + "InstigatorInfo": "Sow discord among the crewmates", + "LazyGuyInfo": "You're too lazy", + "SuperStarInfo": "Everyone knows you", + "CleanserInfo": "Erase All Add-ons of your vote target", + "KeeperInfo": "Reject the Eject, Keeper Protect!", + "MayorInfo": "Your vote counts multiple times", + "PsychicInfo": "One of the red names are evil", + "MechanicInfo": "Vent around and fix sabotages", + "SheriffInfo": "Shoot the Impostors", + "VigilanteInfo": "Not the hero we deserved but the hero we needed", + "JailerInfo": "Jail suspicious players", + "CopyCatInfo": "Use kill button to copy target's role", + "SnitchInfo": "Finish your tasks to find the Impostors", + "MarshallInfo": "Finish your tasks to prove your innocence", + "DoctorInfo": "Know how each player died", + "DictatorInfo": "Exile a player based on your own judgment", + "DetectiveInfo": "Gain extra info from your body reports", + "UndercoverInfo": "Impostors see you as their partner", + "KnightInfo": "You can kill 1 player", + "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", + "GuessMasterInfo": "Whispers heard, every guessed word.", + "TransporterInfo": "Do tasks to swap two players' locations", + "TimeManagerInfo": "Increase meeting time by doing tasks", + "VeteranInfo": "Alert to kill anyone who interacts with you", + "BastionInfo": "Bomb vents", + "BodyguardInfo": "Prevent nearby kills", + "DeceiverInfo": "Try to fool the players", + "GrenadierInfo": "Reduce Impostors' vision by venting", + "MedicInfo": "Cast a shield onto a player", + "FortuneTellerInfo": "Get clues to people's roles", + "JudgeInfo": "Silence in the courtroom!", + "MorticianInfo": "Locate dead bodies", + "MediumInfo": "Talk with ghosts", + "ObserverInfo": "You can see all shield-animations", + "PacifistInfo": "Vent to reset kill cooldowns", + "MonarchInfo": "Give your crew extra voting power!", + "StealthInfo": "Killing Blinds Everyone in the Room", + "PenguinInfo": "Drag your victims", + "OverseerInfo": "Reveal roles of other players", + "CoronerInfo": "Find corpses and their killers", + "PresidentInfo": "You are in charge of the meeting", + "MerchantInfo": "Sell add-ons and bribe killers", + "RetributionistInfo": "Help the crew after you die", + "HawkInfo": "Seek murdering the bad guys!", + "DeputyInfo": "Handcuff killers to increase their cooldowns", + "InvestigatorInfo": "Find potential evils", + "GuardianInfo": "Complete your tasks to become immortal", + "AddictInfo": "Vent to become invulnerable, or you'll die", + "MoleInfo": "Vanish and reappear, the Mole's game is crystal clear!", + "AlchemistInfo": "Brew potions by completing tasks", + "TracefinderInfo": "Sense the location of dead bodies", + "OracleInfo": "Vote a player to see their alignment", + "SpiritualistInfo": "Be guided by the ghostly life", + "ChameleonInfo": "Vent to disguise into your surroundings", + "InspectorInfo": "Validate the alignments of two players", + "CaptainInfo": "Sail with the Captain, lest add-ons be abandoned.", + "AdmirerInfo": "Choose a player to side with you", + "TimeMasterInfo": "Rewind time!", + "CrusaderInfo": "Kill a player's attacker", + "ReverieInfo": "With each kill, your cooldown decreases", + "LookoutInfo": "See through disguises", + "TelecommunicationInfo": "Track device usage", + "LighterInfo": "Catch killers with your enhanced vision", + "TaskManagerInfo": "See the total tasks completed in real-time", + "WitnessInfo": "Find out if someone killed recently", + "GhastlyInfo": "Control somebody!", + "SwapperInfo": "Swap the votes of two players", + "NiceMiniInfo": "No one can hurt you until you grow up.", + "ArsonistInfo": "Douse everyone and ignite", + "PyromaniacInfo": "Douse and kill everyone", + "HuntsmanInfo": "Kill your targets for a low cooldown", + "SpyInfo": "You know who interacts with you", + "RandomizerInfo": "You're going to be someone's burden when you die?", + "EnigmaInfo": "Get Clues about Killers", + "JesterInfo": "Get voted out", + "OpportunistInfo": "Stay alive until the end", + "TerroristInfo": "Finish your tasks, THEN die", + "ExecutionerInfo": "Get your target voted out", + "LawyerInfo": "Help your target win!", + "VectorInfo": "Jump in! Jump out!", + "JackalInfo": "Murder everyone", + "GodInfo": "Everything is under your control", + "InnocentInfo": "Get someone ejected by making them kill you", + "PelicanInfo": "Eat all players", + "RevolutionistInfo": "Recruit players to win with you", + "HaterInfo": "Kill Lovers and Neptunes", + "DemonInfo": "Consume blood volumes", + "StalkerInfo": "Descend into the darkness, release fear!", + "WorkaholicInfo": "Finish all tasks to win solo!", + "SolsticerInfo": "Speed run all your tasks!", + "CollectorInfo": "Collect votes from players", + "ProvocateurInfo": "Victory with help target", + "BloodKnightInfo": "Killing gives you a temporary shield", + "PlagueBearerInfo": "Plague everyone to turn into Pestilence", + "PestilenceInfo": "Obliterate everyone!", + "SoulCollectorInfo": "Predict deaths to collect souls", + "DeathInfo": "Enact Armageddon", + "BakerInfo": "Feed Players Bread to become Famine", + "FamineInfo": "Starve Everyone", + "BerserkerInfo": "Kill to increase your level", + "WarInfo": "Destroy everything", + "GlitchInfo": "Hack and kill everyone", + "SidekickInfo": "Help the Jackal kill everyone", + "FollowerInfo": "Follow a player and help them", + "CultistInfo": "Charm everyone", + "SerialKillerInfo": "Kill off everyone to win!", + "JuggernautInfo": "With each kill, your cooldown decreases", + "InfectiousInfo": "Infect everyone", + "VirusInfo": "Kill and infect everyone", + "PursuerInfo": "Protect yourself and live to the end!", + "PlagueDoctorInfo": "Spread the infection!", + "SpecterInfo": "Get killed and finish your tasks to win!", + "PirateInfo": "Successfully plunder players to win", + "AgitaterInfo": "Pass a Bomb onto others", + "MaverickInfo": "Kill and survive to the end", + "CursedSoulInfo": "Snatch souls and steal the win", + "PickpocketInfo": "Steal votes from your kills", + "TraitorInfo": "Eliminate the Impostors, then win", + "VultureInfo": "Eat bodies by reporting to win", + "TaskinatorInfo": "Silent tasks, deadly blasts", + "BenefactorInfo": "Task complete, shield elite!", + "MedusaInfo": "Stone bodies by reporting them", + "SpiritcallerInfo": "Turn Players into Evil Spirits", + "AmnesiacInfo": "Remember the role of a dead body", + "ImitatorInfo": "Imitate a player's role", + "BanditInfo": "Rob a player's add-on", + "DoppelgangerInfo": "Steal your target's identity", + "PunchingBagInfo": "Get attacked a few times to win!", + "KamikazeInfo": "Kill players with a suicidal mission", + "DoomsayerInfo": "Successfully guess players to win", + "ShroudInfo": "Shroud players to make them kill", + "WerewolfInfo": "Kill crewmates in groups", + "ShamanInfo": "Deflect all the attacks on Voodoo doll", + "SeekerInfo": "Play Hide and Seek with your target", + "PixieInfo": "Tag 'em, Bag 'em, and Eject 'em!", + "OccultistInfo": "Kill and curse your enemies", + "SchrodingersCatInfo": "The cat is both alive and dead until observed.", + "RomanticInfo": "Protect your partner to win together", + "VengefulRomanticInfo": "Revenge your partner to win together", + "RuthlessRomanticInfo": "Kill everyone to win with your partner", + "PoisonerInfo": "Kill everyone with delayed kills", + "HexMasterInfo": "Hex players to kill them in meetings", + "WraithInfo": "Vent to go invisible temporarily", + "JinxInfo": "Reflect attacks onto your attackers", + "PotionMasterInfo": "Use your potions to your advantage", + "NecromancerInfo": "Kill your killer to defy death", + "WardenInfo": "(Ghost) Alert about danger", + "MinionInfo": "(Ghost) Blind enemies", + "LoversInfo": "Stay alive and win together", + "MadmateInfo": "Help the Impostors", + "WatcherInfo": "You see all the colors of votes", + "LastImpostorInfo": "Lower kill cooldown", + "OverclockedInfo": "Lower cooldown", + "FlashInfo": "You're faster", + "TorchInfo": "You have enhanced vision!", + "SeerInfo": "You are alerted when somebody has died", + "TiebreakerInfo": "Break tied votes", + "ObliviousInfo": "You can't report bodies", + "BewilderInfo": "A twist of vision, a web of confusion", + "WorkhorseInfo": "Be the first to complete all tasks and get more", + "FoolInfo": "You can't fix sabotages", + "AvangerInfo": "You take someone with you upon death", + "YoutuberInfo": "Get killed first to win", + "CelebrityInfo": "Everyone knows when you die", + "EgoistInfo": "Win on your own", + "TicketsStealerInfo": "Gain votes with kills", + "ParanoiaInfo": "You're dead and alive simultaneously", + "MimicInfo": "Reveal killed players' roles to impostors upon death", + "GuesserInfo": "Guess roles of players in meetings to kill", + "NecroviewInfo": "See the team of the dead", + "ReachInfo": "You have a longer kill range", + "BaitInfo": "Your killer self-reports your body", + "TrapperInfo": "Freeze your killer for a few seconds", + "OnboundInfo": "You can't be guessed", + "ReboundInfo": "Guess me right, and face your plight!", + "MundaneInfo": "Tasks all done, guessing's begun.", + "UnreportableInfo": "Your body can't be reported", + "LuckyInfo": "Dodge attackers", + "DoubleShotInfo": "You have an extra life when guessing", + "RascalInfo": "You appear evil in some cases", + "SoullessInfo": "You have no soul", + "GravestoneInfo": "Your role is revealed when you die", + "LazyInfo": "You're too lazy", + "AutopsyInfo": "You see how others died", + "LoyalInfo": "You cannot be recruited", + "EvilSpiritInfo": "You are an evil Spirit", + "RecruitInfo": "Help the Jackal", + "AdmiredInfo": "The Admirer chose you as their love", + "GlowInfo": "You glow in the dark", + "RadarInfo": "Arrow's hue, closest to you!", + "DiseasedInfo": "Increase the cooldown of the player who interacts with you", + "AntidoteInfo": "Decrease the cooldown of the player who interacts with you", + "StubbornInfo": "Protect your role and add-ons", + "SwiftInfo": "Your kills don't cause a lunge", + "UnluckyInfo": "Doing things has a chance to kill you", + "VoidBallotInfo": "Your vote count is 0", + "AwareInfo": "Know who revealed your role", + "FragileInfo": "Die instantly if someone uses the kill button on you", + "GhoulInfo": "Kill your killer after dying", + "BloodthirstInfo": "Become bloodthirsty and kill", + "MareInfo": "Kill in the darkness", + "BurstInfo": "Make your killer burst!", + "SleuthInfo": "Gain info from dead bodies", + "ClumsyInfo": "You have a chance to miss your kill", + "NimbleInfo": "You can vent!", + "CircumventInfo": "You can no longer vent", + "OiiaiInfo": "OIIAIOIIIAI", + "CyberInfo": "You're popular!", + "HurriedInfo": "God, I got too much stuff!", + "InfluencedInfo": "You lack decisiveness!", + "SilentInfo": "Vote like a Ghost!", + "SusceptibleInfo": "Death-reason lotto!", + "TrickyInfo": "Tricky slays, in mysterious ways.", + "TiredInfo": "Labor makes you rest Zzz..", + "StatueInfo": "You're still as a rock nearby people", + "GMInfo": "Spectate the chaos!", + "NotAssignedInfo": "No assigned role", + "SunnyboyInfo": "Shine, shine my sunshine!", + "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", + "RainbowInfo": "Colorful melodies! You don't even know your own color.", + "DollMasterInfo": "Take control of players actions!", + "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", + "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", + "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", + "TrackerTOHEInfoLong": "(Crewmates):\nAs the Tracker, press your tracker button on a player to track their location via the map for a limited amount of time.", + "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", + "PhantomTOHEInfoLong": "(Impostors):\nAs the Phantom, you can press your vanish button to go invisible to escape a kill. You can click your appear button if you want to become visible before the timer runs out or not.\nNote: You will make a smoke cloud whenever you go invisible and become visible. So make sure you are in a safe area where no one will see you.", + "GuardianAngelTOHEInfoLong": "(Crewmates):\nAs the Guardian Angel, you are the first crewmate to die and can give Crewmates temporary shields.", + "ImpostorTOHEInfoLong": "(Impostors):\nAs the Impostor, your goal is to simply kill off the crewmates.\nYou can sabotage and vent.", + "CrewmateTOHEInfoLong": "(Crewmates):\nAs the Crewmate, your goal is to find and exile the Impostors.\nCrewmates win by getting rid of all killers or by finishing all their tasks.", + "BountyHunterInfoLong": "(Impostors):\nAs the Bounty Hunter, if you kill your assigned Target (indicated by the arrow if you have one), your next kill cooldown will be shortened.\nIf you kill anyone other than your target, your next kill cooldown will be increased. The Target swaps after a certain amount of time.", + "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworks up to the maximum amount the host sets.\nWhen you are the last Impostor and all Fireworks have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworks, it's considered an Impostor victory.", + "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline, as shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", + "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", + "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. This means that even if a meeting is called first, your target still dies. However, if you bite a Bait, you kill normally and report the body. Depending on the settings, you can use double trigger (bite players - single click, kill normally - double click).", + "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", + "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown but moves very slowly and has very little vision. Zombie can not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", + "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark a target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", + "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", + "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", + "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown with tiny vision. However, you cannot vent, sabotage, report, nor call emergency meetings.\n\nNote: You will bypass any shields, killing bait and beartrap won't take any effect", + "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport; be careful).", + "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", + "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", + "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", + "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", + "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", + "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", + "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift into a player, you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting, your marked location will reset.\n\nAfter every teleported kill, you will freeze for a configurable amount of time.", + "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker, you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out, therefore destroying all the rifts.\n\nNote: Up to two rifts can be placed at a time; if you try to place a third, it removes the first one.", + "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other players, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", + "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", + "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", + "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", + "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", + "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", + "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", + "TrapsterInfoLong": "(Impostors):\nThe Trapster has a unique method of killing. By initiating a body report, the Trapster can eliminate the player attempting to report the body the Trapster killed.\nNote: If Trapster kills the Bait, the Trapster will die immediately.", + "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the Host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", + "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned-up body cannot be reported (including bait).", + "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they encounter to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", + "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always odd.", + "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the max of times you can counterattack)", + "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", + "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the kill cooldown; he will kill even if it's still on cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", + "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shapeshifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", + "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA crew target can only be erased once (include Oiiai)", + "ButcherInfoLong": "(Impostors):\nThe Butcher's kills, including passive ones, leave multiple dead bodies on targets, which can be a bit confusing when reporting. Here's the rule: the killed target must repeatedly display the animation of being killed, which cannot be skipped, and they cannot participate in the meeting normally during this period. And if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", + "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", + "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can vent to vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", + "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you finish a task.", + "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but cannot vent.\nWhen you kill, you temporarily become immune to attacks.", + "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear as a crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", + "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", + "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", + "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", + "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", + "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not teleport after they shapeshift and players who are in the vent will not teleport.", + "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Lights or Reactor), you cannot kill.", + "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Comms or O2), then you can kill.", + "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nDepending on the settings, Councillor will suicide when he judge his teammates.\nConverted Councillor can judge freely.", + "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", + "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, You shapeshift to mark your targets for a deathpact.\nIf you have enough players marked for a death pact, they must meet within a specific period; if they fail to do so, they die.\nIf a marked player dies before the death pact becomes complete, the pact is withdrawn.", + "DevourerInfoLong": "(Impostors):\nAs the Devourer, you use your shapeshift to change the appearance of the target of the shapeshift permanently. Additionally, when each player's appearance changes, you will have your kill cooldown reduced by a defined number of seconds. If the Devourer dies or gets voted out during a meeting, the player's appearance will change back to their normal appearance.", + "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshifted.", + "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shapeshifting to swap the position of all players randomly. The swap happens twice, once when you start your shapeshift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone, and players in vents will not teleport.", + "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown resets to its original value.", + "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following information will be displayed on the players:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in the range of the infected player becomes infected themselves.\nInfection progress is cumulative and does not reset with distance or after meetings.", + "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either an Amnesiac who remembered an Impostor or a killer who killed the Godfather's target.\n\nNow your job is to help the Impostors kill the crewmates.", + "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", + "ConsigliereInfoLong": "(Impostors):\nAs the Consigliere, you can reveal the roles of other players using your kill button.\n\nSingle click: Reveal role\nDouble click: Kill\n\nIf you run out of reveal uses, your kill button functions normally.", + "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your kill cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default kill cooldown.", + "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round, if someone kills the target, the killer will turn into a Refugee.", + "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you have a charge bar which indicates when the slaughter is ready. When it is at 100% the next time you kill someone, you go into slaughter mode, meaning you can kill indefinitely until your bar runs out of charge. Otherwise, you have a normal KCD.", + "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your shapeshift to mark the area around the shapeshift as a trap. Players who enter this area will be immobilized quickly, and their vision will be affected.", + "EvilMiniInfoLong": "(Impostors):\nAs the Evil Mini, you are unkillable until you grow up and have a very long initial kill cooldown, which gets drastically shortened as you grow up.", + "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", + "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate gets voted out in a meeting, if you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The Host determines the number of additional players dying.", + "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task. In addition, the Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for Anonymous, being marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", + "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only die when the Murderer is alone with the Super Star (regular kills only). In addition, the Guessers can't guess the Super Star. ", + "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", + "CleanserInfoLong": "(Crewmates):\nAs The Cleanser, you can vote to erase the add-ons of any target at the meeting. This erasure takes effect after the meeting ends. Depending on the settings, the cleansed player may never receive add-ons again.", + "KeeperInfoLong": "(Crewmates):\nAs keeper, you can vote for someone to protect them from being ejected. You can only do this a configurable number of times.", + "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. Depending on the settings, players can't see your extra votes, you can vent to call a meeting at any time, or you can have yourself revealed as Mayor upon task completion.", + "PsychicInfoLong": "(Crewmates):\nThe Psychic can see the names of several players highlighted in red during the meeting; at least one of them is evil. The Psychic will correctly see all Neutrals and Killing Crewmates displayed as red names when becoming a Madmate.", + "MechanicInfoLong": "(Crewmates):\nThe Mechanic can use the vent at any time. They can also fix Reactors, O2, and Communications using only one side. You can fix Lights by flicking only one switch. Opening a door will open all doors in the map.", + "SheriffInfoLong": "(Crewmates):\nSheriff has no task. The Sheriff can kill the Impostor (according to the host settings, the Sheriff can also kill neutrals). If the Sheriff tries to kill a crewmate, the Sheriff will kill himself. The Sheriff can kill anyone when he becomes a madmate (also according to the host settings).", + "VigilanteInfoLong": "(Crewmates):\nAs the Vigilante, you are tasked with eliminating potential threats to the Crew, but if they mistakenly kill an innocent crew member, they become a Madmate driven by guilt and remorse.\n\n Note: Gangster cannot convert Vigilante into madmate.", + "JailerInfoLong": "(Crewmates):\nAs the Jailer, use your kill button to lock a player in jail. During the next meeting, the jailed player cannot vote or get voted (the vote count will be 0). The Jailer may choose to execute the prisoner by voting for them. If the Jailer executes an innocent player, the Jailer loses the ability to execute for the rest of the game.\nIf the Jailer is evil, then they can execute anyone.\nThe Jailer has limited executions.\n\nNote: Jailed players cannot be guessed or judged, and jailed players can only guess Jailer.", + "SnitchInfoLong": "(Crewmates):\nAfter the Snitch completes all tasks, they can see the Impostor's names displayed in red on the meeting. When the Snitch has only one task left, the Impostors will see a 「★」 mark next to the name of themselves and the Snitch. When a Snitch becomes a Madmate, the 「★」 mark turns red.", + "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the Crew.\nOther teams will not be able to see you.\nHowever, madmates CAN see you.", + "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, the Doctor can access vitals wherever you are while he still has battery left.", + "DictatorInfoLong": "(Crewmates):\nWhen the Dictator votes for someone, the meeting will end on the spot, and the player they voted for will be ejected from the meeting. The moment the Dictator votes someone out, the Dictator will also die.", + "DetectiveInfoLong": "(Crewmate):\nAfter the Detective reports the body, they will receive a clue message, which will tell the Detective what the victim's role is. According to the Host's settings, the Detective may know what the murderer's role is. Note: Detective won't be Oblivious.", + "UndercoverInfoLong": "(Crewmates):\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.", + "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.\nNice Guesser can guess crewmate when become madmate.", + "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role the guesser tried to guess, and you will also be notified in case of a misguess.", + "KnightInfoLong": "(Crewmates):\nThe Knight has no tasks. They can kill anyone but only do it once the whole game.", + "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in the vent will not be selected.", + "TimeManagerInfoLong": "(Crewmates):\nThe more tasks the Time Manager does, the longer the meeting time will be. When the Time Manager dies, the meeting time will return to normal. When the Time Manager becomes a Madmate, the skill changes to reducing the meeting time instead of increasing it.", + "VeteranInfoLong": "(Crewmates):\nAs the Veteran, you can enter the alert state by venting. If a player tries to kill the Veteran in the alert state, the Veteran will kill the murderer instead. Veteran will see a shield animation on their body and a text above their head as a reminder when they enter and exit the alert state.", + "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb vents to kill off impostors and neutrals.\nBe careful though; crewmates can also be killed with the bombs.", + "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your kill button to copy the target's role.\n\nYou can only copy some crewmate roles.\nIf you try to copy a madmate or rascal, you become the madmate variation of the target role.\nIf you target an evil with a crewmate variant, you'll become the crewmate variant.\n\nAdditionally, Your role will be set back to Copycat after every meeting.", + "BodyguardInfoLong": "(Crewmates):\nIf a player is about to be killed near the Bodyguard, the Bodyguard will prevent the kill and die with the murderer. The Bodyguard's skills will affect players of any team. When the Bodyguard becomes a Madmate, and the murderer is an Impostor, the Bodyguard will not activate the skill.", + "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the kill button. If the counterfeit is sold successfully, the Deceiver will see a shield animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the killing ability has the counterfeit, he will commit suicide when he tries to kill someone next time.", + "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", + "MedicInfoLong": "(Crewmates):\nThe Medic can place a shield on the target by pressing the Kill button. The Medic can only give one shield for the whole game. Depending on the settings, the target's shield can or cannot deactivate when the Medic dies. The Medic can also see if someone is trying to break the target's shield.\nDepending on the Host's settings, the Medic or the target can see if the player has a shield (shown as a green circle 「●」 next to the name).", + "FortuneTellerInfoLong": "(Crewmates):\nAs the Fortune Teller, vote for a player in a meeting to get a clue to their role.\nThe clue will relate to their actual role.\n\nWhen the Fortune Teller's tasks are complete, they will obtain the exact role rather than a clue!\n\nNote: If the setting to give random active players as a hint is on, you cannot check the same player multiple times.", + "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the Host). If it is wrong, the Judge commits suicide.\nCommand for judgment: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.", + "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body, they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", + "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after someone reports a dead body. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question, which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", + "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. The shied animations typically indicate a role ability, so look out for this.", + "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has multiple votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exiled.", + "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", + "OverseerInfoLong": "(Crewmates):\nAs The Overseer, you have minimal vision, but you can use your kill button to reveal the role of a nearby player. A 「○」 will be displayed next to the revealed target after you use the kill button on them, and you will also be scanning them (only you can see this). Stay near the target for a defined time to reveal his role; if you move too far away, the reveal will cancel.", + "CoronerInfoLong": "(Crewmates):\nAs a Coroner, you can't report corpses; instead, after trying to report the corpse, you will see an arrow leading you to the killer. If someone calls a meeting, the arrows disappear. Depending on the settings, players can't report the body you found.", + "PresidentInfoLong": "(Crewmates):\nThe President has two abilities: End the meeting and Reveal identity.\n\n+ Ability 1: End the meeting - Type /finish in meetings as President to instantly end the meeting.\n+ Ability 2: Reveal identity - Type /reveal in meetings to reveal yourself. Revealing yourself will make it so every player can see that you are the President, and you will become unguessable after typing the command. However, after the President has revealed themselves, whoever killed the President will have their kill CD greatly reduced on their next kill.", + "MerchantInfoLong": "(Crewmates):\nAs a merchant, you sell a random add-on to a random player for each task you complete. Each add-on sold earns you money. If you have a certain amount of money, you can prevent the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The money used is lost and not available for additional bribes.", + "RetributionistInfoLong": "(Crewmates):\nAs the Retributionist, you can kill a limited amount of players after your death.\n\nUse /ret [playerID] to kill.", + "HawkInfoLong": "(Crewmates [Ghost]):\nAs the Hawk, you can kill a limited amount of players decided by the host, though there's a chance you miss, slicing someone multiple times increases the chances.", + "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your kill button on a player to reset their kill cooldown.\n\nIf the target does not have a kill button, then the handcuff was a waste.", + "InvestigatorInfoLong": "(Crewmates):\nAs an Investigator, you can use your kill button to investigate someone. When you investigate someone, their name will appear in red if they possess a kill button (impostor/SS basis) or light blue if they lack a kill button (crewmate/engineer/scientist basis). However, please note that the color of the names will return to normal when someone calls a meeting.", + "GuardianInfoLong": "(Crewmates):\nAs the Guardian, you become immortal upon task completion. Guessers can't even guess you in meetings.", + "AddictInfoLong": "(Crewmates):\nAs the Addict, you have a suicide timer. When it expires, you kill yourself.\nThe timer is indicated by the vent cooldown. When the vent cooldown is 0 seconds, you still have a short time to vent.\nIf you don't make it, you die; if you make it, the suicide timer is reset.\nAlso after you vent, no one can interact with you for a defined period.\nAfter; the period is over, and you are immobilized for another defined period, and cannot report any bodies.", + "MoleInfoLong": "(Crewmates):\nAs the Mole, when you vent, you stay in the vent for 1 second. When you exit the vent, you will spawn near a random vent in the map (Except the one you used).", + "AlchemistInfoLong": "(Crewmates):\nAs the Alchemist, you brew potions when you complete tasks. The potion you made will show up under your role name with its corresponding description and instructions. You can get seven different potions, some with harmful or no effects. Vent to use the potion.", + "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark people. Double-click to kill normally. When you die, all marked also die, with death reason Targeted.", + "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by the Host.", + "OracleInfoLong": "(Crewmates):\nAs the Oracle, you may vote a player during a meeting.\nYou'll see if they are a Crewmate, Neutral, or Impostor.\nDepending on settings, there can be a chance that your result will be incorrect.", + "SpiritualistInfoLong": "(Crewmates):\nAs the Spiritualist, you get an arrow pointing towards the ghost of the last meeting's victim. There is an option for the arrow to disappear and reappear in intervals. Try to notify the ghost about your ability if you can; if they are on your side, they may lead you to an evil role so you can eject them. Be careful, as evil roles can do the same for Crewmates.", + "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", + "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message if they are on the same team or a denial message if they are not on the same team.\n\nAll neutrals and converted players are counted in the same team. Trickster counts as Crew, and Rascal counts as Impostor.\nChecking command: /cmp [player id 1] [player id 2].", + "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non-crew role. Crewmates can see ☆besides Captain's name.\n\nIf anyone betrays the Captain's trust by voting Captain out, they will lose an add-on.", + "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", + "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", + "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", + "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", + "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", + "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", + "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", + "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real-time.", + "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings).", + "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", + "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, your survival is crucial. You can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses. This unique role adds a new dynamic to the game, where your survival is not just for your benefit but for the entire Crew's success.", + "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the kill button interaction is blocked, the player's cooldown will reset to 10s'", + "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player.", + "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the kill button on the player and following them for a few seconds. When the dousing starts, and it's successful, a shield animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", + "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting. You may have to report the body to receive a clue, depending on the settings. The more tasks you complete, the more precise the clues get.", + "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", + "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your kill cooldown goes down permanently by the set amount. However, if you kill someone who is not one of your targets, your kill cooldown permanently increases by the set amount. A colored name indicates your targets.", + "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini has two roles. A Nice or Evil Mini is chosen.\n\nUse'/r nice mini' and '/r evil mini' respectively for more details.", + "JesterInfoLong": "(Neutrals):\nIf the Jester gets voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", + "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", + "ExecutionerInfoLong": "(Neutrals):\nThe Executioner is a role with an execution target, indicated by a diamond symbol「♦」next to their name. If the execution target is killed, the Executioner's role will change to either Crewmate, Jester, or Opportunist, depending on the game settings. However, if the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", + "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", + "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", + "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", + "JackalInfoLong": "(Neutrals):\nAs the Jackal, you win if you are the last player alive. Additionally, you may recruit using the kill button. If the target is not one you can recruit, you have run out of uses, or you don't have the option to recruit, then you will kill people normally (i.e., don't use kill buttons in front of others thinking it'll recruit). If the target has a kill button and the option to turn into a Sidekick is on, they will become a Sidekick. Otherwise, they will gain the Recruit add-on if the option to give the Recruit add-on is on.", + "GodInfoLong": "(Neutrals):\nAs the God, you know everyone's role from the beginning. If you live until the end of the game, you steal the win, i.e., everyone else loses, and you win.", + "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the kill button to plant any player, and the planted target will immediately kill the Innocent. If the target gets voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", + "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", + "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the kill button on the player and following them until the shield animation plays for you. Recruiting has a chance, set by the Host, to kill players (though they are still recruited). When the required number of players are recruited (displayed next to your name), you must vent within the specified time to win the game immediately with all your recruits. If you do not vent in time, you lose and die.", + "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no kill cooldown. However, depending on the settings, you can only kill Lovers and other recruiting roles and add-ons. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", + "DemonInfoLong": "(Neutrals):\nAs the Demon, you kill by draining health. You see health in percentage near everyone's name, and every attack you make drains a percentage from that health without the victim knowing. Once you drain your victim's health to 0, they die. You win if you are the last one standing.", + "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause a Lights sabotage (if Lights sabotage is already active, nothing will happen). Stalker cannot vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the Host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker wins alone.", + "WorkaholicInfoLong": "(Neutrals):\nAs the Workaholic, you win alone when you complete all tasks. Depending on the Host's settings, you can only win if you are alive and or revealed to everyone at the beginning (these settings are rarely both on).", + "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting finishes, your tasks reset, and you need to start all over again.\nVotes on the Solsticer will be directly canceled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's kill cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in-game.", + "CollectorInfoLong": "(Neutrals):\nAs the Collector, when you vote for a player, for each other player that voted for them, you gain a point. When you collect the required votes, the game ends, and you win alone, even if you voted a Jester or Executioner's target out.", + "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, vent, or report for the hack duration.\nAdditionally, calling a sabotage other than doors will have no effect and will instead disguise you as a random player. You cannot disguise during or after sabotages.\nTo win, be the last player alive.", + "SidekickInfoLong": "(Neutrals):\nAs the Sidekick, your job is to help the Jackal kill everyone.\n\nYou and the Jackal win together.", + "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", + "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", + "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", + "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone at the meeting after you transform.", + "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", + "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after meeting)", + "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", + "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", + "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", + "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", + "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain the majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", + "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive.", + "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", + "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you can outnumber the Crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", + "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", + "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, survive to the end of the game.", + "SpecterInfoLong": "(Neutrals):\nAs the Specter, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", + "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both the Pirate and the target choose the same number, the Pirate wins.\nAdditionally, if the Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command: /duel X (where X can be 0, 1, or 2)\n\nYou win after winning a certain number of duels set by the Host.\n\nNote: The kill would not count towards pirate victory if the target did not participate in the duel.", + "AgitaterInfoLong": "(Neutrals):\nAs the Agitator, your premise is essentially Hot Potato.\n\nUse your kill button on a player to pass the bomb.\nThis can only be done once per round.\n\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\n\nWhen a meeting is called, the player with the bomb dies.\n\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\nOptionally, the Agitator cannot receive the bomb.", + "MaverickInfoLong": "(Neutrals):\nAs the Maverick, you can kill and, depending on options, vent and have impostor vision\nIf you survive until the end of the game, you win with the winning team.\nUse your killing ability to eliminate threats to your life, but don't get voted out.", + "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", + "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", + "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", + "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", + "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", + "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", + "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", + "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", + "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", + "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", + "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button one time to steal a player's addon and twice to kill. You may instantly steal the addon or after the meeting starts, depending on the settings. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable addons on the target or the target is stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can vent is on, Nimble will become unstealable.", + "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", + "PunchingBagInfoLong": "(Neutrals):\nAs the Punching Bag, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", + "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings), then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", + "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themselves after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", + "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher kill cooldown than anyone else.", + "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your kill button to select a voodoo doll once per round. If the kill button is used on you, the effect will be deflected onto the voodoo doll.\nIf you survive until the end, you win with the winning team.\nNote: If the killer cannot kill the chosen target, murder is canceled, but if the killer rechecks the Shaman, the killer will kill the Shaman.", + "SeekerInfoLong": "(Neutrals):\nAs the seeker, use your kill button to tag the target. If the seeker tags the wrong player, a point is deducted, and if the seeker tags the correct player, a point will be added.\nAdditionally, the seeker will not be able to move for 5 seconds after every meeting and after getting a new target.\n\nThe seeker needs to collect a certain number of points set by the Host to win.", + "PixieInfoLong": "(Neutrals):\nAs the Pixie, Mark up to x amount of targets each round by using the kill button on them. You must have one of the marked targets ejected when the meeting starts. If unsuccessful, you will commit suicide, except if you didn't mark any targets or all the targets are dead. The selected targets reset to 0 after the meeting ends. If you succeed, you will gain a point. You see all your targets in colored names.\n\nYou win with the winning team when you have certain amounts of points set by the Host.", + "SchrodingersCatInfoLong": "(Neutrals):\nAs Schrodingers Cat, if someone attempts to use the kill button on you, you will block the action and join their team. This blocking ability works only once. By default, you don't have a victory condition, meaning you win only after switching teams.\nIn Addition to this, you will be counted as nothing in the game.\n\nNote: If the killing machine attempts to use its kill button on you, the interaction is not blocked, and you will die.", + "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield that protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote: If your role changes, your win condition will be changed accordingly", + "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", + "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", + "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", + "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", + "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", + "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", + "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three different potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons default to killing.", + "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally when someone tries to kill you, you will block the kill, and you will teleport to a random vent. You will have a limited time to kill your killer. If you succeed in doing so, you live. If the time runs out before you kill your killer, you die permanently. If you try to kill someone else other than your killer, you will die.", + "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", + "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nThis feature is only assigned to roles with a kill button.", + "LoversInfoLong": "(Add-ons),\nLovers are a combination of two players. The Lovers win when they are the last ones standing, and their victory is shared. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the Host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", + "MadmateInfoLong": "(Add-ons):\nOnly Crewmates can become Madmate. Madmate's task is to help the Impostors win the game. Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors, and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone, including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone\nPacifist => Their ability only works on Crewmates.", + "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", + "FlashInfoLong": "(Add-ons):\nThe Flash's default movement speed is faster than others. (speed depends on the setting of the Host)", + "TorchInfoLong": "(Add-ons):\nTorch has max vision and is not affected by Lights sabotage.", + "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like sabotage).", + "TiebreakerInfoLong": "(Add-ons):\nWhen tie vote, priority will be given to the target voted by the Tiebreaker. Note: If multiple Tiebreakers choose different tie targets simultaneously, the skills of the Tiebreaker will not take effect.", + "ObliviousInfoLong": "(Add-ons):\nDetective and Cleaners won't be Oblivious. The Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still report automatically, and Oblivious can still be used as a scapegoat for Anonymous.", + "BewilderInfoLong": "(Add-ons):\nBewilder may have a smaller/bigger vision. When the Bewilder has died, the murderer's vision may become the same as the Bewilder's, depending on the settings.", + "WorkhorseInfoLong": "(Add-ons):\nThe first player to complete all the tasks will become Workhorse, and Workhorse will give the player extra tasks. The Host sets the number of additional tasks.", + "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any sabotage.", + "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out, and irregular kills will not count), the Avenger will revenge a random player.", + "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to die in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc., will not trigger the skills of the YouTuber.", + "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", + "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the Host sets the vote number, and the decimal is rounded down).\nAlso, extra votes from the Stealer are hidden during the meeting depending on the options.", + "ParanoiaInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Paranoia, you will be considered as two players in the game to determine when the game ends due to killers having the majority. Additionally, this grants you an extra vote, depending on options.", + "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called. This message will include information on roles which the Mimic killed.", + "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess the roles of players in meetings to kill them.\nGuessing the incorrect role kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", + "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. Unlike everyone else, you have the longest kill range possible in the game.", + "BaitInfoLong": "(Add-ons):\nWhen the Bait dies, the murderer who killed the Bait will self-report the Bait's body. However, this won't happen when a Scavenger, Cleaner, Swooper, Wraith, Medusa, or Killing Machine kills the Bait. The report may have a delay according to the Host's settings.", + "TrapperInfoLong": "(Add-ons):\nWhen Beartrap dies, Beartrap immobilizes killer for a configurable amount of time.", + "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", + "CleansedInfoLong": "(Add-ons):\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", + "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", + "OnboundInfoLong": "(Add-ons):\nWith the Onbound add-on, you cannot be guessed in meetings.", + "ReboundInfoLong": "(Add-ons):\nWith the Rebound add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", + "MundaneInfoLong": "(Add-ons):\nAs Mundane, you can only guess once you complete all of your tasks.", + "KnightedInfoLong": "(Add-ons):\nWhen a Monarch knights someone, they get an extra vote.", + "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded add-on, your corpse will be unreportable.", + "ContagiousInfoLong": "(Betrayal Add-ons):\nWhen the Virus infects you, you become contagious.\nContagious players are on the Virus team.\n\nWhether or not you die after a meeting depends on the settings for the Virus.", + "LuckyInfoLong": "(Add-ons):\nWith the Lucky add-on, there is a probability for you to evade the kill; the Host sets the specific probability. The killer will see the shield animation when the evasion takes effect, but you will not know anything.", + "DoubleShotInfoLong": "(Add-ons):\nWhen a player with Double Shot guesses a role incorrectly, they will get a second chance to guess, but the next wrong guess will result in suicide.", + "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", + "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul steals your soul, you get this add-on.\n\nYou are not counted as alive.", + "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", + "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", + "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", + "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", + "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", + "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", + "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", + "GlowInfoLong": "(Add-ons):\nDuring lights out, you and players nearby you will receive a vision boost.", + "RadarInfoLong": "(Add-ons):\nAs Radar, you have arrows pointing towards the closest person at all times.", + "DiseasedInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be increased by a configurable amount of time.", + "AntidoteInfoLong": "(Add-ons):\nWhen someone tries to use the kill button on you, their cooldown will be decreased by a configurable amount of time.", + "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add-on, Eraser can't erase your role, Cleanser can't cleanse you, Bandit can't steal from you, and Monarch can't knight you.\nAdditionally, you can't gain any new add-ons from the Merchant.", + "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.\nNote: Swift also ignores Bait", + "UnluckyInfoLong": "(Add-ons):\nAs Unlucky, when you complete tasks, kill, venting, or open door, you have a chance to die.", + "VoidBallotInfoLong": "(Add-ons):\nHolder of this add-on will have 0 vote count.", + "AwareInfoLong": "(Add-ons):\nAs the Aware, you get a notification in the next meeting if a revealing role had interacted with you.", + "FragileInfoLong": "(Add-ons):\nAs Fragile, you will instantly die if someone tries to use the kill button on you (even if the role cannot directly kill).", + "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on task completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nThis is only assigned to crewmates, and not crewmates with no tasks or are task-based.", + "BloodthirstInfoLong": "(Add-ons):\nAs the Bloodthirst, doing tasks allows you to become bloodthirsty and kill players.\nWhen you finish a task, the next player you come in contact with dies.\n\nYour Bloodthirst remains after a meeting.\nUpon making a kill, your Bloodthirst clears till the next task you complete.\nBloodthirsts do not stack.\n\nOnly assigned to crewmates with tasks.", + "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low kill cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", + "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a vent after a set amount of time.", + "SleuthInfoLong": "(Add-ons):\nAs the Sleuth, you gain info from dead bodies.\n\nOptionally, you may also gain the killer's role.\n\nNot assigned to Detective or Mortician.", + "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your cooldown is reset, and the target remains untouched.\n\nOnly assigned to killers.", + "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't vent.\n\nOnly assigned to Impostors.", + "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the vent button.\n\nOnly assigned to certain crewmates.", + "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced, then the vote result won't shift\nCollector cannot become influenced.", + "SilentInfoLong": "(Add-ons):\nAs the Silent, your vote icon won't appear on the result screen.\nSo nobody knows who you voted for.", + "SusceptibleInfoLong": "(Add-ons):\nAs the Susceptible, your death reason will be random.", + "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", + "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", + "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", + "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", + "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", + "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", + "RainbowInfoLong": "(Add-ons):\nAs the rainbow, you change your colors like crazy.", + "GMInfoLong": "(None):\nThe Game Master is an observer role.\nTheir presence does not affect the game, and all players know who the Game Master is. The Game Master role will be assigned to the Host, who will automatically become a ghost at the start of the game.", + "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. The game will not end when you are alive due to killers gaining the majority.\nAdditionally, you have access to portable vitals.", + "BardInfoLong": "(Impostors):\nWhen a bard is alive, the exile confirmation will display a sentence composed by the bard. Whenever the bard completes a creation, the bard's kill cooldown will be permanently halved.", + "WardenInfoLong": "(Crewmates [Ghost]):\nAs the Warden, alert someone of nearby danger, additionally giving them a temporary speed boost.", + "GhastlyInfoLong": "(Crewmates [Ghost]):\nAs the Ghastly, possess an unsuspecting person, after that choose a target for them, now they'll only be able to use their kill (or kill ability) on target until you possess someone else or possess time runs out.", + "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", + "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", + "ShowTextOverlay": "Text Overlay", + "Overlay.GuesserMode": "Guesser Mode", + "Overlay.NoGameEnd": "No Game End", + "Overlay.DebugMode": "Debug Mode", + "Overlay.LowLoadMode": "Low Load Mode", + "Overlay.AllowConsole": "Console", + "DisableShieldAnimations": "Disable Unnecessary Shield Animations", + "DisableKillAnimationOnGuess": "Disable Kill Animation on Guesses", + "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", + "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", + "AbilityUseLimit": "Initial Ability Use Limit", + "ShowArrows": "Has Arrows pointing toward bodies", + "ArrowDelayMin": "Minimum Arrow show-up delay", + "ArrowDelayMax": "Maximum Arrow show-up delay", + "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", + "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", + + "AbilityCD": "Ability Cooldown", + "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", + "ShowSpecificRole": "Know specific roles on Task Completion", + "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", + "SwooperVentNormallyOnCooldown": "Swooper vents normally when swooping is on cooldown", + "WraithVentNormallyOnCooldown": "Wraith vents normally when invis is on cooldown", + "DisableMeeting": "Disable Meetings", + "DisableCloseDoor": "Disable Doors Sabotage", + "DisableSabotage": "Disable Sabotages", + "NoGameEnd": "No Game End", + "AllowConsole": "BepInEx Console", + "DebugMode": "Debug Mode", + "SyncButtonMode": "Limit Meeting Times", + "RandomMapsMode": "Random Maps Mode", + "SyncedButtonCount": "Max Number of Emergency Meetings per Game", + "HHSuccessKCDDecrease": "Kill cooldown decrease on killing target", + "HHFailureKCDIncrease": "Kill cooldown increase on killing others", + "HHNumOfTargets": "Number of targets", + "Targets": "Targets: ", + "HHMaxKCD": "Maximum kill cooldown", + "HHMinKCD": "Minimum kill cooldown", + "AllAliveMeeting": "Meeting When No One is Dead", + "AllAliveMeetingTime": "Meeting Time When No One is Dead", + "AdditionalEmergencyCooldown": "Additional Emergency Cooldown", + "AdditionalEmergencyCooldownThreshold": "Minimum Living Players to be Applied", + "AdditionalEmergencyCooldownTime": "Additional Cooldown", + "LadderDeath": "Fall From Ladders", + "LadderDeathChance": "Fall To Death Chance", + "EveryoneCanSeeDeathReason": "Everyone Can See Death Reason", + "DisableSwipeCardTask": "Disable Swipe Card Task", + "DisableSubmitScanTask": "Disable Submit Scan Task", + "DisableUnlockSafeTask": "Disable Unlock Safe Task", + "DisableUploadDataTask": "Disable Upload Data Task", + "DisableStartReactorTask": "Disable Start Reactor Task", + "DisableResetBreakerTask": "Disable Reset Breakers Task", + "DisableShortTasks": "Disable Short Tasks", + "DisableCleanVent": "Disable Clean Vent Task", + "DisableCalibrateDistributor": "Disable Calibrate Distributor Task", + "DisableChartCourse": "Disable Chart Course Task", + "DisableStabilizeSteering": "Disable Stabilize Steering Task", + "DisableCleanO2Filter": "Disable Clean O2 Filter Task", + "DisableUnlockManifolds": "Disable Unlock Manifolds Task", + "DisablePrimeShields": "Disable Prime Shields Task", + "DisableMeasureWeather": "Disable Measure Weather", + "DisableBuyBeverage": "Disable Buy Beverage", + "DisableAssembleArtifact": "Disable Assemble Artifact Task", + "DisableSortSamples": "Disable Sort Samples Task", + "DisableProcessData": "Disable Process Data Task", + "DisableRunDiagnostics": "Disable Run Diagnostics Task", + "DisableRepairDrill": "Disable Repair Drill Task", + "DisableAlignTelescope": "Disable Align Telescope Task", + "DisableRecordTemperature": "Disable Record Temperature Task", + "DisableFillCanisters": "Disable Fill Canisters Task", + "DisableMonitorTree": "Disable Monitor Tree Task", + "DisableStoreArtifacts": "Disable Store Artifacts Task", + "DisablePutAwayPistols": "Disable Put Away Pistols Task", + "DisablePutAwayRifles": "Disable Put Away Rifles Task", + "DisableMakeBurger": "Disable Make Burger Task", + "DisableCleanToilet": "Disable Clean Toilet Task", + "DisableDecontaminate": "Disable Decontaminate Task", + "DisableSortRecords": "Disable Sort Records Task", + "DisableFixShower": "Disable Fix Shower Task", + "DisablePickUpTowels": "Disable Pick Up Towels Task", + "DisablePolishRuby": "Disable Polish Ruby Task", + "DisableDressMannequin": "Disable Dress Mannequin Task", + "DisableCommonTasks": "Disable Common Tasks", + "DisableFixWiring": "Disable Fix Wiring Task", + "DisableEnterIdCode": "Disable Enter ID Code Task", + "DisableInsertKeys": "Disable Insert Keys Task", + "DisableScanBoardingPass": "Disable Scan Boarding Pass Task", + "DisableLongTasks": "Disable Long Tasks", + "DisableAlignEngineOutput": "Disable Align Engine Output Task", + "DisableInspectSample": "Disable Inspect Sample Task", + "DisableEmptyChute": "Disable Empty Chute Task", + "DisableClearAsteroids": "Disable Clear Asteroids Task", + "DisableWaterPlants": "Disable Water Plants Task", + "DisableOpenWaterways": "Disable Open Waterways Task", + "DisableReplaceWaterJug": "Disable Replace Water Jug Task", + "DisableRebootWifi": "Disable Reboot Wifi Task", + "DisableDevelopPhotos": "Disable Develop Photos Task", + "DisableRewindTapes": "Disable Rewind Tapes Task", + "DisableStartFans": "Disable Start Fans Task", + "DisableOtherTasks": "Disable Situational Tasks", + "DisableEmptyGarbage": "Disable Empty Garbage Task", + "DisableFuelEngines": "Disable Fuel Engines Task", + "DisableDivertPower": "Disable Divert Power Task", + "DisableActivateWeatherNodes": "Disable Weather Nodes Task", + "DisableRoastMarshmallow": "Disable Roast Marshmallow", + "DisableCollectSamples": "Disable Collect Samples", + "DisableReplaceParts": "Disable Replace Parts", + "DisableCollectVegetables": "Disable Collect Vegetables", + "DisableMineOres": "Disable Mine Ores", + "DisableExtractFuel": "Disable Extract Fuel", + "DisableCatchFish": "Disable Catch Fish", + "DisablePolishGem": "Disable Polish Gem", + "DisableHelpCritter": "Disable Help Critter", + "DisableHoistSupplies": "Disable Hoist Supplies", + "DisableFixAntenna": "Disable Fix Antenna", + "DisableBuildSandcastle": "Disable Build Sandcastle", + "DisableCrankGenerator": "Disable Crank Generator", + "DisableMonitorMushroom": "Disable Monitor Mushroom", + "DisablePlayVideoGame": "Disable Play Video Game", + "DisableFindSignal": "Disable Find Signal", + "DisableThrowFisbee": "Disable Throw Frisbee", + "DisableLiftWeights": "Disable Lift Weights", + "DisableCollectShells": "Disable Collect Shells", + "SuffixMode": "Suffix", + "SuffixMode.None": "None", + "SuffixMode.Version": "Version", + "SuffixMode.Streaming": "Streaming", + "SuffixMode.Recording": "Recording", + "SuffixMode.RoomHost": "Room Host", + "SuffixMode.OriginalName": "Original Name", + "SuffixMode.DoNotKillMe": "Don't kill me", + "SuffixMode.NoAndroidPlz": "No phones", + "SuffixMode.AutoHost": "Auto-Host", + "SuffixModeText.DoNotKillMe": "Don't kill me", + "SuffixModeText.NoAndroidPlz": "No phones please", + "SuffixModeText.AutoHost": "Auto-hosting", + "FormatNameMode": "Player Name Mode", + "FormatNameModes.None": "Disable", + "FormatNameModes.Color": "Color", + "FormatNameModes.Snacks": "Random", + "DisableEmojiName": "Disable Emoji in names", + "FixFirstKillCooldown": "Override Starting Kill Cooldown", + "FixKillCooldownValue": "Starting Kill Cooldown", + "OverclockedReduction": "Kill Cooldown Reduction", + "GhostCanSeeOtherRoles": "Ghosts Can See Other Roles", + "GhostCanSeeOtherVotes": "Ghosts Can See Vote Colors", + "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", + "GhostIgnoreTasks": "Ghosts Exempt From Tasks", + "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", + "MaxImpGhostRole": "Max Impostor Ghost-Roles", + "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", + "DefaultAngelCooldown": "Default Ability Cooldown", + "DisableTaskWin": "Disable Task Win", + "DisableTaskWinIfAllCrewsAreDead": "Disable Task Win If All <#8cffff>Crewmates Are Dead", + "DisableTaskWinIfAllCrewsAreConverted": "Disable Task Win If All <#8cffff>Crewmates Are <#ffab1b>Converted", + "HideGameSettings": "Hide Game Settings", + "DIYGameSettings": "Enable only custom /n messages", + "Settings:": "Settings:", + "PlayerCanSetColor": "Players can use the /color command", + "PlayerCanSetName": "Players can use the /rn command", + "PlayerCanUseQuitCommand": "Players can use the /quit command to leave the lobby forever", + "PlayerCanUseTP": "Players can use the /tpin and /tpout command", + "CanPlayMiniGames": "Players can play mini-games", + "KPDCamouflageMode": "Camouflage Appearance", + "RoleOptions": "Role Options", + "DarkTheme": "Enable Dark Theme", + "DisableLobbyMusic": "Disable Lobby Music", + "AutoStart": "Auto start", + "EnableCustomButton": "Enable Custom Button Images", + "EnableCustomSoundEffect": "Enable Custom Sound Effects", + "EnableCustomDecorations": "Enable Custom Map Decorations", + "SwitchVanilla": "Switch Vanilla", + "UnlockFPS": "Unlock FPS", + "ForceOwnLanguage": "Force mod to use your language if possible", + "ForceOwnLanguageRoleName": "Force role names in your language if possible", + "VersionCheat": "Bypass version synchronization check", + "GodMode": "God Mode", + + "AutoDisplayKillLog": "Display Kill-log", + "AutoDisplayLastRoles": "Display Last Roles", + "AutoDisplayLastResult": "Auto Display Last Result", + "RevertOldKillLog": "Revert to old kill-log", + + "HideExileChat": "Hide exile (lava) chat", + "ExileSpamMsg": "Someone is trying to be a smart ass by lava chatting", "EnableYTPlan": "Enable Youtuber Plan", "InvalidPermissionCMD": "INVALID PERMISSION\n\nSorry, you do not have the permission to use this command, check /me for all permissions", @@ -1343,2409 +1343,2409 @@ "ShowFPS": "Show FPS", "FPSGame": "FPS: ", - "ControlCooldown": "Control Cooldown", - "PoisonCooldown": "Poison Cooldown", - "PoisonerKillDelay": "Poison Kill Delay", - "WardenNotifyLimit": "Max number of alerts", - - "BombCooldown": "Bomb Cooldown", - "Warlock_CanKillSelf": "Can Kill Themselves", - "CrewpostorKnowsAllies": "Knows Impostors", - "AlliesKnowCrewpostor": "Known to Impostors", - "CrewpostorLungeKill": "Crewpostor lunges on kill", - "CrewpostorKillAfterTask": "Number of tasks completed to make one kill", - - "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", - "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", - "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", - "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", - "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", - "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", - "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", - "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", - "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", - "ImpKnowWhosMadmate": "Impostors know Madmates", - "MadmateKnowWhosImp": "Madmates know Impostors", - "MadmateKnowWhosMadmate": "Madmates know each other", - "ImpCanKillMadmate": "Impostors can kill Madmates", - "MadmateCanKillImp": "Madmates can kill Impostors", - "MadmateHasImpostorVision": "Madmates Have Impostor Vision", - "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", - "EGCanGuessImp": "Can Guess Impostor Roles", - "GGCanGuessCrew": "Can Guess Crewmate Roles", - "EGCanGuessAdt": "Can Guess Add-Ons", - "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "GGCanGuessAdt": "Can Guess Add-Ons", - "GuesserCanGuessTimes": "Maximum number of guesses", - "GuesserTryHideMsg": "Try to hide the guesser's command", - "GCanGuessImp": "Impostor can guess Impostor roles", - "GCanGuessCrew": "Crewmate can guess Crewmate roles", - "GCanGuessAdt": "Can guess Add-ons", - "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", - "BountyTargetChangeTime": "Time Until Target Swaps", - "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", - "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", - "BountyShowTargetArrow": "Show arrow pointing towards the target", - "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", - "DeadImpCantSabotage": "Impostors can't sabotage after they've died", - "VampireKillDelay": "Bite Kill Delay", - "VampireTargetDead": "Target died", - "VampireActionMode": "Action Mode", - "Vampire_OnlyBites": "Only Bites", - "Youtuber_KillerWinsWithYouTuber": "The Killer Wins With YouTuber", - "Maverick_MinKillsToWin": "Minimum number of kills to win", - - "Cooldown": "Cooldown", - "AbilityCooldown": "Ability Cooldown", - "CanKill": "Can Kill", - "KillCooldown": "Kill Cooldown", - "CanVent": "Can Vent", - "ImpostorVision": "Has Impostor Vision", - "CanUseSabotage": "Can Sabotage", - "CanKillImpostors": "Can Kill Impostors", - "CanGuess": "Can Guess in Guesser Mode or as Guesser", - "HideVote": "Hide Vote", - "HideAdditionalVotes": "Hide additional vote(s)", - "CanUseMeetingButton": "Can Call Emergency Meetings", - "ModeSwitchAction": "Switch Action via", - "ShowShapeshiftAnimations": "Show Shapeshift animations", - - "ShapeshifterBase_ShapeshiftCooldown": "Shapeshift Cooldown", - "ShapeshifterBase_ShapeshiftDuration": "Shapeshift Duration", - "ShapeshifterBase_LeaveShapeshiftingEvidence": "Leave Shapeshifting Evidence", - "PhantomBase_InvisCooldown": "Invis Cooldown", - "PhantomBase_InvisDuration": "Invis Duration", - "GuardianAngelBase_ProtectCooldown": "Protect Cooldown", - "GuardianAngelBase_ProtectionDuration": "Protection Duration", - "GuardianAngelBase_ImpostorsCanSeeProtect": "Protect Visible To Impostors", - "ScientistBase_BatteryCooldown": "Vitals Display Cooldown", - "ScientistBase_BatteryDuration": "Battery Duration", - "EngineerBase_VentCooldown": "Vent Cooldown", - "EngineerBase_InVentMaxTime": "Max Time In Vents", - "NoisemakerBase_ImpostorAlert": "Impostors Can Get Alert", - "NoisemakerBase_AlertDuration": "Alert Duration", - "TrackerBase_TrackingCooldown": "Tracking Cooldown", - "TrackerBase_TrackingDuration": "Tracking Duration", - "TrackerBase_TrackingDelay": "Tracking Delay", - - "KamikazeControversialSymbol": "Use controversial symbol", - "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", - "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", - "MechanicSkillLimit": "Initial repair use limit", - "MechanicFixesDoors": "Can open all doors in the same building", - "MechanicFixesReactors": "Can Fix Both Reactors Alone", - "MechanicFixesOxygens": "Can Fix Both O2 Alone", - "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", - "MechanicFixesElectrical": "Can Fix Lights With One Switch", - - "SheriffShowShotLimit": "Display Shot Limit next to Role Name", - "SheriffCanKill%role%": "Can Kill %role%", - "SheriffCanKillNeutrals": "Can Kill Neutrals", - "SheriffCanKillNeutralsMode": "Neutral Configuration", - "SheriffCanKillAll": "All ON", - "SheriffCanKillSeparately": "Individual Settings", - "In%team%": "(Team %team%)", - "SheriffMisfireKillsTarget": "Misfire Kills Target", - "SheriffShotLimit": "Max number of Kills", - "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", - "SheriffCanKillCharmed": "Can kill Charmed players", - "SheriffCanKillEgoist": "Can Kill Egoists", - "SheriffCanKillSidekick": "Can Kill Sidekicks", - "SheriffCanKillLovers": "Can Kill Lovers", - "SheriffCanKillMadmate": "Can Kill Madmates", - "SheriffCanKillInfected": "Can Kill Infected players", - "SheriffCanKillContagious": "Can Kill Contagious players", - "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", - "SheriffMadCanKillImp": "Can kill Impostors", - "SheriffMadCanKillNeutral": "Can kill Neutrals", - "SheriffMadCanKillCrew": "Can kill Crewmates", - - "ReverieIncreaseKillCooldown": "Increase kill cooldown", - "ReverieMaxKillCooldown": "Max kill cooldown", - "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", - "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", - "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", - - "VigilanteNotify": "You have become the very thing you swore to destroy", - - "DoctorTaskCompletedBatteryCharge": "Battery Duration", - "SnitchEnableTargetArrow": "See Arrow Towards Target", - "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", - "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", - "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", - "SnitchCanFindMadmate": "Can Find Madmates", - "SnitchRemainingTaskFound": "Remaining tasks to be known", - "MayorAdditionalVote": "Additional Votes Count", - "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", - "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", - "MeetingsNeededForWin": "Meetings needed to win", - "ExecutionerCanTargetImpostor": "Can Target Impostors", - "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", - "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", - "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", - "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", - "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", - "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", - "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", - "LawyerCanTargetImpostor": "Can Target Impostors", - "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", - "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", - "LawyerCanTargetCrewmate": "Can Target Crewmates", - "LawyerCanTargetJester": "Can Target Jester", - "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", - "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", - "LawyerTargetDeadInMeeting": "Your target was killed while meeting.\nYour role may change depending on the settings.", - - "MercenaryLimit": "Time Until Suicide", - "ArsonistDouseTime": "Douse Duration", - "CanTerroristSuicideWin": "Can Win By Suicide", - "FireworkerMaxCount": "Fireworker Count", - "FireworkerRadius": "Firework Explosion Radius", - "SniperCanKill": "Sniper can kill with bullets remaining", - "SniperBulletCount": "Ammo", - "SniperPrecisionShooting": "Precise Shooting", - "SniperAimAssist": "Aim Assist", - "SniperAimAssistOneshot": "One shot Assist", - - "PyroDouseCooldown": "Douse cooldown", - "PyroBurnCooldown": "Kill cooldown after killing a doused player", - - "UndertakerFreezeDuration": "Freeze Duration", - "NameDisplayAddons": "Display Add-Ons next to the role name", - "YourAddon": "Your Add-ons:", - "NoLimitAddonsNumMax": "Max Add-ons Per Player", - "LoverSpawnChances": "Spawn Chance of Lovers", - "AdditionRolesSpawnRate": "Spawn Chance", - "TorchVision": "Torch Vision", - "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", - "BewilderVision": "Bewilder Vision", - "JesterVision": "Jester Vision", - "LawyerVision": "Lawyer Vision", - "FlashSpeed": "Flash Speed", - "LoverSuicide": "Lovers die together", - "NumberOfLovers": "Number of Lover Pairs (x2 members)", - "LoverKnowRoles": "Lovers know the roles of each other", - "TrapperBlockMoveTime": "Freeze time", - "BecomeTrapperBlockMoveTime": "Freeze time", - "ImpCanBeTrapper": "Impostors can become Beartrap", - "CrewCanBeTrapper": "Crewmates can become Beartrap", - "NeutralCanBeTrapper": "Neutrals can become Beartrap", - "ImpCanBeGravestone": "Impostors can become Gravestone", - "CrewCanBeGravestone": "Crewmates can become Gravestone", - "NeutralCanBeGravestone": "Neutrals can become Gravestone", - "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", - "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", - "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", - "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", - "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", - "EvilTrackerTargetMode": "Can Set Target", - "EvilTrackerTargetMode.Never": "Never", - "EvilTrackerTargetMode.OnceInGame": "Once in-game", - "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", - "EvilTrackerTargetMode.Always": "Any time", - - "EvilHackerCanSeeDeadMark": "Can See The Location of Dead-bodies", - "EvilHackerCanSeeImpostorMark": "Can See The Location of Other Impostors", - "EvilHackerCanSeeKillFlash": "Can See Kill-Flash", - "EvilHackerCanSeeMurderRoom": "Can See The Murder Location", - "EvilHackerMurderNotify": "Murder In", - "EvilHackerLastAdminInfoTitle": "Last-minute admin information", - "EvilHackerDeadbody": "DEAD", - - "TraitorKnowMadmate": "Traitor Knows Madmates", - "NBareRed": "Neutral Benign can be red", - "NEareRed": "Neutral Evil can be red", - "NCareRed": "Neutral Chaos can be red", - "NAareRed": "Neutral Apocalypse can be red", - "CrewKillingRed": "Crewmate Killings can be red", - "PsychicCanSeeNum": "Max number of red names", - "PsychicFresh": "New red names every meeting", - "DetectiveCanknowKiller": "Can find the killer's role", - "EveryOneKnowSuperStar": "Everyone knows the Super Star", - "HackLimit": "Ability Use Count", - "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", - "NemesisCanKillNum": "Max number of revenges", - "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", - "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", - "VectorVentNumWin": "Number of Vents to win", - "CanCheckCamera": "Can track camera usage", - "DefaultKillCooldown": "Starting kill cooldown", - "ReduceKillCooldown": "Reduce kill cooldown by", - "MinKillCooldown": "Minimum kill cooldown", - "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", - "NotifyGodAlive": "Inform players at meetings that God is still alive", - "TransporterTeleportMax": "Max number of teleports", - "TriggerKill": "Kill", - "TriggerVent": "Vent", - "TriggerDouble": "Double Click", - "TimeManagerIncreaseMeetingTime": "Increase voting time by", - "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", - "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", - "AssignOnlyToCrewmate": "Assign only to Crewmates", - "WorkhorseNumLongTasks": "Additional Long Tasks", - "WorkhorseNumShortTasks": "Additional Short Tasks", - "SnitchCanBeWorkhorse": "Snitch can become Workhorse", - "InnocentCanWinByImp": "If their target was an Impostor then they win with them", - "ImpCanBeParanoia": "Impostors can become Paranoia", - "CrewCanBeParanoia": "Crewmates can become Paranoia", - "DualVotes": "Duplicate votes", - "VeteranSkillCooldown": "Alert Cooldown", - "VeteranSkillDuration": "Alert Duration", - "BodyguardProtectRadius": "Protect Radius", - "ImpCanBeEgoist": "An Impostor can become Egoist", - "CrewCanBeEgoist": "Crewmates can become Egoist", - "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", - "EgoistCountAsConverted": "Egoist count as converted neutral", - "ImpCanBeSeer": "Impostors can become Seer", - "CrewCanBeSeer": "Crewmates can become Seer", - "NeutralCanBeSeer": "Neutrals can become Seer", - "ImpCanBeGuesser": "Impostors can become Guesser", - "CrewCanBeGuesser": "Crewmates can become Guesser", - "NeutralCanBeGuesser": "Neutrals can become Guesser", - "ImpCanBeWatcher": "Impostors can become Watcher", - "CrewCanBeWatcher": "Crewmates can become Watcher", - "NeutralCanBeWatcher": "Neutrals can become Watcher", - "ImpCanBeBait": "Impostors can become Bait", - "CrewCanBeBait": "Crewmates can become Bait", - "NeutralCanBeBait": "Neutrals can become Bait", - "ImpCanBeRainbow": "Impostors can become Rainbow", - "NeutralCanBeRainbow": "Neutrals can become Rainbow", - "CrewCanBeRainbow": "Crewmates can become Rainbow", - "GuessRainbow": "He seems too obvious, doesn't he?", - "RainbowColorChangeCoolDown": "The cooldown for changing colors", - "RainbowInCamouflage": "Rainbow color changes during Camouflage", - "BaitDelayMin": "Minimum Report Delay", - "BaitDelayMax": "Maximum Report Delay", - "BaitDelayNotify": "Warn the killer about the upcoming self-report", - "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", - "BaitNotification": "Reveal Bait at the first meeting", - "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self-report.", - "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if a meeting is disabled during comms sabotage", - "DeceiverSkillCooldown": "Ability cooldown", - "DeceiverSkillLimitTimes": "Max number of uses", - "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", - "PursuerSkillCooldown": "Ability cooldown", - "PursuerSkillLimitTimes": "Max number of uses", - "AddictSuicideTimer": "Time Until Suicide", - "GrenadierSkillCooldown": "Grenade Cooldown", - "GrenadierSkillDuration": "Grenade Duration", - "GrenadierCauseVision": "Lowered vision", - "GrenadierCanAffectNeutral": "Can affect Neutrals", - "TicketsPerKill": "Votes Increase Amount Per Kill", - "GangsterRecruitCooldown": "Recruit cooldown", - "GangsterRecruitLimit": "Recruit limit", - "KamikazeMaxMarked": "Max Marked", - "RevolutionistDrawTime": "Tag Duration", - "RevolutionistCooldown": "Tag Cooldown", - "RevolutionistDrawCount": "Amount of Players needed to Tag", - "RevolutionistKillProbability": "Tagged player sacrifice probability", - "RevolutionistVentCountDown": "Time to Vent", - "PelicanKillCooldown": "Eat Cooldown", - "Pelican.TargetCannotBeEaten": "Target cannot be eaten", - "MadSnitchTasks": "Snitch Tasks", - "MedicWhoCanSeeProtect": "Who can see shield", - "MedicKnowShieldBroken": "Who sees kill attempt", - "Medic_SeeMedicAndTarget": "Medic+Shielded", - "Medic_SeeMedic": "Medic", - "Medic_SeeTarget": "Shielded", - "Medic_SeeNoOne": "Nothing", - "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", - "MedicShielDeactivationIsVisible": "Shield deactivation is visible", - "MedicShieldDeactivationIsVisible_Immediately": "Immediately", - "MedicShieldDeactivationIsVisible_AfterMeeting": "After Meeting", - "MedicShieldDeactivationIsVisible_OFF": "OFF", - "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", - "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", - "FortuneTellerSkillLimit": "Max number of ability uses", - "MadmateSpawnMode": "Madmate spawning mode", - "MadmateSpawnMode.Assign": "Assign", - "MadmateSpawnMode.FirstKill": "First Kill", - "MadmateSpawnMode.SelfVote": "Self Vote", - "MadmateCountMode": "Madmates count as", - "MadmateCountMode.None": "Nothing", - "MadmateCountMode.Imp": "Impostors", - "MadmateCountMode.Original": "Original Team", - - "SnatchesWin": "Snatches victory", - "DemonKillCooldown": "Attack Cooldown", - "DemonHealthMax": "Player max health", - "DemonDamage": "Damage ", - "DemonSelfHealthMax": "Demon max health", - "DemonSelfDamage": "Demon damage received", - "LightningConvertTime": "Duration of the transformation to Quantum Ghost", - "LightningKillCooldown": "Lightning Cooldown", - "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", - "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", - "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", - "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", - "WorkaholicCannotWinAtDeath": "Can't win after they died", - "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", - "WorkaholicGiveAdviceAlive": "Advice at the first meeting if alive, can win after death, ghost tasks ON", - "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", - "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", - "KillAttackerWhenAbilityRemaining": "Kill attacker when ability is remaining", - "JinxSpellTimes": "Amount of Jinx Spells", - "CollectorCollectAmount": "Required number of votes", - "GlitchCanVote": "Can vote", - "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", - "MeetingReserved": "Max Bullets reserved for a meeting", - "AccurateCheckMode": "Can know specific role when tasks are not done", - "RandomActiveRoles": "Show random active roles in Fortune Teller hints", - "CamouflageCooldown": "Camouflage Cooldown", - "CamouflageDuration": "Camouflage Duration", - "NinjaMarkCooldown": "Mark Cooldown", - "NinjaAssassinateCooldown": "Assassinate Cooldown", - "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", - "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", - "JudgeCanTrialNeutralB": "Can trial Neutral Benign", - "JudgeCanTrialNeutralK": "Can trial Neutral Killing", - "JudgeCanTrialNeutralE": "Can trial Neutral Evil", - "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", - "JudgeCanTrialNeutralA": "Can trial Neutral Apocalypse", - "JudgeCanTrialSidekick": "Can trial Sidekick", - "JudgeCanTrialInfected": "Can trial Infected", - "JudgeCanTrialContagious": "Can trial Contagious", - "JudgeTryHideMsg": "Hide Judge's commands", - "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", - "JudgeCanTrialMadmate": "Can trial Madmates", - "JudgeCanTrialCharmed": "Can trial Charmed players", - "JudgeDead": "Sorry, you can't trial players after death.", - "JudgeTrialMax": "\nNo more trials left!", - "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", - "Judge_TrialKill": "{0} was judged.", - "Judge_TrialKillTitle": "COURT", - "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Judge_TrialNull": "Please choose a living player for the trial", - "VeteranSkillMaxOfUseage": "Max number of Alerts", - "SwooperCooldown": "Swoop Cooldown", - "SwooperDuration": "Swoop Duration", - "WraithCooldown": "Vanish Cooldown", - "WraithDuration": "Vanish Duration", - "BastionNotify": "A bomb was set off", - "EnteredBombedVent": "That vent was bombed!", - "BastionVentButtonText": "Bomb", - "BombsClearAfterMeeting": "Bombs clear after meetings", - "BastionMaxBombs": "(Initial) Maximum bombs", - "VentBombSuccess": "Bomb has been planted", - "LowLoadMode": "Low Load Mode", - "ShowLobbyCode": "Show lobby code in Discord status", - "BKProtectDuration": "Protection Duration", - "FollowerMaxBetTimes": "Maximum Number of Follows", - "FollowerBetCooldown": "Follow Cooldown", - "FollowerMaxBetCooldown": "Maximum Follow Cooldown", - "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", - "FollowerKnowTargetRole": "Follower knows their target's role", - "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", - "FortuneTellerHideVote": "Hide Fortune Teller's Votes", - "CultistCharmCooldown": "Charm Cooldown", - "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", - "CultistCharmMax": "Maximum Number Of Charm", - "CultistKnowTargetRole": "Know Charmed Player's Role", - "CultistTargetKnowOtherTarget": "Charmed players know each other", - "CultistCanCharmNeutral": "Neutral Roles can be Charmed", - "InfectiousBiteCooldown": "Infect Cooldown", - "KnowTargetRole": "Knows role of target", - "TargetKnowsLawyer": "Target knows their Lawyer", - "InfectiousBiteMax": "Maximum Infections", - "InfectiousKnowTargetRole": "Know infected player's role", - "InfectiousTargetKnowOtherTarget": "Infected players know each other", - "DoubleClickKill": "Double click to kill the target", - - "VirusInfectMax": "Maximum Number Of Spreads", - "VirusKnowTargetRole": "Know Contagious Player's Role", - "VirusTargetKnowOtherTarget": "Contagious players know each other", - "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", - "Virus_ContagiousCountMode": "Contagious players count as", - "Virus_ContagiousCountMode_None": "Nothing", - "Virus_ContagiousCountMode_Virus": "Virus", - "Virus_ContagiousCountMode_Original": "Original Team", - "VirusNoticeTitle": "[ Infected Corpse! ]", - "VirusNoticeMessage": "The body you reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", - "VirusNoticeMessage2": "The body you reported was infected by the Virus! Vote the Virus out during this meeting, or you will die.", - - "Cultist_CharmedCountMode": "Charmed players count as", - "Cultist_CharmedCountMode_None": "Nothing", - "Cultist_CharmedCountMode_Cultist": "Cultist", - "Cultist_CharmedCountMode_Original": "Original Team", - - "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", - "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", - "JackalResetKillCooldownOn": "Kill Cooldown On Reset", - "JackalCanRecruitSidekick": "Can recruit Sidekick", - "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", - "Jackal_SidekickCountMode": "Sidekicks count as", - "Jackal_SidekickCountMode_None": "Nothing", - "Jackal_SidekickCountMode_Jackal": "Jackal", - "Jackal_SidekickCountMode_Original": "Original Team", - "Jackal_SidekickAssignMode": "Sidekick Assign Mode", - "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", - "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", - "Jackal_SidekickAssignMode_Recruit": "Recruit Only", - "JackalWinWithSidekick": "Jackal can win with Sidekick's team", - "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", - "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", - "JackalCanKillSidekick": "Jackal can kill Sidekick", - - "ImpCanBeNecroview": "Impostors can become Necroview", - "CrewCanBeNecroview": "Crewmates can become Necroview", - "NeutralCanBeNecroview": "Neutrals can become Necroview", - "ImpCanBeInLove": "Impostors can be in love", - "CrewCanBeInLove": "Crewmates can be in love", - "NeutralCanBeInLove": "Neutrals can be in love", - "ImpCanBeOblivious": "Impostors can become Oblivious", - "CrewCanBeOblivious": "Crewmates can become Oblivious", - "NeutralCanBeOblivious": "Neutrals can become Oblivious", - "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", - "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", - "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", - "HexesLookLikeSpells": "Hexes appear as spells", - "HexButtonText": "Hex", - "ObliviousBaitImmune": "Immune to Bait", - "ImpCanBeOnbound": "Impostors can become Onbound", - "CrewCanBeOnbound": "Crewmates can become Onbound", - "NeutralCanBeOnbound": "Neutrals can become Onbound", - - "ImpCanBeRebound": "Impostors can become Rebound", - "CrewCanBeRebound": "Crewmates can become Rebound", - "NeutralCanBeRebound": "Neutrals can become Rebound", - - "CrewCanBeMundane": "Crewmates can become Mundane", - "NeutralCanBeMundane": "Neutrals can become Mundane", - "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", - - "ImpCanBeUnreportable": "Impostors can become Disregarded", - "CrewCanBeUnreportable": "Crewmates can become Disregarded", - "NeutralCanBeUnreportable": "Neutrals can become Disregarded", - "PacifistCooldown": "Ability Cooldown", - "PacifistMaxOfUseage": "Max Number of Ability Uses", - "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", - "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", - - "PresidentAbilityUses": "Max Number of Ability Uses", - "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", - "HidePresidentEndCommand": "Hide President's commands", - "NeutralsSeePresident": "Neutrals can see revealed President", - "MadmatesSeePresident": "Madmates can see revealed President", - "ImpsSeePresident": "Impostors can see revealed President", - "PresidentDead": "Sorry, you can't force end the meeting after death.", - "PresidentEndMax": "No more force end meeting uses left!", - "PresidentRevealMax": "You have already revealed yourself...", - "PresidentRevealed": "[{0}] has chosen to reveal themselves as President!", - "GuessPresident": "President has revealed themselves. You can't guess them.", - "PresidentRevealTitle": "PRESIDENT REVEAL", - - "LuckyProbability": "Probability of surviving a kill", - "ImpCanBeLucky": "Impostors can become Lucky", - "CrewCanBeLucky": "Crewmates can become Lucky", - "NeutralCanBeLucky": "Neutrals can become Lucky", - "ImpCanBeFool": "Impostors can become Fool", - "CrewCanBeFool": "Crewmates can become Fool", - "NeutralCanBeFool": "Neutrals can become Fool", - "ImpCanBeDoubleShot": "Impostors can have Double Shot", - "CrewCanBeDoubleShot": "Crewmates can have Double Shot", - "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", - "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", - "DisableReportWhenCamouflageIsActive": "Disable body reporting when camouflage is active", - "CanUseCommsSabotage": "Can use comms sabotage", - "ModTag": "Moderator♥", - "ApplyModeratorList": "Apply Moderator List", - "VipTag": "VIP★", - "ApplyVipList": "Apply VIP List", - "AllowSayCommand": "Allow moderators to use /say command", - "KickCommandDisabled": "The kick command is currently disabled.", - "KickCommandNoAccess": "You do not have access to the kick command.", - "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reason]' to kick a player.\nExample :- /kick 5 not following rules", - "KickCommandKickHost": "You are not permitted to kick the host.", - "KickCommandKickMod": "You are not permitted to kick other moderators.", - "KickCommandKicked": "was kicked from the game by ", - "KickCommandKickedRole": "Their role was", - "BanCommandDisabled": "The ban command is currently disabled.", - "BanCommandNoAccess": "You do not have access to the ban command.", - "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", - "BanCommandBanHost": "You are not permitted to ban the host.", - "BanCommandBanMod": "You are not permitted to ban other moderators.", - "BanCommandBanned": "was banned from the game by ", - "BanCommandBannedRole": "Their role was", - "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", - "ColorCommandDisabled": "The modcolor command is currently disabled.", - "ColorCommandNoAccess": "You do not have access to the modcolor command.", - "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change the color of MODERATOR♥.\nExample :- /modcolor 33ccff", - "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", - "VipColorCommandDisabled": "The vipcolor command is currently disabled.", - "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", - "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", - "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change the color of VIP★.\nExample :- /vipcolor 33ccff", - "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", - "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change the color of your tag.\nExample :- /tagcolor ff00ff", - "midCommandDisabled": "The mid command is currently disabled.", - "midCommandNoAccess": "You do not have access to the mid command.", - "WarnCommandDisabled": "The warn command is currently disabled.", - "WarnCommandNoAccess": "You do not have access to the warn command.", - "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", - "WarnCommandWarnHost": "You are not permitted to warn the host.", - - "WarnCommandWarnMod": "You are not permitted to warn other moderators.", - "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", - "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", - "SayCommandDisabled": "The say command is currently disabled.", - "MessageFromModerator": "MODERATOR", - "DeathReason.Kill": "Kill", - "DeathReason.Vote": "Ejected", - "DeathReason.Suicide": "Suicide", - "DeathReason.Spell": "Spelled", - "DeathReason.Cursed": "Cursed", - "DeathReason.Hex": "Hexed", - "DeathReason.Bite": "Bitten", - "DeathReason.Poison": "Poisoned", - "DeathReason.Gambled": "Guessed", - "DeathReason.FollowingSuicide": "Heartbroken", - "DeathReason.Bombed": "Exploded", - "DeathReason.Misfire": "Misfire", - "DeathReason.Torched": "Burned", - "DeathReason.Sniped": "Sniped", - "DeathReason.Execution": "Executed", - "DeathReason.Fall": "Fall", - "DeathReason.Revenge": "Revenge", - "DeathReason.Eaten": "Eaten", - "DeathReason.Sacrifice": "Victim", - "DeathReason.Quantization": "Quantization", - "DeathReason.Overtired": "Overtired", - "DeathReason.Ashamed": "Ashamed", - "DeathReason.PissedOff": "Destroyed", - "DeathReason.Dismembered": "Dismembered", - "DeathReason.LossOfHead": "Strangled", - "DeathReason.Trialed": "Judged", - "DeathReason.Infected": "Infected", - "DeathReason.Jinx": "Jinxed", - "DeathReason.Pirate": "Plundered", - "DeathReason.Shrouded": "Shrouded", - "DeathReason.etc": "Other", - "DeathReason.Mauled": "Mauled", - "DeathReason.Hack": "Hacked", - "DeathReason.Curse": "Cursed", - "DeathReason.Drained": "Drained", - "DeathReason.Shattered": "Shattered", - "DeathReason.Trap": "Trapped", - "DeathReason.Targeted": "Targeted", - "DeathReason.Retribution": "Retribution", - "DeathReason.Slice": "Sliced", - "DeathReason.BloodLet": "Bleed", - "DeathReason.Armageddon": "Armageddon", - "DeathReason.Starved": "Starved", - "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", - "Alive": "Alive", - "Disconnected": "Disconnected", - "Win": " Wins!", - - "Last-": "Last ", - "Madmate-": "Madmate ", - "Recruit-": "Recruit ", - "Charmed-": "Charmed ", - "Soulless-": "Soulless ", - "Infected-": "Infected ", - "Contagious-": "Contagious ", - "Admired-": "Admired ", - - "DeputyHandcuffCooldown": "Handcuff Cooldown", - "DeputyHandcuffMax": "Maximum Handcuffs", - "DeputyHandcuffedPlayer": "Handcuffed target", - "HandcuffedByDeputy": "You were handcuffed!", - "DeputyInvalidTarget": "Target cannot be handcuffed", - "DeputyHandcuffText": "Handcuff", - "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", - - "RejectShapeshift.AbilityWasUsed": "Ability was used", - - "EscapisMtarkedPosition": "You marked self-position", - - "InvestigateCooldown": "Investigate Cooldown", - "InvestigateMax": "Maximum Investigations", - "InvestigateRoundMax": "Maximum Investigations in one round", - - "Color.Red": "Red", - "Color.Green": "Green", - "Color.Gray": "Gray", - "InvestigatorInvestigatedPlayer": "Player Investigated", - "InvestigatorInvalidTarget": "Can not investigate", - "InvestigatorButtonText": "Check", - - "Investigator.Suspicion": "Suspicion", - "Investigator.Role": "Role", - "SabotageCooldownControl": "Sabotage Cooldown Control", - "SabotageCooldown": "Sabotage Cooldown", - "SabotageTimeControl": "Sabotage Duration Control", - "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", - "SkeldO2TimeLimit": "The Skeld O2 Time Limit", - "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", - "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", - "PolusReactorTimeLimit": "Polus Reactor Time Limit", - "AirshipReactorTimeLimit": "Airship Reactor Time Limit", - "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", - "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", - "CommandList": "★ Command list:", - "Command.now": "→ Display active Settings", - "Command.roles": "[RoleName] → Display Role description", - "Command.myrole": "→ Displays a description of your role", - "Command.lastresult": "→ Display match results", - "Command.winner": "→ Display winners", - "CommandOtherList": "● Other commands:", - "Command.color": "[Color] → Change your color", - "Command.rename": "[Name] → Change Host Name", - "Command.quit": "→ I don't want to enter this lobby anymore", - "CommandHostList": "▲ Host Commands:", - "Command.say": "[Content] → Send message as Host", - "Command.mw": "[Seconds] → Set the message waiting duration", - "Command.solvecover": "→ Fix an issue where role names overlap the messages", - "Command.kill": "[Player ID] → Kill assigned player", - "Command.exe": "[Player ID] → Eject assigned player", - "Command.level": "[Level] → Change your in-game level", - "Command.idlist": "→ Display a list of player IDs", - "Command.qq": "→ Lobby will be posted on QQ website (China only)", - "Command.dump": "→ Output Log to Desktop", - "Command.death": "→ Display info on how you died", - "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Capitan to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.", - - "Command.iconinfo": "→ Display info on in-meeting icons", - "Command.iconhelp": "→ Display info on in-meeting icons to everyone", - "Command.Poll": "→ Start a poll with up-to 5 choices", - "IconsTitle": "Icon Meanings⚠", - "Remaining.ImpostorCount": "Impostors left: {0}", - "Remaining.MadmateCount": "Madmates left: {0}", - "Remaining.NeutralCount": "Neutral Killers left: {0}", - "Remaining.ApocalypseCount": "Neutral Apocalypse left: {0}", - "EnableKillerLeftCommand": "Enable use of /kcount command", - "ShowMadmatesInLeftCommand": "Show Madmates (including add-ons)", - "ShowApocalypseInLeftCommand": "Show Neutral Apocalypse", - "SeeEjectedRolesInMeeting": "See ejected roles in meetings", - - "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", - "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", - "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", - "NemesisKillDead": "Choose a living player to take revenge", - "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", - "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", - "NemesisKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", - - "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period!", - "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", - "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", - "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", - "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer. This death is most likely suicide.", - "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", - "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", - "GuessDead": "Sorry, but it's impossible to guess roles after your death", - "GuessSuperStar": "The Super Star can't be guessed... you thought it would be that easy, right?", - "GuessNotifiedBait": "Bait can't be guessed because it was announced. You thought it would be that easy, right?", - "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", - "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", - "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", - "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", - "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", - "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", - "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", - "GuessKill": "{0} was guessed", - "GuessNull": "Please select an ID of a living player to guess their role", - "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "GGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", - "EGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", - "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all their tasks are done? Nice try. You're not getting out of this that easily.", - "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot, so you get another chance!", - "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", - "GuessDisabled": "Sorry, the host restricted guessing for your role.", - "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", - "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", - "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", - "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", - "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", - "GuessShielded": "Sorry, you can't guess the player who the Medic shields", - "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", - "MimicDeadMsg": "Mimic's hint: ", - "FortuneTellerCheck": "According to your fortune...", - "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", - "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", - "FortuneTellerCheckReachLimit": "You've run out of fortunes.", - "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", - "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", - "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", - - "MediumContactLimit": "Max number of contacts (ability uses)", - "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", - "MediumTitle": "MEDIUM", - "MediumHelp": "/ms yes to agree\n/ms no to disagree", - "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", - "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", - "MediumDone": "You successfully responded to the Medium.", - "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", - "MediumNotifySelf": "You established contact with {0}. Please ask them questions and wait for them to respond.\n\nRemaining ability uses: {1}", - "MediumKnowPlayerDead": "Someone died somewhere", - - "ByBard": "by Bard", - "ByBardGetFailed": "Oops, I seem to be out of inspiration.", - "GangsterSuccessfullyRecruited": "You successfully recruited a player", - "GangsterRecruitmentFailure": "Target cannot be recruited", - "BeRecruitedByGangster": "The Gangster has recruited you", - "KamikazeHostage": "Can't hold target hostage", - "VeteranOnGuard": "Ability in use", - "VeteranOffGuard": "Ability expired, {0} uses remain", - "VeteranMaxUsage": "Ability use limit reached", - "GrenadierSkillInUse": "Ability in use", - "GrenadierSkillStop": "Ability expired", - "TicketsStealerGetTicket": "You've got {0} votes", - "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", - "CleanerCleanBody": "The body has been cleaned", - "QuickShooterStoraging": "Bullets stored successfully", - "PoisonerTargetDead": "Target died", - "BloodthirstAdded": "Your bloodthirst is now active!", - "WarlockNoTarget": "Manipulation failed due to no target", - "WarlockNoTargetYet": "You haven't marked a target.", - "WarlockTargetDead": "Manipulation failed due to target dead", - "WarlockControlKill": "Target died", - "OnCelebrityDead": "Warning: Celebrity death!", - "OnCyberDead": "Warning: Cyber died!", - "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", - "TeleportedByTransporter": "Swapping places with: {0}", - "ErrorTeleport": "Teleport failed", - "EraseLimit": "Max Erases", - "EraserHideVote": "Hide Eraser Votes", - "EraserEraseMsgTitle": "ERASER", - "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", - "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", - "EraserEraseSelf": "Unfortunately, you can't erase yourself... Wait, why would you do that in the first place?!", - "EraserTryingGuessErasedPlayer": "You can't guess the role of the player you erased, except add-ons", - "LostRoleByEraser": "You lost your role because of the Eraser", - "KilledByScavenger": "The Scavenger killed you and thus teleported off-map", - "SnitchDoneTasks": "Call a meeting to find the impostors", - "SwooperCanVent": "Vent to turn invisible", - "SwooperInvisState": "You're invisible", - "SwooperInvisStateOut": "You're now visible", - "SwooperInvisInCooldown": "Swoop cooldown isn't up yet. Swooping failed", - "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", - "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", - "WraithCanVent": "Vent to turn invisible", - "WraithInvisState": "You are invisible", - "WraithInvisStateOut": "You are visible again", - "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", - "WraithInvisStateCountdown": "Invisibility will expire in {0}s", - "WraithInvisCooldownRemain": "{0}s left in invisibility", - "WerewolfKillButtonText": "Maul", - "BKInProtect": "Currently immortal", - "BKProtectOut": "Shield expired", - "BKSkillTimeRemain": "You're immune for {0} seconds", - "BKSkillNotice": "Kill a player to enter immune status", - "BKOffsetKill": "Someone tried killing you", - "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", - "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", - "FollowerBetPlayer": "You're now following your target", - "FollowerBetOnYou": "The Follower is now following you", - "CultistCharmedPlayer": "You successfully charmed a player", - "CharmedByCultist": "You have been charmed by the Cultist", - "CultistInvalidTarget": "Target cannot be charmed", - "KillBaitNotify": "You'll self-report in {0}s", - "InfectiousInvalidTarget": "Target cannot be infected", - "BittenByInfectious": "The Infectious infected you!", - "InfectiousBittenPlayer": "You successfully infected a player", - "GuessNotAllowed": "Sorry, your role does not have access to guessing.", - "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", - "GuessSpecter": "You can't guess a Specter. That allows them to win!", - "PacifistOnGuard": "Ability used, {0} uses remain", - "PacifistMaxUsage": "Ability use limit reached", - "PacifistSkillNotify": "Pacifist reset your kill cooldown", - "BeRecruitedByJackal": "The Jackal has recruited you", - "CoronerTrackRecorded": "Track recorded", - "CoronerNoTrack": "Nothing to track", - "CoronerIsTrackingYou": "The Coroner is tracking you!", - "CoronerReportButtonText": "Track", - "MerchantAddonDelivered": "Add-on sold", - "MerchantAddonSell": "The Merchant sold you a new Add-on", - "MerchantAddonSellFail": "Could not sell an Add-on", - "BribedByMerchant": "The Merchant bribed you. You can't kill him", - "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", - "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", - "TrapTrapsterBody": "Trap Trapster's body", - "TrapConsecutiveBodies": "Trap consecutive bodies", - "HauntedByEvilSpirit": "Haunted by an Evil Spirit", - "MonarchKnightCooldown": "Knight Cooldown", - "MonarchKnightMax": "Maximum Knights", - "HideAdditionalVotesForKnighted": "Hide additional vote for Knighted players", - "MonarchKnightedPlayer": "You successfully knighted a player!", - "KnightedByMonarch": "A Monarch has knighted you!", - "MonarchInvalidTarget": "Target cannot be knighted", - "GhostTransformTitle": "Your Role Has Transformed!", - "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", - "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", - "OverseerRevealCooldown": "Reveal Cooldown", - "OverseerRevealTime": "Reveal Time", - "OverseerVision": "Overseer Vision", - "MerchantMaxSell": "Max number of Add-ons to sell", - "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", - "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", - "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", - "MerchantTargetCrew": "Can sell to Crewmates", - "MerchantTargetImpostor": "Can sell to Impostors", - - "MerchantTargetNeutral": "Can sell to Neutrals", - "MerchantSellHelpful": "Can sell Helpful Add-ons", - "MerchantSellHarmful": "Can sell Harmful Add-ons", - "MerchantSellMixed": "Can sell Mixed Add-ons", - "MerchantSellExperimental": "Can sell experimental Add-ons", - "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", - "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", - "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", - - "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", - "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", - "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", - "SpiritcallerProtectTime": "Evil Spirit ability protect time", - "SpiritcallerCauseVision": "Evil Spirit ability caused vision", - "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", - "Message.SetToSeconds": "Set to [{0}] seconds.", - "Message.MessageWaitHelp": "Specify the first argument in seconds.", - "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", - "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", - "Message.SyncButtonLeft": "There are {0} more emergency buttons left", - "Message.Executed": "{0} was executed", - "Message.HideGameSettings": "The host has hidden the game settings.", - "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", - "Message.NoDescription": "No description", - "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", - "Message.BannedByBanList": "{0} was banned because they were banned in the past.", - "Message.BannedByEACList": "{0} has been banned because he is in the EAC list of Banned people.", - "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", - "Message.DumpcmdUsed": "{0} used /dump command.", - "Message.KickedByInvalidFriendCode": "{0} was kicked because their friend code is invalid.", - "Message.TempBannedByInvalidFriendCode": "{0} was temporarily banned because their friend code is invalid.", - "Message.AddedPlayerToBanList": "Added {0} to the ban list", - "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", - "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", - "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", - "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", - "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", - "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", - "Message.NoticeByEAC": "[{0}]Detected:{1}", - "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", - "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", - "Message.KickedByWhiteList": "{0} kicked because their friendcode was not found in WhiteList.txt", - "Message.SetLevel": "Your game level is set to: {0}", - "Message.SetColor": "Your color is set to: {0}", - "Message.SetName": "Your name is set to: {0}", - "Message.AllowLevelRange": "The game level can be set in the range: 0-100", - "Message.AllowNameLength": "Nickname can be set length: 1-10", - "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", - "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", - "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", - "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", - "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", - "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", - "Message.HostLeftGameInGame": "★Warning★ Host left the game, and the game wouldn't start normally next time. Please exit the lobby or wait until the new Host opens a lobby.", - "Message.HostLeftGameInLobby": "★Warning★ Host left the game, and the game wouldn't start normally next time. If the new Host has TOHE, you need to re-enter the lobby to play normally.", - "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, start a game and end it immediately to reset the lobby!", - "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please exit the lobby or wait until the new Host opens a lobby.", - "Message.LobbyShared": "The lobby has successfully been shared!", - "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", - "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", - "Message.YTPlanSelected": "In the next game, your role will be {0}", - "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", - "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", - "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. If the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", - "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", - "Message.MaxPlayers": "Maximum players set to ", - "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", - "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", - "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", - "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", - "Message.MeCommandNoPermission": "You are not allowed to use /me command for others", - - "PollTitle": "〖 Poll 〗", - "PollResultTitle": "Poll Results", - "Poll.Result": "And... The winner was {0} with {1} votes!\n\nRunner ups:", - "Poll.Tied": "Uh oh, The vote was tied between {0}, all having {1} votes.", - "Poll.MissingPlayers": "You can't start a poll with yourself dummy ;3", - - "Poll.Begin": "You may vote using /pv {answer}, ps: a number also works.", - "Poll.TimeInfo": "The results will be final in 2 minuttes", - "Poll.OnlyInLobby": "<#ab4f75>Sorry, this command may only be used in lobby", - - "Poll.Inactive": "There isn't any active poll currently.", - "Poll.AlreadyVoted": "You already cast your vote, so it won't be counted.", - - "Poll.VotingInfo": "Use /pv {answer} to vote, answer can be a character or a number.", - "Poll.YouVoted": "You have voted for {0}, which has now {1} votes.", - "PollUsage": "To create a poll type \n/poll {Question}? {Answer A} {Answer B} \n{Answer C (Optional)} {Answer D (Optional)} {Answer E (Optional)}\nIt is important you end the question with a ? \n\nUse /poll Replay to replay the latest poll", - "Replay": "Replay", - - "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", - "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", - "WarningTitle": "Warning!", - "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", - "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map, the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", - - "AntiBlackoutProtectionTitle": "Anti Blackout", - "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normally", - "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in the last meeting.\n", - "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", - - - - "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE are installed.", - "Warning.NoModHost": "TOHE is not installed on the host", - "Warning.MismatchedVersion": "{0} has a different version of {1}", - "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", - "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", - "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", - "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", - "Error.InvalidColor": "Error: Only default colors are available", - "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors. Otherwise, it will result in a serious error", - "ErrorLevel1": "Bugs may occur.", - "ErrorLevel2": "This may be a bug.", - "ErrorLevel3": "This version shouldn't have been released.", - "TerminateCommand": "Abort Command", - "ERR-000-000-0": "No Error", - "ERR-000-900-0": "Test Error Lv.0", - "ERR-000-910-1": "Test Error Lv.1", - "ERR-000-920-2": "Test Error Lv.2", - "ERR-000-930-3": "Test Error Lv.3", - "ERR-000-804-1": "Sorry, TOHE temporarily not support the Vanilla HnS, so mod unloaded", - "ERR-001-000-3": "Main dictionary has duplicated keys.", - "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", - "DefaultSystemMessageTitle": "SYSTEM MESSAGE", - "MessageFromTheHost": "HOST MESSAGE", - "MessageFromEAC": "EAC", - "DetectiveNoticeTitle": "INVESTIGATION", - "SleuthNoticeTitle": "SLEUTH", - "GuessKillTitle": "GUESSING INFO", - "CelebrityNewsTitle": "CELEBRITY", - "CyberNewsTitle": "CYBER", - "GodAliveTitle": "GOD ", - "WorkaholicAliveTitle": "WORKAHOLIC", - "BaitAliveTitle": "BAIT", - "MessageFromKPD": "KARPED1EM ", - "MessageFromSponsor": "SPONSOR MESSAGE ", - "MessageFromDev": "DEVELOPER MESSAGE ", - "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", - "MimicMsgTitle": "MIMIC", - "MorticianCheckTitle": "CORPSE EXAMINATION", - "NemesisRevengeTitle": "NEMESIS", - "RetributionistRevengeTitle": "RETRIBUTIONIST", - "TabVanilla.GameSettings": "Game Settings", - "TabGroup.SystemSettings": "System Settings", - "TabGroup.ModSettings": "Mod Settings", - "TabGroup.ModifierSettings": "Game Modifiers", - "TabGroup.CrewmateRoles": "Crewmate Roles", - "TabGroup.NeutralRoles": "Neutral Roles", - "TabGroup.ImpostorRoles": "Impostor Roles", - "TabGroup.Addons": "Add-Ons", - "TabMenuDescription_General": "Here you can configure the functions that are in the mod", - "TabMenuDescription_Roles&AddOns": "Here you can add, remove and change the settings of all roles or add-ons in the mod", - "Experimental.Roles": "★ Experimental Roles (NOTICE: Use with caution, as these require testing)", - "ActiveRolesList": "Active Roles List", - "ForExample": "Example Use", - "updateButton": "Update", - "updatePleaseWait": "Please Wait...", - "updateManually": "Update failed.\nPlease try again or Update Manually.", - "updateInProgress": "Updating...", - "deletingFiles": "Deleting update files...", - "updateRestart": "Update Finished!\nPlease restart the game.", - "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease Update.", - "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", - "UnsupportedVersion": "Unsupported Among Us version.\nPlease Update Among Us", - "DisabledByProgram": "The program has disabled public rooms", - "EnterVentToWin": "Enter Vent to Win!!", - "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", - "FireworkerPutPhase": "{0} Fireworker Left", - "FireworkerWaitPhase": "Wait for it...", - "FireworkerReadyFirePhase": "Fire!", - "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", - "On": "ON", - "Off": "OFF", - "ColoredOn": "ON", - "ColoredOff": "OFF", - "CurrentActiveSettingsHelp": "Current Active Settings Help", - "WitchCurrentMode": "Current Mode", - "WitchModeKill": "Kill", - "WitchModeSpell": "Spell", - "HexMasterModeHex": "Hex", - "HexMasterModeKill": "Kill", - "PoisonerPoisonButtonText": "Poison", - "WitchModeDouble": "Double Click = Kill, Single Click = Spell", - "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", - "BountyCurrentTarget": "Current Target", - "Roles": "Roles", - "Settings": "Settings", - "Addons": "Add-Ons", - "LastResult": "★ Match Results", - "LastEndReason": "★ End Reason", - "KillLog": "Kill Log", - "Maximum": "Max", - "RoleRate": "ON", - "RoleOn": "ALWAYS", - "RoleOff": "OFF", - "Chance0": "0%", - "Chance5": "5%", - "Chance10": "10%", - "Chance15": "15%", - "Chance20": "20%", - "Chance25": "25%", - "Chance30": "30%", - "Chance35": "35%", - "Chance40": "40%", - "Chance45": "45%", - "Chance50": "50%", - "Chance55": "55%", - "Chance60": "60%", - "Chance65": "65%", - "Chance70": "70%", - "Chance75": "75%", - "Chance80": "80%", - "Chance85": "85%", - "Chance90": "90%", - "Chance95": "95%", - "Chance100": "100%", - "Preset": "Preset", - "Preset_1": "Preset 1", - "Preset_2": "Preset 2", - "Preset_3": "Preset 3", - "Preset_4": "Preset 4", - "Preset_5": "Preset 5", - "Standard": "Standard", - "GameMode": "Game Mode", - "PressTabToNextPage": "Press Tab or Number for Next Page...", - "RoleSummaryText": "Role Summary:", - "doOverride": "Override %role%'s Tasks", - "assignCommonTasks": "%role% has Common Tasks", - "roleLongTasksNum": "Amount of Long Tasks for %role%", - "roleShortTasksNum": "Amount of Short Tasks for %role%", - "Format.Players": "{0}", - "Format.Seconds": "{0}s", - "Format.Percent": "{0}%", - "Format.Times": "{0}", - "Format.Multiplier": "{0}x", - "Format.Votes": "{0}", - "Format.Pieces": "{0}", - "Format.Health": "{0}", - "Format.Level": "{0}", - "KillButtonText": "Kill", - "ReportButtonText": "Report", - "VentButtonText": "Vent", - "SabotageButtonText": "Sabotage", - "SniperSnipeButtonText": "Snipe", - "FireworkerExplosionButtonText": "Detonate", - "FireworkerInstallAtionButtonText": "Install", - "MercenarySuicideButtonText": "Suicide Timer", - "WarlockCurseButtonText": "Curse", - "NinjaShapeshiftText": "Kill", - "NinjaMarkButtonText": "Mark", - "WitchSpellButtonText": "Spell", - "VampireBiteButtonText": "Bite", - "MinerTeleButtonText": "Warp", - "ArsonistDouseButtonText": "Douse", - "PuppeteerOperateButtonText": "Manipulate", - "WarlockShapeshiftButtonText": "Spell", - "BountyHunterChangeButtonText": "Swap", - "EvilTrackerChangeButtonText": "Track", - "InnocentButtonText": "Frame", - "PelicanButtonText": "Eat", - "DeceiverButtonText": "Cheat", - "PursuerButtonText": "Trick", - "GangsterButtonText": "Recruit", - "RevolutionistDrawButtonText": "Win over", - "HaterButtonText": "Hatred", - "MedicalerButtonText": "Protect", - "DemonButtonText": "Attack", - "SoulCatcherButtonText": "Teleport", - "LightningButtonText": "Evaporate", - "ProvocateurButtonText": "Greet", - "ButcherButtonText": "Dismember", - "BomberShapeshiftText": "Explode", - "QuickShooterShapeshiftText": "Keep", - "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", - "CamouflagerShapeshiftTextAfterDisguise": "Duration", - "AnonymousShapeshiftText": "Hack", - "DefaultShapeshiftText": "Shift", - "CleanerReportButtonText": "Clean", - "SwooperVentButtonText": "Swoop", - "SwooperRevertVentButtonText": "Expose", - "WraithVentButtonText": "Vanish", - "WraithRevertVentButtonText": "Expose", - "VectorVentButtonText": "Hop", - "VeteranVentButtonText": "Alert", - "GrenadierVentButtonText": "Flash", - "MayorVentButtonText": "Button", - "SheriffKillButtonText": "Shoot", - "UndertakerButtonText": "Mark", - "ArsonistVentButtonText": "Ignite", - "RevolutionistVentButtonText": "Revolution", - "FollowerKillButtonText": "Follow", - "PacifistVentButtonText": "Reset", - "CultistKillButtonText": "Charm", - "InfectiousKillButtonText": "Infect", - "MonarchKillButtonText": "Knight", - "OverseerKillButtonText": "Reveal", - "DisabledBySettings": "Disabled by Settings", - "Disabled": "Disabled", - "FailToTrack": "Failed To Track", - "KillCount": "Kills: {0}", - "CantUse.lastroles": "Unable to use /lastroles during a game.", - "CantUse.killlog": "Unable to use /killlog during a game.", - "CantUse.lastresult": "Unable to use /lastresult during a game.", - "IllegalColor": "Please enter the correct color", - "DisableUseCommand": "The Host's settings do not allow this command to be used.", - "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", - "PlayerIdList": "List of player IDs: ", - "CancelStartCountDown": "The starting countdown was canceled", - "RestTOHESetting": "TOHE settings have been restored to default", - "FPSSetTo": "FPS Set To: {0}", - "HostKillSelfByCommand": "The lobby Host decided to commit suicide", - "SyncCustomSettingsRPC": "Synchronized RPC", - "Mode": "Mode", - "Target": "Target", - "PlayerInfo": "Player Info", - "NoInfoExists": "No Info Exists", - "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", - "PlayerLeftByError": "Game will auto-end to prevent black screens.", - "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", - "KickBecauseLowLevel": "{0} was kicked because their level was too low", - "TempBannedBecauseLowLevel": "{0} was temporarily banned because their level was too low", - "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", - - "FFADisplayScore": "Ranking: {0} Score: {1}", - "FFATimeRemain": "Time Remaining: {0} second(s)", - - "GameOver": "Game Over", - "TOHEOptions": "TOHE Options", - "Cancel": "Cancel", - "Back": "Back", - "Yes": "Yes", - "No": "No", - - "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent a black screen.", - "AntiBlackOutNotifyInLobby": "An error occurred to prevent a black screen. Do a «/dump» and send the logs to the discord server TOHE in «bug-reports» and we will try to fix it.", - - "EndWhenPlayerBug": "End the game when a modded player gets a critical error (While loading)", - "AntiBlackOutRequestHostToForceEnd": "You were the reason for the black screen. The game will end", - "AntiBlackOutHostRejectForceEnd": "You were the reason for the black screen, and the host is not going to end the game\nYou will be disconnected soon", - - "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred. To prevent a black screen, turn off [{1}] in settings.", - "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, the game will end to prevent a black screen.", - "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", - - "NextPage": "Next Page", - "PreviousPage": "Previous Page", - "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", - "PressF1ShowMainRoleDes": "Press F1: Show Role Description", - "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", - "PressF3ShowRoleSettings": "Press F3: Show Role Settings", - "PressF4ShowAddOnsSettings": "Press F4: Show Add-ons Settings", - "FakeTask": "Fake Tasks:", - "PVP.ATK": "Attack", - "PVP.DF": "Defend", - "PVP.RCO": "Recover", - "SettingsAreLoading": "Loading\nsettings...", - "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", - "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", - "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", - "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", - "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", - "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", - "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", - "IsGood": "{0} was a good guy", - "BelongTo": "{0} belongs to {1}", - "PlayerIsRole": "{0} was The {1}", - "PlayerExiled": "{0} was ejected", - "NoImpRemain": "0 Impostors remain", - "OneImpRemain": "1 Impostor remains", - "TwoImpRemain": "2 Impostors remain", - "ThreeImpRemain": "3 Impostors remain", - "ImpRemain": "{0} Impostors remaining", - "NeutralRemain": "\n{0} Neutral Killers remain", - "OneNeutralRemain": "\n{0} Neutral Killer remains", - "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", - "GameOverReason.HumansByTask": "The Crewmates completed all tasks", - "GameOverReason.HumansDisconnect": "Crewmates disconnected", - "GameOverReason.ImpostorByVote": "The Crewmates were ejected", - "GameOverReason.ImpostorByKill": "The Impostors killed everyone", - "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", - "GameOverReason.ImpostorDisconnect": "Impostors disconnected", - "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", - "DevAndSpnTitle": "TOHE family", - "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", - "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", - "SunnyboyChance": "Sunnyboy Chance", - "BardChance": "Bard Chance", - "SkeldChance": "Chance that the map is The Skeld", - "MiraChance": "Chance that the map is MIRA HQ", - "PolusChance": "Chance that the map is Polus", - "DleksChance": "Chance that the map is dlekS ehT", - "AirshipChance": "Chance that the map is Airship", - "FungleChance": "Chance that the map is The Fungle", - "UseMoreRandomMapSelection": "Use a more random map selection", - "CamouflageMode.Default": "Default", - "CamouflageMode.Host": "Host", - "CamouflageMode.Random": "Random", - "CamouflageMode.OnlyRandomColor": "Only Random Color", - "CamouflageMode.Karpe": "KARPED1EM", - "CamouflageMode.Lauryn": "Lauryn", - "CamouflageMode.Moe": "Moe", - "CamouflageMode.Pyro": "Pyro", - "CamouflageMode.ryuk": "ryuk", - "CamouflageMode.Gurge44": "Gurge44", - "CamouflageMode.TommyXL": "TommyXL", - "CamouflageMode.Sarha": "Sarha", - "DeathCmd.HeyPlayer": "Hey ", - "DeathCmd.YouAreRole": ", looks like you're the ", - "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", - "DeathCmd.KillerName": "You were killed by ", - "DeathCmd.KillerRole": "Their role is ", - "DeathCmd.DeathReason": "Your cause of death was ", - "DeathCmd.YourName": "You are ", - "DeathCmd.YourRole": "Your role is ", - "DeathCmd.Ejected": "You were ejected during a meeting", - "DeathCmd.Misfired": "You misfired.", - "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", - "DeathCmd.Lovers": "Your lover had died.", - - "RpsCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", - "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", - "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", - "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I have the world's worst luck algorithm.", - - "CoinFlipCommandInfo": "This Command can only be used when in the lobby or after you die.", - "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", - - "GNoCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", - "GNoLost": "Oh, you were so close! Just one more guess: you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", - "GNoLow": "Oh, you're really nailing this! It's so low. I almost need a shovel to dig it up!\nYou have {0} guesses left!", - "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high that I need a telescope to see it from here! \nYou have {0} guesses left!", - "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", - - "RandCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", - "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", - - "8BallTitle": "The Magic 8 Ball Reveals...", - "8BallYes": "Yes", - "8BallNo": "No", - "8BallMaybe": "Maybe", - "8BallTryAgainLater": "Ask again later", - "8BallCertain": "It is certain", - "8BallNotLikely": "Outlook not so good", - "8BallLikely": "Outlook good", - "8BallDontCount": "Don't count on it", - "8BallStop": "Stop using an 8Ball in an Among Us mod", - "8BallPossibly": "Possibly", - "8BallProbably": "Probably", - "8BallProbablyNot": "Probably not", - "8BallBetterNotTell": "Better not tell you now", - "8BallCantPredict": "Cannot predict now", - "8BallWithoutDoubt": "Without a doubt", - "8BallWithDoubt": "Very doubtful", - - "ChanceToMiss": "Chance to miss a kill", - - "SoulCollectorPointsToWin": "Required number of souls", - "SoulCollectorTarget": "You have predicted the death of {0}", - "SoulCollectorTitle": "SOUL COLLECTOR", - "SoulCollector_CollectOwnSoulOpt": "Can collect their own soul", - "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", - "SoulCollectorToDeath": "You have become Death!!!", - "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", - "GetPassiveSouls": "Gain a passive soul every round", - "PassiveSoulGained": "You have gained a passive soul from the underworld.", - "SoulCollectorTargetUsed": "You've already targeted someone this round!", - "SoulCollectorSoulGained": "Soul gained", - "SoulCollectorCanVent": "Soul Collector can Vent", - "DeathMeetingTimeIncrease": "Increased Meeting time when Death exists", - "SoulCollectorMeetingDeath": "Your target has died during the meeting. You have gained a soul.", - "SoulCollectorKillButtonText": "Predict", - - "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", - "ApocalypseImmune": "This player is immune because they are invincible!", - "BakerToFamine": "You have become Famine!!!", - "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", - "BakerAlreadyBreaded": "That player already has bread!", - "BakerBreadUsedAlready": "You've already given a player bread this round!", - "BakerBreaded": "Player given bread", - "BakerBreadNeededToTransform": "Required number of bread to become Famine", - "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", - "BakerKillButtonText": "Bread", - "BakerRevealBread": "Reveal", - "BakerRoleblockBread": "Roleblock", - "BakerBarrierBread": "Barrier", - "BakerCurrentBread": "Current Bread: ", - "BakerSwitchBread": "Bread Switched to: ", - "BakerCanVent": "Baker can Vent", - "BakerBreadGivesEffects": "Bread gives additional effects", - "FamineKillButtonText": "Starve", - "FamineStarveCooldown": "Famine starve cooldown", - "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", - "FamineAlreadyStarved": "That player has already been starved!", - "FamineStarved": "Player starved", - - "ChronomancerKillCooldown": "Ability Charge Time", - "ChronomancerDecreaseTime": "Slaughter Decrease Time (lower is faster)", - "ChronomancerStartMassacre": "SLAUGHTER: ACTIVATED", - "ChronomancerVisionMassacre": "Vision When In Slaughter", - - "ShamanButtonText": "Voodoo", - "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", - "Shaman_KillerCannotMurderChosenTarget": "The killer cannot murder chosen target", - "VoodooCooldown": "Voodoo Cooldown", - - "AdminWarning": "Admin Table in use!", - "VitalsWarning": "Vitals in use!", - "DoorlogWarning": "Doorlogs in use!", - "CameraWarning": "Cameras in use!", - "MinWaitAutoStart": "Minutes to wait before auto-starting", - "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", - "PlayerAutoStart": "Minimum Player Threshold to auto-start", - "AutoStartTimer": "Initial countdown for auto-starting", - "ImmediateAutoStart": "Immediately start the game when reaching certain conditions", - "ImmediateStartTimer": "Initial countdown for Immediate-starting", - "StartWhenPlayersReach": "Immediately Start when we have enough players above", - "StartWhenTimerLowerThan": "Immediately Start when Lobby Timer goes below", - "AutoPlayAgainCountdown": "Delay before re-entering lobby", - "AutoPlayAgain": "Auto Play Again", - "AutoRehost": "Auto Re-Host on Bad Disconnect", - "CountdownText": "Rejoining lobby in {0}s", - "TimeMasterSkillDuration": "Time Shield Duration", - "TimeMasterSkillCooldown": "Time Shield Cooldown", - "TimeMasterOnGuard": "Time Shield is active!", - "TimeMasterSkillStop": "Time Shield has ended!", - "TimeMasterVentButtonText": "Time Shield", - "BodyCannotBeReported": "Body could not be reported", - "BurstKillDelay": "Burst Kill Delay", - "BurstNotify": "That was a Burst! Get in a vent or die.", - "ImpCanBeBurst": "Impostors can become Burst", - "CrewCanBeBurst": "Crewmates can become Burst", - "NeutralCanBeBurst": "Neutrals can become Burst", - "BurstFailed": "Burst failed to bomb you", - "ShroudButtonText": "Shroud", - "ShroudCooldown": "Shroud Cooldown", - "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", - "LudopathRandomKillCD": "Maximum kill cooldown", - "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", - "GodfatherTargetCountMode": "Killer turns into", - "GodfatherCount_Refugee": "Refugee", - "GodfatherCount_Madmate": "Madmate", - "MissChance": "Chance To Miss", - "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", - "HawkMissed": "Missed!", - "HawkCanKillNum": "Max Slices", - "HawkKillMax": "You've run out of ability uses", - "HawkKillTooManyDead": "Too many people are dead", - "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", - "BloodMoonCanKillNum": "Max BloodLettings", - "BloodMoonTimeTilDie": "Time Until Death", - "DeathTimer": "Death In: {DeathTimer}s", - "BerserkerKillCooldown": "Berserker kill cooldown", - "BerserkerMax": "Max level that Berserker can reach", - "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", - "WarHasImpostorVision": "War Has Impostor Vision", - "BerserkerCanVent": "Berserker Can Vent", - "WarCanVent": "War Can Vent", - "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", - "BerserkerOneKillCooldown": "Kill cooldown after unlocking", - "BerserkerTwoCanScavenger": "Unlock scavenged kills", - "BerserkerThreeCanBomber": "Unlock bombed kills", - "BerserkerFourCanNotKill": "Become War", - "BerserkerMaxReached": "Maximum level reached!", - "BerserkerLevelChanged": "Increased level to {0}", - "BerserkerLevelRequirement": "Level requirement for unlock", - "KilledByBerserker": "Killed by Berserker", - "BerserkerToWar": "You have become War!!!", - "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", - "WarKillCooldown": "War kill cooldown", - - "ImpCanBeUnlucky": "Impostors can become Unlucky", - "CrewCanBeUnlucky": "Crewmates can become Unlucky", - "NeutralCanBeUnlucky": "Neutrals can become Unlucky", - "BlackmailerSkillCooldown": "Blackmail Cooldown", - "BlackmailerMax": "Maximum times blackmailed players may speak", - "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", - "BlackmaileKillTitle": "BLACKMAILER", - "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", - "UnluckyKillSuicideChance": "Chance to suicide from killing", - "UnluckyVentSuicideChance": "Chance to suicide from venting", - "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", - "UnluckyOpenDoorSuicideChance": "Chance to suicide from opening a door", - "ImpCanBeVoidBallot": "Impostors can become VoidBallot", - "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", - "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", - "ImpCanBeAware": "Impostors can become Aware", - "NeutralCanBeAware": "Neutrals can become Aware", - "CrewCanBeAware": "Crewmates can become Aware", - "AwareKnowRole": "Knows the role of the player", - "AwareInteracted": "{0} tried to reveal your role.", - "AwareTitle": "AWARE MESSAGE", - "LighterVentButtonText": "Light", - "LighterSkillCooldown": "Light Cooldown", - "LighterSkillDuration": "Light Duration", - "LighterVisionNormal": "Increased Vision", - "LighterVisionOnLightsOut": "Increased Vision During Lights Out", - "LighterSkillInUse": "Ability in use", - "LighterSkillStop": "Ability expired", - "StealthDarkened": "Darkened: {0}", - "StealthExcludeImpostors": "Ignore Impostors when Blinding", - "StealthDarkenDuration": "Blinding Duration", - "PenguinAbductTimerLimit": "Dragging Time", - "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", - "PenguinKillButtonText": "Drag", - "PenguinTimerText": "Drag Timer", - "PenguinTargetOnCheckMurder": "You are grabbed. Try to escape that first!", - "WitnessTime": "Max Time after killing where killer appears red", - "WitnessButtonText": "Examine", - "WitnessFoundInnocent": "✓", - "WitnessFoundKiller": "⚠", - "SwapperMax": "Maximum swaps", - "CanSwapSelfVotes": "Can exchange your own votes.", - "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", - "CantSwapSelf": "Can't exchange of one's own vote", - "SwapVote": "The votes of {0} and {1} were swapped!", - "SwapDead": "Sorry, you can't swap votes after death.", - "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", - "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", - "Swap1": "Swap target 1 selected", - "Swap2": "Swap target 2 selected", - "CancelSwap": "Cleared your previous swap!", - "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your targets is dead.", - "Swap1=Swap2": "The target you input is the same as Swap target 1.\nPls input a different one", - "SwapTitle": "SWAPPER", - "SwapperTryHideMsg": "Try to hide Swapper's command", - "SwapperPreResult": "Currently, you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", - "ImpCanBeFragile": "Impostors can become Fragile", - "NeutralCanBeFragile": "Neutrals can become Fragile", - "CrewCanBeFragile": "Crewmates can become Fragile", - "ImpCanKillFragile": "Impostors can force kill Fragile", - "NeutralCanKillFragile": "Neutrals can force kill Fragile", - "CrewCanKillFragile": "Crewmates can force kill Fragile", - "FragileKillerLunge": "Killer lunges on kill", - "CrusaderSkillLimit": "Maximum Crusades", - "CrusaderSkillCooldown": "Crusade Cooldown", - "CrusaderKillButtonText": "Crusade", - "JailorKillButtonText": "Jail", - "AgitaterKillButtonText": "Pass", - "HasSerialKillerBuddy": "Has Serial Killer buddy", - "ChanceToSpawn": "Chance to spawn", - "ChanceToSpawnAnother": "Chance to spawn another", - "BloodthirstKillCD": "Bloodthirst Kill Cooldown", - "BloodthirstPlayerCount": "Max players alive for Bloodthirst", - "ReflectHarmfulInteractions": "Reflect harmful interactions", - - "ImpCanBeDiseased": "Impostors can become Diseased", - "NeutralCanBeDiseased": "Neutrals can become Diseased", - "CrewCanBeDiseased": "Crewmates can become Diseased", - "DiseasedCDOpt": "Increase the cooldown by", - "DiseasedCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeAntidote": "Impostors can become Antidote", - "NeutralCanBeAntidote": "Neutrals can become Antidote", - "CrewCanBeAntidote": "Crewmates can become Antidote", - "AntidoteCDOpt": "Decrease the cooldown by", - "AntidoteCDReset": "Cooldown returns to normal after a meeting", - - "ImpCanBeRadar": "Impostors can become Radar", - "NeutralCanBeRadar": "Neutrals can become Radar", - "CrewCanBeRadar": "Crewmates can become Radar", - - "ImpCanBeGlow": "Impostors can become Glow", - "NeutralCanBeGlow": "Neutrals can become Glow", - "CrewCanBeGlow": "Crewmates can become Glow", - "GlowRadius": "Glow Radius", - "GlowVisionOthers": "Vision Boost for nearby Players", - "GlowVisionSelf": "Vision Boost for Glow", - - "ImpCanBeStubborn": "Impostors can become Stubborn", - "NeutralCanBeStubborn": "Neutrals can become Stubborn", - "CrewCanBeStubborn": "Crewmates can become Stubborn", - - "ImpCanBeAvanger": "Impostors can become Avenger", - "NeutralCanBeAvanger": "Neutrals can become Avenger", - "CrewCanBeAvanger": "Crewmates can become Avenger", - "ImpCanBeSleuth": "Impostors can become Sleuth", - "CrewCanBeSleuth": "Crewmates can become Sleuth", - "NeutralCanBeSleuth": "Neutrals can become Sleuth", - "SleuthCanKnowKillerRole": "Can find the role of the killer", - "SleuthNoticeKiller": "\nThe killer's role is {0}.", - "SleuthNoticeVictim": "{0}'s role is {1}.", - "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", - "BomberDiesInExplosion": "Bomber dies in their explosion", - "ImpostorsSurviveBombs": "Impostors survive bombs", - - "PunchingBagKillMax": "Amount of attacks needed to win", - "GuessPunchingBag": "You just tried to guess a Punching Bag!\nThey're now one step closer to winning!", - "GuessPunchingBagAgain": "You just tried to guess a Punching Bag again!\n\nIt no longer counts your attacks by guessing", - "PunchingBagKill": "You were attacked!", - "SelfGuessPunchingBag": "You can't self-guess as a Punching Bag, you cheater!", - "GuessPunchingBagBlocked": "Punching Bag cannot guess due to self-guessing.", - "EradicatePunchingBag": "You just tried to terminate punching bag, that is not allowed.", - - "RememberCooldown": "Imitate Cooldown", - "RefugeeKillCD": "Refugee's Kill Cooldown", - "RememberedNeutralKiller": "You remembered you were a neutral killer!", - "RememberedMaverick": "You remembered you were a Maverick!", - "RememberedPursuer": "You remembered you were a Pursuer!", - "RememberedFollower": "You remembered you were a Follower!", - "RememberedAmnesiac": "You failed to remember your role.", - "RememberedImitator": "You remembered you were an Imitator.", - "RememberedImpostor": "You remembered you were an Impostor!", - "RememberedCrewmate": "You remembered you were a crewmate!", - "ImitatorImitated": "An Imitator imitated your role!", - "ImitatorInvalidTarget": "Imitation failed", - "RememberButtonText": "Remember", - "ImitatorKillButtonText": "Imitate", - "IncompatibleNeutralMode": "If neutral is incompatible, turn into", - "RememberedYourRole": "An Amnesiac remembered your role!", - "YouRememberedRole": "You remembered who you were!", - - "BanditStealMode": "Steal Mode", - "BanditStealMode_OnMeeting": "On Meeting", - "BanditStealMode_Instantly": "Instantly", - "BanditMaxSteals": "Maximum Steals", - "BanditCanStealBetrayalAddon": "Can Steal Betrayal Add-ons", - "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", - "Bandit_NoStealableAddons": "Could not steal add-on from the player", - "BanditStealCooldown": "Steal cooldown", - - "DoppelMaxSteals": "Maximum Steals", - "DoppelCurrentVictimCanSeeRolesAsDead": "Last victim can see role and add-on info of alive players as a ghost", - - "NecromancerRevengeTime": "Necromancy time", - "NecromancerRevenge": "You have {0}s to kill {1}", - "NecromancerSuccess": "Necromancy complete! You live to see another day.", - "NecromancerHide": "Venting is disabled, hide from the Necromancer!", - "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", - "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", - "RetributionistKillMax": "You've reached the maximum amount of kills. You can't kill anymore!", - "RetributionistKillDead": "Choose a living player to kill.", - "RetributionistKillSucceed": "{0} was killed by the Retributionist!", - "RetributionistKillDisable": "You can't retribute until your tasks are done.", - "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", - "RetributionistCanKillNum": "Max retributions", - "RetributionistKillTooManyDead": "Too many players are dead. You can't retribute.", - "MinimumPlayersAliveToRetri": "Minimum players alive to retribute", - "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", - "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", - - "TwisterCooldown": "Twist Cooldown", - "TwisterButtonText": "Twist", - "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", - "InstigatorAbilityLimit": "Ability Use Count", - "InstigatorKillsPerAbilityUse": "Kills per Ability use", - - "CrewCanFindCaptain": "Crewmates can find Captain", - "MadmateCanFindCaptain": "Madmates can find Captain", - "ReducedSpeed": "Reduced speed", - "ReducedSpeedTime": "Time duration for reduced speed", - "CaptainCanTargetNB": "Captain can target Neutral Benign", - "CaptainCanTargetNE": "Captain can target Neutral Evil", - "CaptainCanTargetNC": "Captain can target Neutral Chaos", - "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", - "CaptainCanTargetNK": "Captain can target Neutral Killer", - "CaptainSpeedReduced": "Captain reduced your speed", - "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", - "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", - - "InspectorTryHideMsg": "Hide Inspector's commands", - "MaxInspectCheckLimit": "Max inspections per game", - "InspectCheckLimitPerMeeting": "Max inspections per meeting", - "InspectCheckTargetKnow": "Targets know they were checked by Inspector", - "InspectCheckOtherTargetKnow": "Targets know who they were checked with", - "InspectorDead": "You can not use your power after death", - "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", - "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", - "InspectCheckSelf": "HA!! You thought it would be this easy. You can not check yourself", - "InspectCheckReveal": "HA! You thought it would be this easy. You can not check a role that is revealed", - "InspectCheckTitle": "INSPECTOR ", - "InspectCheckTrue": "{0} and {1} are in the same team!", - "InspectCheckFalse": "{0} and {1} are NOT in the same team!", - "InspectCheckTargetMsg": " were checked by Inspector.", - "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", - "InspectCheckNull": "Please select an ID of a living player to check their team", - "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", - "InspectCheckRevealTarget": "When tasks are done, the target knows the team of the other target", - "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", - - "EgoistCountMode.Original": "Original", - "EgoistCountMode.Neutral": "Neutral", - - "JailerJailCooldown": "Jail cooldown", - "JailerMaxExecution": "Maximum executions", - "JailerNBCanBeExe": "Can execute Neutral Benign", - "JailerNCCanBeExe": "Can execute Neutral Chaos", - "JailerNECanBeExe": "Can execute Neutral Evil", - "JailerNKCanBeExe": "Can execute Neutral Killing", - "JailerNACanBeExe": "Can execute Neutral Apocalypse", - "JailerCKCanBeExe": "Can execute Crew Killing", - "JailerTargetAlreadySelected": "You have already selected a target", - "SuccessfullyJailed": "Target successfully jailed", - "CantGuessJailed": "You can not guess the target", - "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", - "CanNotTrialJailed": "You can not trial the target.", - "notifyJailedOnMeeting": "Notify jailed player when a meeting starts", - "JailedNotifyMsg": "The Jailer has jailed you. No one can guess or judge you. You can only guess The Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", - "JailerTitle": "Jailer", - - "CopyCatCopyCooldown": "Copy cooldown", - "CopyCatRoleChange": "Your role has been changed to {0}", - "CopyCatCanNotCopy": "You can not copy the target's role", - "CopyButtonText": "Copy", - "CopyCrewVar": "Can copy evil variants of crew roles", - "CopyTeamChangingAddon": "Can copy team changing add-on", - - "MaxCleanserUses": "Max cleanses", - "CleansedCanGetAddon": "Cleansed player can get Add-on", - "CleanserTitle": "CLEANSER", - "CleanserRemoveSelf": "You can not cleanse yourself", - "CleanserCantRemove": "Oops! the player can not be cleansed.", - "CleanserRemovedRole": "{0} has been cleansed. All their Add-ons will be removed after the meeting.", - "LostAddonByCleanser": "The cleanser removed all your Add-ons", - - "MaxProtections": "Max protections", - "KeeperHideVote": "Hide Keeper's vote", - "KeeperProtect": "You chose to protect {0}, your vote has been returned", - "KeeperTitle": "Keeper", - - "MaulRadius": "Maul Radius", - "ImpCanBeAutopsy": "Impostors can become Autopsy", - "CrewCanBeAutopsy": "Crewmates can become Autopsy", - "NeutralCanBeAutopsy": "Neutrals can become Autopsy", - "ImpCanBeCyber": "Impostors can become Cyber", - "CrewCanBeCyber": "Crewmates can become Cyber", - "NeutralCanBeCyber": "Neutrals can become Cyber", - "ImpKnowCyberDead": "Impostors know if Cyber died", - "CrewKnowCyberDead": "Crewmates know if Cyber died", - "NeutralKnowCyberDead": "Neutrals know if Cyber died", - "CyberKnown": "Everyone can see Cyber", - "ImpCanBeInfluenced": "Impostors can become Influenced", - "CrewCanBeInfluenced": "Crewmates can become Influenced", - "NeutralCanBeInfluenced": "Neutrals can become Influenced", - "ImpCanBeBewilder": "Impostors can become Bewilder", - "CrewCanBeBewilder": "Crewmates can become Bewilder", - "NeutralCanBeBewilder": "Neutrals can become Bewilder", - "KillerGetBewilderVision": "Killer gets Bewilder's vision", - "ImpCanBeOiiai": "Impostors can be OIIAI", - "CrewCanBeOiiai": "Crewmates can be OIIAI", - "NeutralCanBeOiiai": "Neutrals can be OIIAI", - "OiiaiCanPassOn": "OIIAI can pass on to the killer", - "NeutralChangeRolesForOiiai": "Neutrals turns to ", - "LostRoleByOiiai": "You got erased by OIIAI!", - "ImpCanBeLoyal": "Impostors can become Loyal", - "CrewCanBeLoyal": "Crewmates can become Loyal", - "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", - "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", - "SheriffCanBeMadmate": "Sheriff can become Madmate", - "MayorCanBeMadmate": "Mayor can become Madmate", - "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", - "SnitchCanBeMadmate": "Snitch can become Madmate", - "JudgeCanBeMadmate": "Judge can become Madmate", - "MarshallCanBeMadmate": "Marshall can become Madmate", - "GanRetributionistCanBeMadmate": "Retributionist can be converted", - "RetributionistCanBeMadmate": "Retributionist can become Madmate", - "OverseerCanBeMadmate": "Overseer can become Madmate", - "GanSheriffCanBeMadmate": "Sheriff can be converted", - "GanMayorCanBeMadmate": "Mayor can be converted", - "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", - "GanJudgeCanBeMadmate": "Judge can be converted", - "GanMarshallCanBeMadmate": "Marshall can be converted", - "GanOverseerCanBeMadmate": "Overseer can be converted", - "RascalAppearAsMadmate": "Appear As Madmate On Ejection", - - "CouncillorDead": "Sorry, you can't murder from the dead.", - "CouncillorMurderMaxMeeting": "Sorry, you've reached the maximum amount of murders for the meeting.", - "CouncillorMurderMaxGame": "Sorry, you've reached the maximum amount of murders for the game.", - "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", - "Councillor_MurderKill": "{0} was murdered.", - "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", - "Councillor_MurderNull": "Please choose a living player to murder.", - "Councillor_MurderKillTitle": "WICKED COURT ", - "CouncillorMakeEvilJudgeClear": "Show Trial as Councillor Murder", - "Councillor_CannotMurderImpTeam": "Sorry, you can not murder your teammate.", - "Councillor_SuicideForMurderImps": "You died because you are trying to murder your team members.", - "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", - "CouncillorMurderLimitPerGame": "Maximum Kills Per Game", - "CouncillorCanMurderMadmate": "Can Murder Madmates", - "CouncillorCanMurderImpostor": "Can Murder Impostors", - "CouncillorSuicideOnJudgeImpTeam": "Suicide when judge Impostors Team Wrongly", - "CouncillorCanMurderTaskDoneSnitch": "Can Murder Snitch with All Tasks Done", - "CouncillorTryHideMsg": "Try to hide Councillor's commands", - - "DazzlerDazzled": "You were dazzled by the Dazzler!", - "DazzlerCauseVision": "Reduced vision", - "DazzlerDazzleLimit": "Max number of players affected by reduced vision", - "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", - "DazzleCooldown": "Dazzle Cooldown", - "DazzleButtonText": "Dazzle", - - "MoleVentButtonText": "Dig", - "MoleVentCooldown": "Dig cooldown", - - "AddictVentButtonText": "Get Fix", - "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", - "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerable", - - "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", - "AlchemistShieldDur": "Resistance Potion Duration", - "AlchemistInvisDur": "Invisibility Potion Duration", - "AlchemistVision": "Night Vision", - "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", - "AlchemistVisionDur": "Night Vision Potion Duration", - "AlchemistSpeed": "Speed Potion Boost", - "AlchemistVentButtonText": "Drink", - "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", - "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", - "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", - "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", - "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", - "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", - "AlchemistGotBloodthirstPotion": "Potion of Harming: Kill the next player you touch", - "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", - "NoPotion": "You have no potions", - - "StoreShield": "Potion of Resistance", - "StoreSuicide": "Potion of Poison", - "StoreTP": "Potion of Warping", - "StoreSP": "Potion Of Speed", - "StoreQF": "Potion of Fixing", - "StoreBL": "Potion of Harming", - "StoreNS": "Potion of Night Vision", - "StoreINV": "Potion of Invisibility", - "StoreNull": "None", - "PotionStore": "Potion in store: ", - "WaitQFPotion": "\nPotion of Fixing waiting for use", - - "AlchemistShielded": "Potion of Resistance started", - "AlchemistHasVision": "Potion of Night Vision started", - "AlchemistShieldOut": "Potion of Resistance ended", - "AlchemistVisionOut": "Potion of Night Vision ended", - "AlchemistPotionBloodthirst": "You gained bloodthirst", - "AlchemistHasSpeed": "Potion Of Speed started", - "AlchemistSpeedOut": "Potion Of Speed ended", - - "DeathpactDuration": "Death Pact duration", - "DeathPactCooldown": "Death Pact Assign Cooldown", - "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", - "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", - "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", - "DeathpactVisionWhileInPact": "Vision for players in Death Pact", - "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", - "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", - "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", - "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", - "DeathpactComplete": "Death Pact was concluded.", - "DeathpactExecuted": "Death Pact was executed.", - "DeathpactAverted": "Death Pact was averted.", - "DeathpactButtonText": "Assign", - "DevourerHideNameConsumed": "Hide the names of consumed players", - "DevourCooldown": "Devour Cooldown", - "DevourerButtonText": "Devour", - "DollMasterPossessionButtonText": "Possess", - "DollMasterUnPossessionButtonText": "UnPossess", - "DollMaster_PossessedTarget": "Possessed target", - "DollMaster_CannotPossessImpTeammate": "Unable to possess teammate", - "DollMaster_CouldNotSwapWithTarget": "Unable to possess player", - "DollMaster_CanNotSwapWithDeadTarget": "Possesing a dead player isn't possible", - "DollMaster_MainBody": "Main Body", - "DollMaster_Doll": "Doll", - "DollMaster_UnableToUseAbility": "Unable to use your ability on player", - "Doppelganger_RoleInfo": "Spoofed Role: {0}", - "EatenByDevourer": "The Devourer ate your skin", - "DevourerEatenSkin": "Target skin is eaten", - "DevouredName": "Devoured", - "PitfallTrapCooldown": "Trap Cooldown", - "PitfallMaxTrapCount": "Number of Traps that can be set", - "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", - "PitfallTrapDuration": "Time the Trap remains active", - "PitfallTrapRadius": "Trap Radius", - "PitfallTrapFreezeTime": "Trap freeze time", - "PitfallTrapCauseVision": "Trap caused vision", - "PitfallTrapCauseVisionTime": "Trap caused vision time", - "PitfallTrap": "You have fallen into a trap!", - "ConsigliereDivinationMaxCount": "Maximum Reveals", - "RitualMaxCount": "Maximum Reveals", - "CleanserHideVote": "Hide Cleanser's vote", - "OracleSkillLimit": "Maximum Uses", - "OracleHideVote": "Hide Oracle's vote", - "OracleCheckReachLimit": "You're out of uses!", - "OracleCheckSelfMsg": "You can't even trust yourself, huh?", - "OracleCheckLimit": "Reminder: You have {0} uses left", - "OracleCheckMsgTitle": "ORACLE ", - "OracleCheck.NotCrewmate": "Appears not to be a crewmate", - "OracleCheck.Crewmate": "Appears to be a crewmate", - "OracleCheck.Neutral": "Appears to be a neutral", - "OracleCheck.Impostor": "Appears to be an Impostor", - "OracleCheck": "Target Results:", - "FailChance": "Chance of showing incorrect result", - "OracleCheckAddons": "Oracle checks add-ons", - "ChameleonCanVent": "Vent to disguise", - "ChameleonInvisState": "You are disguising!", - "ChameleonInvisStateOut": "Your disguise ended", - "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", - "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", - "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", - "ChameleonCooldown": "Disguise Cooldown", - "ChameleonDuration": "Disguise Duration", - "ChameleonRevertDisguise": "Expose", - "ChameleonDisguise": "Disguise", - "KillCooldownAfterCleaning": "Kill Cooldown On Clean", - "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", - "MedusaStoneBody": "Body stoned", - "MedusaReportButtonText": "Stone", - - "CursedSoulCurseCooldown": "Soul Snatch Cooldown", - "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", - "CursedSoulCurseMax": "Maximum Soul Snatches", - "CursedSoulKnowTargetRole": "Know the roles of Soulless players", - "CursedSoulCanCurseNeutral": "Neutral roles have souls", - "CursedSoulKillButtonText": "Snatch", - "SoullessByCursedSoul": "A Cursed Soul snatched your soul", - "CursedSoulSoullessPlayer": "Soul snatched", - "CursedSoulInvalidTarget": "No soul found", - - "AdmireCooldown": "Admire Cooldown", - "AdmirerKnowTargetRole": "Know the roles of Admired players", - "AdmirerSkillLimit": "Skill Limit", - "AdmireButtonText": "Admire", - "AdmirerAdmired": "The Admirer admired you!", - "AdmiredPlayer": "Player admired", - "AdmirerInvalidTarget": "Target cannot be admired", - - "SpiritualistNoticeTitle": "SPIRITUALIST ", - "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", - "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", - "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", - "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", - "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", - "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", - "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", - "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", - "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", - "EnigmaClueHat1": "The Killer wears a Hat!", - "EnigmaClueHat2": "The Killer does not wear a Hat!", - "EnigmaClueHat3": "The Killer wears {0} as a Hat!", - "EnigmaClueSkin1": "The Killer wears a Skin!", - "EnigmaClueSkin2": "The Killer does not wear a Skin!", - "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", - "EnigmaClueVisor1": "The Killer wears a Visor!", - "EnigmaClueVisor2": "The Killer does not wear a Visor!", - "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", - "EnigmaCluePet1": "The Killer does have a Pet!", - "EnigmaCluePet2": "The Killer does not have a Pet!", - "EnigmaCluePet3": "The Killer has {0} as a Pet!", - "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", - "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", - "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", - "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", - "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", - "EnigmaClueColor1": "The Killer has a light color!", - "EnigmaClueColor2": "The Killer has a dark color!", - "EnigmaClueColor3": "The Killer's color is {0}!", - "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", - "EnigmaClueStatus1": "The Killer is currently inside a Vent!", - "EnigmaClueStatus2": "The Killer is currently on a Ladder!", - "EnigmaClueStatus3": "The Killer is already Dead!", - "EnigmaClueStatus4": "The Killer is still Alive!", - "EnigmaClueRole1": "The Killer is an Impostor!", - "EnigmaClueRole2": "The Killer is a Neutral!", - "EnigmaClueRole3": "The Killer is a Crewmate!", - "EnigmaClueRole4": "The Killer's Role is {0}!", - "EnigmaClueLevel1": "The Killer's Level is above 50!", - "EnigmaClueLevel2": "The Killer's Level is below 50!", - "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", - "EnigmaClueLevel4": "The Killer's Level is {0}!", - "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", - "EnigmaClueHatTitle": "Enigma Hat Clue!", - "EnigmaClueVisorTitle": "Enigma Visor Clue!", - "EnigmaClueSkinTitle": "Enigma Skin Clue!", - "EnigmaCluePetTitle": "Enigma Pet Clue!", - "EnigmaClueNameTitle": "Enigma Name Clue!", - "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", - "EnigmaClueColorTitle": "Enigma Color Clue!", - "EnigmaClueLocationTitle": "Enigma Location Clue!", - "EnigmaClueStatusTitle": "Enigma Status Clue!", - "EnigmaClueRoleTitle": "Enigma Role Clue!", - "EnigmaClueLevelTitle": "Enigma Level Clue!", - "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", - - "VotesPerKill": "Votes gained for each kill", - "PickpocketGetVote": "You've got {0} votes", - "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", - "VultureNumberOfReportsToWin": "Bodies needed to win", - "VultureReportBody": "Body eaten!", - "VultureEatButtonText": "Consume", - "VultureReportCooldown": "Eat Cooldown", - "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", - "VultureCooldownUp": "Eat Cooldown finished", - - "GhastlyPossessCD": "Possess Cooldown", - "GhastlyMaxPossessions": "Max Possessions", - "GhastlyPossessionDuration": "Possession Duration", - "GhastlySpeed": "Ghastly Speed", - "GhastlyKillAllies": "Ghastly cannot possess allies", - "GhastlyCannotPossessTarget": "Couldn't Possess Target", - "GhastlyChooseTarget": "Now: Choose Target", - "GhastlyNoMorePossess": "You've run out of possessions!'", - "GhastlyNotUrTarget": "That is not your target", - "GhastlyYouvePosses": "You've Been Possessed!", - "GhastlyPossessedUser": "You have possessed: {0}", - "GhastlyExpired": "{0} is no longer possessed", - - "TasksMarkPerRound": "Number of tasks that can be marked in one round", - "TaskinatorBombPlanted": "Bomb has been planted", - - "ShieldDuration": "Shield duration", - "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", - "BenefactorTaskMarked": "Task marked successfully", - "BenefactorTargetGotShield": "You got a shield by Benefactor", - - "PirateTryHideMsg": "Hide Pirate's commands", - "SuccessfulDuelsToWin": "Number of successful duels needed to win", - "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the Duel if you choose the same option as the target", - "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", - "PirateTitle": "PIRATE ", - "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", - "PirateDead": "Ye be dead. Ye cannot duel anymore.", - "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", - "DuelDone": "Ye 'ave chosen yer option fer the Duel.\nWait fer the meetin' to end to see the result.", - "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", - "PirateDuelButtonText": "Duel", - "DuelCooldown": "Duel Cooldown", - "Rock": "Rock", - "Paper": "Paper", - "Scissors": "Scissors", - "Heads": "Heads", - "Tails": "Tails", - "SpyRedNameDur": "Colored Name Duration", - "SpyInteractionBlocked": "Block kill button interaction", - "AgitaterBombCooldown": "Agitator bomb cooldown", - "AgitaterPassCooldown": "Bomb pass cooldown", - "BombExplodeCooldown": "Bomb explode cooldown", - "AgitaterPassNotify": "Bomb successfully passed", - "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", - "AgitaterCanGetBombed": "Agitator can get bomb", - "AgitaterAutoReportBait": "Agitator Auto Report Bait", - - "SeekerPointsToWin": "Number of points required to win", - "SeekerTagCooldown": "Tag Cooldown", - "SeekerNotify": "Your target is {0}", - "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", - "SeekerKillButtonText": "Tag", - - "PixiePointsToWin": "Number of points required to win", - "MaxTargets": "Maximum number of targets per round", - "MarkCooldown": "Mark cooldown", - "PixieSuicide": "Pixie suicides if the target is not voted out", - "PixieMaxTargetReached": "You have already selected all the targets this round", - "PixieTargetAlreadySelected": "Target is already selected", - "PixieButtonText": "Mark", - - "PlagueBearerCooldown": "Plague cooldown", - "PestilenceCooldown": "Pestilence Kill cooldown", - "PestilenceCanVent": "Pestilence Can Vent", - "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", - "PlagueBearerAlreadyPlagued": "Player has already been plagued", - "PlagueBearerToPestilence": "You have turned into Pestilence!!", - "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", - "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", - "RomanticBetCooldown": "Pick Partner Cooldown", - "RomanticProtectCooldown": "Protect Cooldown", - "RomanticBetPlayer": "You picked your partner", - "RomanticBetOnYou": "The Romantic chose you as their Partner!", - "VengefulKCD": "Vengeful Romantic Kill Cooldown", - "VengefulCanVent": "Vengeful Romantic Can Vent", - "RuthlessKCD": "Ruthless Romantic Kill Cooldown", - "RuthlessCanVent": "Ruthless Romantic Can Vent", - "RomanticProtectPartner": "Your partner is under protection", - "RomanticIsProtectingYou": "The Romantic is protecting you", - "ProtectingOver": "Shield expired", - "RomanticProtectDuration": "Protect Duration", - "RomanticKnowTargetRole": "Romantic knows their target's role", - "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", - "RomanticPartnerButtonText": "Pick Partner", - "RomanticProtectButtonText": "Protect", - - "GuessMasterMisguess": "{0} misguessed", - "GuessMasterTargetRole": "Someone tried to guess {0}", - "GuessMasterTitle": "Guess Master ", - - "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", - "DCanGuessImpostors": "Can Guess Impostors", - "DCanGuessCrewmates": "Can Guess Crewmates", - "DCanGuessNeutrals": "Can Guess Neutrals", - "DCanGuessAdt": "Can Guess Add-Ons", - "DoomsayerAdvancedSettings": "Advanced Settings", - "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", - "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", - "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", - "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", - "DoomsayerTryHideMsg": "Hide Doomsayer's commands", - "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", - "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", - "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", - "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", - "DoomsayerGuessCountTitle": "DOOMSAYER", - "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", - - "EveryoneCanKnowMini": "Everyone can see the Mini", - "CanBeEvil": "Mini can be an Impostor", - "EvilMiniSpawnChances": "Probability of Mini being an Impostor", - "GuessMini": "Sorry, you can't hurt a kid Mini.", - "GrowUpDuration": "Time required to grow (s)", - "MajorCooldown": "Kill Cooldown when over 18", - "UpDateAge": "Display age change in real-time", - "Cantkillkid": "You can't kill a Mini that hasn't grown up.", - "CantEat": "You can't eat a Mini that hasn't grown up", - "CantShroud": "You can't control a Mini that hasn't grown up.", - "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", - "CantRecruit": "You can't recruit a Mini that hasn't grown up.", - "CantDuel": "You can't duel a Mini that hasn't grown up.", - "CantMark": "You can't mark a Mini that hasn't grown up.", - "CantBlood": "You can't blood a Mini that hasn't grown up.", - "CantPosses": "You can't possess a Mini that hasn't grown up.", - "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", - "MiniUp": "You're a year older!", - "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilt while you can no longer guess.\nYou can guess again after you have grown up.", - "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", - "CountMeetingTime": "Meeting time can continue to grow", - "YouKillRandomizer1": "You kill Randomizer, Self-report!", - "YouKillRandomizer2": "You kill Randomizer, Cannot move!", - "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", - "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", - "MadmateCanBeHurried": "Madmate can be Hurried on game start", - "TaskBasedCrewCanBeHurried": "Task-based Crews can be Hurried", - "HurriedCanBeConverted": "Hurried can be recruited in the game (excludes madmate)", - "Developer": "Developer", - "Sponsor": "Sponsor", - "Booster": "Server Booster", - "Translator": "Translator", - "NoAccess": "Unauthorized Access!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", - "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", - "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", - "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", - "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", - "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", - "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", - "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", - "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", - "DCNotify.Inactivity": "The lobby closed due to inactivity.", - "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", - "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", - "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", - "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", - "RoleType.VanillaRoles": "★ Vanilla Roles", - "RoleType.ImpKilling": "★ Impostor Killing Roles", - "RoleType.ImpSupport": "★ Impostor Support Roles", - "RoleType.ImpConcealing": "★ Impostor Concealing Roles", - "RoleType.ImpHindering": "★ Impostor Hindering Roles", - "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", - "RoleType.Madmate": "★ Madmate Roles", - "RoleType.CrewSupport": "★ Crewmate Support Roles", - "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", - "RoleType.CrewPower": "★ Crewmate Power Roles", - "RoleType.CrewKilling": "★ Crewmate Killing Roles", - "RoleType.CrewBasic": "★ Crewmate Basic Roles", - "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", - "RoleType.NeutralEvil": "★ Neutral Evil Roles", - "RoleType.NeutralBenign": "★ Neutral Benign Roles", - "RoleType.NeutralChaos": "★ Neutral Chaos Roles", - "RoleType.NeutralKilling": "★ Neutral Killing Roles", - "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", - "RoleType.Harmful": "★ Harmful Add-ons", - "RoleType.Support": "★ Supportive Add-ons", - "RoleType.Helpful": "★ Helpful Add-ons", - "RoleType.Mixed": "★ Mixed Add-ons", - "RoleType.Misc": "★ Miscellaneous Add-ons", - "RoleType.Impostor": "★ Impostor Add-ons", - "RoleType.Neut": "★ Neutral Add-ons", - "SubType.Impostor": "★ Impostors", - "SubType.Shapeshifter": "★ Shapeshifters", - "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", - "SubType.Madmate": "★ Madmates", - "SubType.CrewmateKilling": "★ Crewmate Killings", - "SubType.Crewmate": "★ Regular Crewmates", - "SubType.New": "★ New!", - "CrewmateRoles": "★ Crewmate Roles ★", - "ImpostorRoles": "★ Impostor Roles ★", - "NeutralRoles": "★ Neutral Roles ★", - "AddonRoles": "★ Add-ons ★", - "WinnerRoleText.Impostor": "Impostors Win!", - "WinnerRoleText.Crewmate": "Crewmates Win!", - "WinnerRoleText.Apocalypse": "Apocalypse Wins!", - "WinnerRoleText.Terrorist": "Terrorist Wins!", - "WinnerRoleText.Jester": "Jester Wins!", - "WinnerRoleText.Lovers": "Lovers Win!", - "WinnerRoleText.Executioner": "Executioner Wins!", - "WinnerRoleText.Arsonist": "Arsonist Wins!", - "WinnerRoleText.Revolutionist": "Revolutionist Wins!", - "WinnerRoleText.Jackal": "Jackals Win!", - "WinnerRoleText.God": "God Wins!", - "WinnerRoleText.Vector": "Vector Wins!", - "WinnerRoleText.Innocent": "Innocent Wins!", - "WinnerRoleText.Pelican": "Pelican Wins!", - "WinnerRoleText.Youtuber": "YouTuber Wins!", - "WinnerRoleText.Necromancer": "Necromancer Wins!", - "WinnerRoleText.Egoist": "Egoists Win!", - "WinnerRoleText.Demon": "Demon Wins!", - "WinnerRoleText.Stalker": "Stalker Wins!", - "WinnerRoleText.Workaholic": "Workaholic Wins!", - "WinnerRoleText.Collector": "Collector Wins!", - "WinnerRoleText.BloodKnight": "Blood Knight Wins!", - "WinnerRoleText.Poisoner": "Poisoner Wins!", - "WinnerRoleText.Huntsman": "Huntsman Wins!", - "WinnerRoleText.HexMaster": "Hex Master Wins!", - "WinnerRoleText.Cultist": "Cultist Wins!", - "WinnerRoleText.Wraith": "Wraith Wins!", - "WinnerRoleText.SerialKiller": "Serial Killers Win!", - "WinnerRoleText.Juggernaut": "Juggernaut Wins!", - "WinnerRoleText.Infectious": "Infectious Wins!", - "WinnerRoleText.Virus": "Virus Wins!", - "WinnerRoleText.Specter": "Specter Wins!", - "WinnerRoleText.Jinx": "Jinx Wins!", - "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", - "WinnerRoleText.PotionMaster": "Potion Master Wins!", - "WinnerRoleText.Pickpocket": "Pickpocket Wins!", - "WinnerRoleText.Traitor": "Traitor Wins!", - "WinnerRoleText.Vulture": "Vulture Wins!", - "WinnerRoleText.Medusa": "Medusa Wins!", - "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", - "WinnerRoleText.Glitch": "Glitch Wins!", - "WinnerRoleText.Pestilence": "Pestilence Wins!", - "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", - "WinnerRoleText.PunchingBag": "Punching Bag Wins!", - "WinnerRoleText.Doomsayer": "Doomsayer Wins!", - "WinnerRoleText.Pirate": "Pirate Wins!", - "WinnerRoleText.Shroud": "Shroud Wins!", - "WinnerRoleText.Werewolf": "Werewolf Wins!", - "WinnerRoleText.Seeker": "Seeker Wins!", - "WinnerRoleText.Occultist": "Occultist Wins!", - "WinnerRoleText.SoulCollector": "Soul Collector Wins!", - "WinnerRoleText.NiceMini": "Nice Mini Wins!", - "WinnerRoleText.Mini": "Nice Mini was killed", - "WinnerRoleText.Bandit": "Bandit Wins!", - "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", - "WinnerRoleText.Solsticer": "Solsticer Wins!", - "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", - "WinnerRoleText.Doppelganger": "Doppelganger Wins!", - "WinnerRoleText.Quizmaster": "Quizmaster Wins!", - "WinnerRoleText.Agitater": "Agitator Wins!", - "AdditionalWinnerRoleText.Sidekick": "Sidekick", - "AdditionalWinnerRoleText.Taskinator": "Taskinator", - "AdditionalWinnerRoleText.Opportunist": "Opportunist", - "AdditionalWinnerRoleText.Lawyer": "Lawyer", - "AdditionalWinnerRoleText.Hater": "Hater", - "AdditionalWinnerRoleText.Provocateur": "Provocateur", - "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", - "AdditionalWinnerRoleText.Follower": "Follower", - "AdditionalWinnerRoleText.Pursuer": "Pursuer", - "AdditionalWinnerRoleText.Jester": "Jester", - "AdditionalWinnerRoleText.Lovers": "Lovers", - "AdditionalWinnerRoleText.Executioner": "Executioner", - "AdditionalWinnerRoleText.Specter": "Specter", - "AdditionalWinnerRoleText.Maverick": "Maverick", - "AdditionalWinnerRoleText.Shaman": "Shaman", - "AdditionalWinnerRoleText.Pixie": "Pixie", - "AdditionalWinnerRoleText.NiceMini": "Nice Mini", - "AdditionalWinnerRoleText.Romantic": "Romantic", - "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", - "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", - "ErrorEndText": "An error occurred", - "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", - "ForceEnd": "Aborted", - "EveryoneDied": "Everyone died", - "ForceEndText": "Host has aborted the game", - "NiceMiniDied": "Nice Mini was killed", - "HaterMisFireKillTarget": "Hater kills target when misfiring", - "HaterChooseConverted": "Select add-ons that Hater can kill", - "HaterCanKillMadmate": "Can kill madmate", - "HaterCanKillCharmed": "Can kill charmed", - "HaterCanKillLovers": "Can kill lovers", - "HaterCanKillSidekick": "Can kill jackal team", - "HaterCanKillEgoist": "Can kill egoist", - "HaterCanKillInfected": "Can kill infected team", - "HaterCanKillContagious": "Can kill virus team", - "HaterCanKillAdmired": "Can kill admirer", - "HorseMode": "Enable to become a horse", - "LongMode": "Enable to have a long neck", - "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", - - - "FFA": "Free For All", - "ModeFFA": "Gamemode: FFA", - "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", - "KillerInfoLong": "In the FFA (Free For All) game mode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", - "FFA_GameTime": "Maximum Game Length", - "FFA_KCD": "Kill Cooldown", - "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", - "FFA_EnableRandomAbilities": "Enable Random Events", - "FFA_ShieldDuration": "Shield Duration", - "FFA_IncreasedSpeed": "Increased Speed", - "FFA_DecreasedSpeed": "Decreased Speed", - "FFA_ModifiedSpeedDuration": "Modified Speed Duration", - "FFA_LowerVision": "Lowered Vision", - "FFA_ModifiedVisionDuration": "Lowered Vision Duration", - "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", - "FFA-Event-GetShield": "You have a temporary shield!", - "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", - "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", - "FFA-Event-GetHighKCD": "You got a higher kill cooldown", - "FFA-Event-GetLowVision": "You have lower vision temporarily", - "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", - "FFA-Event-GetTP": "You got teleported to a random vent!", - "FFA-Event-RandomTP": "Everyone was swapped with someone", - "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", - "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", - "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", - "FFA_TargetIsShielded": "The player you tried to kill is shielded!", - "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", - "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", - "Killer": "FREE FOR ALL", - "KillerInfo": "Kill Everyone to Win", - - "Hide&SeekTOHE": "Hide & Seek", - "MenuTitle.Hide&Seek": "Hide & Seek Settings", - "NumImpostorsHnS": "Num Impostors", - - "EveryOneKnowSolsticer": "Every One Know who is Solsticer", - "SolsticerKnowItsKiller": "Solsticer knows the role of whom used the kill button on it", - "SolsticerSpeed": "Movement speed of Solsticer", - "SolsticerRemainingTaskWarned": "Remaining tasks to be known", - "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", - "SolsticerMurdered": "{0} attempted to murder you!", - "MurderSolsticer": "You stopped Solsticer this round!", - "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", - "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", - "SolsticerTitle": "Solsticer", - "GuessSolsticer": "Sorry, but you can not guess Solsticer!", - "VoteSolsticer": "Sorry, but you can not vote Solsticer!", - "SolsticerTasksReset": "Your tasks get reset!", - "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", - "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", - - "VoteDead": "The player you voted for was exiled before the meeting concluded. Your vote was rescinded.", - - "ImpCanBeSilent": "Impostors can become Silent", - "CrewCanBeSilent": "Crewmates can become Silent", - "NeutralCanBeSilent": "Neutrals can become Silent", - "LastMessageReplay": "Last System Message Replay", - "Contributor": "Contributor", - - "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", - "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", - - "ImpCanBeSusceptible": "Impostors can become Susceptible", - "CrewCanBeSusceptible": "Crewmates can become Susceptible", - "NeutralCanBeSusceptible": "Neutrals can become Susceptible", - - "Quizmaster": "Quizmaster", - "QuizmasterInfo": "Quiz people to kill them in meetings", - "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will have \"?!\" next to their name. The player will die if they answer the question wrong or doesn't answer. The player will live if the Quizmaster is killed/ejected in the same meeting.\nThe Quizmaster cannot mark multiple people in the same round", - "QuizmasterKillButtonText": "Quiz", - - "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", - "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", - "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", - "QuizmasterChat.CorrectTarget": "Correct", - "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", - "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", - "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", - "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", - "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", - "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die\n\nQuestion for {QMTARGET} => {QMQUESTION}", - "QuizmasterChat.Title": "Quizmaster Information", - "QuizmasterChat.CantAnswer": "As the quizmaster, you can't answer questions", - "QuizmasterChat.AnswerNotValid": "Your answer must be A, B, or C", - "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", - - "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", - "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", - "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", - "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", - "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", - - "Quizmaster.None": "None", - - "QuizmasterSabotages.Lights": "Lights", - "QuizmasterSabotages.Reactor": "Reactor", - "QuizmasterSabotages.Communications": "Communications", - "QuizmasterSabotages.O2": "O2", - "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", - "QuizmasterAnswers.One": "One", - "QuizmasterAnswers.Two": "Two", - "QuizmasterAnswers.Three": "Three", - "QuizmasterAnswers.Four": "Four", - "QuizmasterAnswers.Five": "Five", - "QuizmasterAnswers.Pacifist": "Pacifist", - "QuizmasterAnswers.Vampire": "Vampire", - "QuizmasterAnswers.Snitch": "Snitch", - "QuizmasterAnswers.Vigilante": "Vigilante", - "QuizmasterAnswers.Jackal": "Jackal", - "QuizmasterAnswers.Mole": "Mole", - "QuizmasterAnswers.Sniper": "Sniper", - "QuizmasterAnswers.Coven": "Coven", - "QuizmasterAnswers.Sabotuer": "Saboteur", - "QuizmasterAnswers.Sorcerers": "Sorcerers", - "QuizmasterAnswers.Killer": "Killer", - "QuizmasterAnswers.Edition": "Edition", - "QuizmasterAnswers.Experimental": "Experimental", - "QuizmasterAnswers.Enhanced": "Enhanced", - "QuizmasterAnswers.Edited": "Edited", - - "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", - "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", - "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", - "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", - "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called the last meeting before this meeting?", - "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", - "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", - "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", - "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", - "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed in an update later?", - "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", - "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", - "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", - "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", - "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", - "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", - "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", - - "DeathReason.WrongAnswer": "Wrong Quiz Answer", - - "TPCooldown": "Teleport Cooldown", - "RiftsTooClose": "Location too close to the first rift", - "RiftCreated": "Rift made successfully", - "RiftsDestroyed": "All rifts Destroyed", - "RiftRadius": "Rift Radius", - - "TiredVision": "Vision When Tired", - "TiredSpeed": "Speed When Tired", - "TiredDur": "Tired Duration", - "ImpCanBeTired": "Impostors can become Tired", - "CrewCanBeTired": "Crewmates can become Tired", - "NeutralCanBeTired": "Neutrals can become Tired", - - "TiredNotify": "Zzz..", - - "PlagueDoctorInfectLimit": "Infect Limit", - "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", - "PlagueDoctorInfectTime": "Infect Time", - "PlagueDoctorInfectDistance": "Infect Distance", - "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", - "PlagueDoctorCanInfectSelf": "Can Infect Self", - "PlagueDoctorCanInfectVent": "Can Infect While In Vent", - "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", - - "StatueSlow": "Statue Slowness", - "StatuePeopleToSlow": "People Needed To Slow", - - "ImpCanBeStatue": "Impostors can become Statue", - "CrewCanBeStatue": "Crewmates can become Statue", - "NeutralCanBeStatue": "Neutrals can become Statue", - - "WardenIncreaseSpeed": "Increase Speed By", - "WardenWarn": "DANGER! RUN!", - - "MinionAbilityTime": "Ability Duration", - "Minion_Blind": "blinded" + "ControlCooldown": "Control Cooldown", + "PoisonCooldown": "Poison Cooldown", + "PoisonerKillDelay": "Poison Kill Delay", + "WardenNotifyLimit": "Max number of alerts", + + "BombCooldown": "Bomb Cooldown", + "Warlock_CanKillSelf": "Can Kill Themselves", + "CrewpostorKnowsAllies": "Knows Impostors", + "AlliesKnowCrewpostor": "Known to Impostors", + "CrewpostorLungeKill": "Crewpostor lunges on kill", + "CrewpostorKillAfterTask": "Number of tasks completed to make one kill", + + "NonNeutralKillingRolesMinPlayer": "Minimum amount of Non-Killing Neutrals", + "NonNeutralKillingRolesMaxPlayer": "Maximum amount of Non-Killing Neutrals", + "NeutralKillingRolesMinPlayer": "Minimum amount of Neutral Killers", + "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", + "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", + "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", + "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", + "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", + "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", + "ImpKnowWhosMadmate": "Impostors know Madmates", + "MadmateKnowWhosImp": "Madmates know Impostors", + "MadmateKnowWhosMadmate": "Madmates know each other", + "ImpCanKillMadmate": "Impostors can kill Madmates", + "MadmateCanKillImp": "Madmates can kill Impostors", + "MadmateHasImpostorVision": "Madmates Have Impostor Vision", + "MadmateCanFixSabotage": "Madmates Can Fix Sabotages", + "EGCanGuessImp": "Can Guess Impostor Roles", + "GGCanGuessCrew": "Can Guess Crewmate Roles", + "EGCanGuessAdt": "Can Guess Add-Ons", + "EGCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "GGCanGuessAdt": "Can Guess Add-Ons", + "GuesserCanGuessTimes": "Maximum number of guesses", + "GuesserTryHideMsg": "Try to hide the guesser's command", + "GCanGuessImp": "Impostor can guess Impostor roles", + "GCanGuessCrew": "Crewmate can guess Crewmate roles", + "GCanGuessAdt": "Can guess Add-ons", + "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", + "BountyTargetChangeTime": "Time Until Target Swaps", + "BountySuccessKillCooldown": "Kill Cooldown After Killing Bounty", + "BountyFailureKillCooldown": "Kill Cooldown After Killing Others", + "BountyShowTargetArrow": "Show arrow pointing towards the target", + "DefaultShapeshiftCooldown": "Default Shapeshift Cooldown", + "DeadImpCantSabotage": "Impostors can't sabotage after they've died", + "VampireKillDelay": "Bite Kill Delay", + "VampireTargetDead": "Target died", + "VampireActionMode": "Action Mode", + "Vampire_OnlyBites": "Only Bites", + "Youtuber_KillerWinsWithYouTuber": "The Killer Wins With YouTuber", + "Maverick_MinKillsToWin": "Minimum number of kills to win", + + "Cooldown": "Cooldown", + "AbilityCooldown": "Ability Cooldown", + "CanKill": "Can Kill", + "KillCooldown": "Kill Cooldown", + "CanVent": "Can Vent", + "ImpostorVision": "Has Impostor Vision", + "CanUseSabotage": "Can Sabotage", + "CanKillImpostors": "Can Kill Impostors", + "CanGuess": "Can Guess in Guesser Mode or as Guesser", + "HideVote": "Hide Vote", + "HideAdditionalVotes": "Hide additional vote(s)", + "CanUseMeetingButton": "Can Call Emergency Meetings", + "ModeSwitchAction": "Switch Action via", + "ShowShapeshiftAnimations": "Show Shapeshift animations", + + "ShapeshifterBase_ShapeshiftCooldown": "Shapeshift Cooldown", + "ShapeshifterBase_ShapeshiftDuration": "Shapeshift Duration", + "ShapeshifterBase_LeaveShapeshiftingEvidence": "Leave Shapeshifting Evidence", + "PhantomBase_InvisCooldown": "Invis Cooldown", + "PhantomBase_InvisDuration": "Invis Duration", + "GuardianAngelBase_ProtectCooldown": "Protect Cooldown", + "GuardianAngelBase_ProtectionDuration": "Protection Duration", + "GuardianAngelBase_ImpostorsCanSeeProtect": "Protect Visible To Impostors", + "ScientistBase_BatteryCooldown": "Vitals Display Cooldown", + "ScientistBase_BatteryDuration": "Battery Duration", + "EngineerBase_VentCooldown": "Vent Cooldown", + "EngineerBase_InVentMaxTime": "Max Time In Vents", + "NoisemakerBase_ImpostorAlert": "Impostors Can Get Alert", + "NoisemakerBase_AlertDuration": "Alert Duration", + "TrackerBase_TrackingCooldown": "Tracking Cooldown", + "TrackerBase_TrackingDuration": "Tracking Duration", + "TrackerBase_TrackingDelay": "Tracking Delay", + + "KamikazeControversialSymbol": "Use controversial symbol", + "MareAddSpeedInLightsOut": "Additional Speed During Lights Out", + "MareKillCooldownInLightsOut": "Kill Cooldown During Lights Out", + "MechanicSkillLimit": "Initial repair use limit", + "MechanicFixesDoors": "Can open all doors in the same building", + "MechanicFixesReactors": "Can Fix Both Reactors Alone", + "MechanicFixesOxygens": "Can Fix Both O2 Alone", + "MechanicFixesCommunications": "Can Fix Both Comms Alone In MIRA HQ", + "MechanicFixesElectrical": "Can Fix Lights With One Switch", + + "SheriffShowShotLimit": "Display Shot Limit next to Role Name", + "SheriffCanKill%role%": "Can Kill %role%", + "SheriffCanKillNeutrals": "Can Kill Neutrals", + "SheriffCanKillNeutralsMode": "Neutral Configuration", + "SheriffCanKillAll": "All ON", + "SheriffCanKillSeparately": "Individual Settings", + "In%team%": "(Team %team%)", + "SheriffMisfireKillsTarget": "Misfire Kills Target", + "SheriffShotLimit": "Max number of Kills", + "SheriffCanKillAllAlive": "Can Kill When No One Is Dead", + "SheriffCanKillCharmed": "Can kill Charmed players", + "SheriffCanKillEgoist": "Can Kill Egoists", + "SheriffCanKillSidekick": "Can Kill Sidekicks", + "SheriffCanKillLovers": "Can Kill Lovers", + "SheriffCanKillMadmate": "Can Kill Madmates", + "SheriffCanKillInfected": "Can Kill Infected players", + "SheriffCanKillContagious": "Can Kill Contagious players", + "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", + "SheriffMadCanKillImp": "Can kill Impostors", + "SheriffMadCanKillNeutral": "Can kill Neutrals", + "SheriffMadCanKillCrew": "Can kill Crewmates", + + "ReverieIncreaseKillCooldown": "Increase kill cooldown", + "ReverieMaxKillCooldown": "Max kill cooldown", + "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", + "ReverieResetCooldownMeeting": "Reset kill cooldown after meeting", + "ConvertedReverieKillAll": "Converted Reverie can kill anyone without repercussions", + + "VigilanteNotify": "You have become the very thing you swore to destroy", + + "DoctorTaskCompletedBatteryCharge": "Battery Duration", + "SnitchEnableTargetArrow": "See Arrow Towards Target", + "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", + "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", + "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", + "SnitchCanFindMadmate": "Can Find Madmates", + "SnitchRemainingTaskFound": "Remaining tasks to be known", + "MayorAdditionalVote": "Additional Votes Count", + "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", + "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", + "MeetingsNeededForWin": "Meetings needed to win", + "ExecutionerCanTargetImpostor": "Can Target Impostors", + "ExecutionerCanTargetNeutralKiller": "Can Target Neutral Killing", + "ExecutionerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "ExecutionerChangeRolesAfterTargetKilled": "When Target Dies, Executioner becomes", + "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", + "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", + "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", + "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", + "LawyerCanTargetImpostor": "Can Target Impostors", + "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", + "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "LawyerCanTargetCrewmate": "Can Target Crewmates", + "LawyerCanTargetJester": "Can Target Jester", + "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", + "LaywerShouldChangeRoleAfterTargetKilled": "Should Lawyer Change Role when Target Dies", + "LawyerTargetDeadInMeeting": "Your target was killed while meeting.\nYour role may change depending on the settings.", + + "MercenaryLimit": "Time Until Suicide", + "ArsonistDouseTime": "Douse Duration", + "CanTerroristSuicideWin": "Can Win By Suicide", + "FireworkerMaxCount": "Fireworker Count", + "FireworkerRadius": "Firework Explosion Radius", + "SniperCanKill": "Sniper can kill with bullets remaining", + "SniperBulletCount": "Ammo", + "SniperPrecisionShooting": "Precise Shooting", + "SniperAimAssist": "Aim Assist", + "SniperAimAssistOneshot": "One shot Assist", + + "PyroDouseCooldown": "Douse cooldown", + "PyroBurnCooldown": "Kill cooldown after killing a doused player", + + "UndertakerFreezeDuration": "Freeze Duration", + "NameDisplayAddons": "Display Add-Ons next to the role name", + "YourAddon": "Your Add-ons:", + "NoLimitAddonsNumMax": "Max Add-ons Per Player", + "LoverSpawnChances": "Spawn Chance of Lovers", + "AdditionRolesSpawnRate": "Spawn Chance", + "TorchVision": "Torch Vision", + "TorchAffectedByLights": "Torch's vision is affected by Lights Sabotage", + "BewilderVision": "Bewilder Vision", + "JesterVision": "Jester Vision", + "LawyerVision": "Lawyer Vision", + "FlashSpeed": "Flash Speed", + "LoverSuicide": "Lovers die together", + "NumberOfLovers": "Number of Lover Pairs (x2 members)", + "LoverKnowRoles": "Lovers know the roles of each other", + "TrapperBlockMoveTime": "Freeze time", + "BecomeTrapperBlockMoveTime": "Freeze time", + "ImpCanBeTrapper": "Impostors can become Beartrap", + "CrewCanBeTrapper": "Crewmates can become Beartrap", + "NeutralCanBeTrapper": "Neutrals can become Beartrap", + "ImpCanBeGravestone": "Impostors can become Gravestone", + "CrewCanBeGravestone": "Crewmates can become Gravestone", + "NeutralCanBeGravestone": "Neutrals can become Gravestone", + "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", + "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", + "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", + "EvilTrackerCanSeeKillFlash": "Can See Kill-Flash", + "EvilTrackerCanSeeLastRoomInMeeting": "Can See Target's Last Room In Meeting", + "EvilTrackerTargetMode": "Can Set Target", + "EvilTrackerTargetMode.Never": "Never", + "EvilTrackerTargetMode.OnceInGame": "Once in-game", + "EvilTrackerTargetMode.EveryMeeting": "Every Meeting", + "EvilTrackerTargetMode.Always": "Any time", + + "EvilHackerCanSeeDeadMark": "Can See The Location of Dead-bodies", + "EvilHackerCanSeeImpostorMark": "Can See The Location of Other Impostors", + "EvilHackerCanSeeKillFlash": "Can See Kill-Flash", + "EvilHackerCanSeeMurderRoom": "Can See The Murder Location", + "EvilHackerMurderNotify": "Murder In", + "EvilHackerLastAdminInfoTitle": "Last-minute admin information", + "EvilHackerDeadbody": "DEAD", + + "TraitorKnowMadmate": "Traitor Knows Madmates", + "NBareRed": "Neutral Benign can be red", + "NEareRed": "Neutral Evil can be red", + "NCareRed": "Neutral Chaos can be red", + "NAareRed": "Neutral Apocalypse can be red", + "CrewKillingRed": "Crewmate Killings can be red", + "PsychicCanSeeNum": "Max number of red names", + "PsychicFresh": "New red names every meeting", + "DetectiveCanknowKiller": "Can find the killer's role", + "EveryOneKnowSuperStar": "Everyone knows the Super Star", + "HackLimit": "Ability Use Count", + "ZombieSpeedReduce": "After a certain time, decrease the speed of Zombie by", + "NemesisCanKillNum": "Max number of revenges", + "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", + "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", + "VectorVentNumWin": "Number of Vents to win", + "CanCheckCamera": "Can track camera usage", + "DefaultKillCooldown": "Starting kill cooldown", + "ReduceKillCooldown": "Reduce kill cooldown by", + "MinKillCooldown": "Minimum kill cooldown", + "BomberRadius": "Bomb radius (5x is about half a Cafeteria)", + "NotifyGodAlive": "Inform players at meetings that God is still alive", + "TransporterTeleportMax": "Max number of teleports", + "TriggerKill": "Kill", + "TriggerVent": "Vent", + "TriggerDouble": "Double Click", + "TimeManagerIncreaseMeetingTime": "Increase voting time by", + "TimeManagerLimitMeetingTime": "Maximum Length of Meetings", + "MadTimeManagerLimitMeetingTime": "Mad Time Manager - Minimum Voting Time", + "AssignOnlyToCrewmate": "Assign only to Crewmates", + "WorkhorseNumLongTasks": "Additional Long Tasks", + "WorkhorseNumShortTasks": "Additional Short Tasks", + "SnitchCanBeWorkhorse": "Snitch can become Workhorse", + "InnocentCanWinByImp": "If their target was an Impostor then they win with them", + "ImpCanBeParanoia": "Impostors can become Paranoia", + "CrewCanBeParanoia": "Crewmates can become Paranoia", + "DualVotes": "Duplicate votes", + "VeteranSkillCooldown": "Alert Cooldown", + "VeteranSkillDuration": "Alert Duration", + "BodyguardProtectRadius": "Protect Radius", + "ImpCanBeEgoist": "An Impostor can become Egoist", + "CrewCanBeEgoist": "Crewmates can become Egoist", + "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", + "EgoistCountAsConverted": "Egoist count as converted neutral", + "ImpCanBeSeer": "Impostors can become Seer", + "CrewCanBeSeer": "Crewmates can become Seer", + "NeutralCanBeSeer": "Neutrals can become Seer", + "ImpCanBeGuesser": "Impostors can become Guesser", + "CrewCanBeGuesser": "Crewmates can become Guesser", + "NeutralCanBeGuesser": "Neutrals can become Guesser", + "ImpCanBeWatcher": "Impostors can become Watcher", + "CrewCanBeWatcher": "Crewmates can become Watcher", + "NeutralCanBeWatcher": "Neutrals can become Watcher", + "ImpCanBeBait": "Impostors can become Bait", + "CrewCanBeBait": "Crewmates can become Bait", + "NeutralCanBeBait": "Neutrals can become Bait", + "ImpCanBeRainbow": "Impostors can become Rainbow", + "NeutralCanBeRainbow": "Neutrals can become Rainbow", + "CrewCanBeRainbow": "Crewmates can become Rainbow", + "GuessRainbow": "He seems too obvious, doesn't he?", + "RainbowColorChangeCoolDown": "The cooldown for changing colors", + "RainbowInCamouflage": "Rainbow color changes during Camouflage", + "BaitDelayMin": "Minimum Report Delay", + "BaitDelayMax": "Maximum Report Delay", + "BaitDelayNotify": "Warn the killer about the upcoming self-report", + "BecomeBaitDelayNotify": "Warn the killer about the upcoming self-report", + "BaitNotification": "Reveal Bait at the first meeting", + "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self-report.", + "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if a meeting is disabled during comms sabotage", + "DeceiverSkillCooldown": "Ability cooldown", + "DeceiverSkillLimitTimes": "Max number of uses", + "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", + "PursuerSkillCooldown": "Ability cooldown", + "PursuerSkillLimitTimes": "Max number of uses", + "AddictSuicideTimer": "Time Until Suicide", + "GrenadierSkillCooldown": "Grenade Cooldown", + "GrenadierSkillDuration": "Grenade Duration", + "GrenadierCauseVision": "Lowered vision", + "GrenadierCanAffectNeutral": "Can affect Neutrals", + "TicketsPerKill": "Votes Increase Amount Per Kill", + "GangsterRecruitCooldown": "Recruit cooldown", + "GangsterRecruitLimit": "Recruit limit", + "KamikazeMaxMarked": "Max Marked", + "RevolutionistDrawTime": "Tag Duration", + "RevolutionistCooldown": "Tag Cooldown", + "RevolutionistDrawCount": "Amount of Players needed to Tag", + "RevolutionistKillProbability": "Tagged player sacrifice probability", + "RevolutionistVentCountDown": "Time to Vent", + "PelicanKillCooldown": "Eat Cooldown", + "Pelican.TargetCannotBeEaten": "Target cannot be eaten", + "MadSnitchTasks": "Snitch Tasks", + "MedicWhoCanSeeProtect": "Who can see shield", + "MedicKnowShieldBroken": "Who sees kill attempt", + "Medic_SeeMedicAndTarget": "Medic+Shielded", + "Medic_SeeMedic": "Medic", + "Medic_SeeTarget": "Shielded", + "Medic_SeeNoOne": "Nothing", + "MedicShieldDeactivatesWhenMedicDies": "Shield deactivates when the Medic dies", + "MedicShielDeactivationIsVisible": "Shield deactivation is visible", + "MedicShieldDeactivationIsVisible_Immediately": "Immediately", + "MedicShieldDeactivationIsVisible_AfterMeeting": "After Meeting", + "MedicShieldDeactivationIsVisible_OFF": "OFF", + "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", + "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", + "FortuneTellerSkillLimit": "Max number of ability uses", + "MadmateSpawnMode": "Madmate spawning mode", + "MadmateSpawnMode.Assign": "Assign", + "MadmateSpawnMode.FirstKill": "First Kill", + "MadmateSpawnMode.SelfVote": "Self Vote", + "MadmateCountMode": "Madmates count as", + "MadmateCountMode.None": "Nothing", + "MadmateCountMode.Imp": "Impostors", + "MadmateCountMode.Original": "Original Team", + + "SnatchesWin": "Snatches victory", + "DemonKillCooldown": "Attack Cooldown", + "DemonHealthMax": "Player max health", + "DemonDamage": "Damage ", + "DemonSelfHealthMax": "Demon max health", + "DemonSelfDamage": "Demon damage received", + "LightningConvertTime": "Duration of the transformation to Quantum Ghost", + "LightningKillCooldown": "Lightning Cooldown", + "LightningKillerConvertGhost": "Killer can transform into Quantum Ghost", + "CanCountNeutralKiller": "When Crewmates win by killing a Neutral player, they can snatch the victory", + "GreedyOddKillCooldown": "Odd-Numbered kill cooldown", + "GreedyEvenKillCooldown": "Even-Numbered kill cooldown", + "WorkaholicCannotWinAtDeath": "Can't win after they died", + "WorkaholicVisibleToEveryone": "Everyone knows who the Workaholic is", + "WorkaholicGiveAdviceAlive": "Advice at the first meeting if alive, can win after death, ghost tasks ON", + "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", + "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", + "KillAttackerWhenAbilityRemaining": "Kill attacker when ability is remaining", + "JinxSpellTimes": "Amount of Jinx Spells", + "CollectorCollectAmount": "Required number of votes", + "GlitchCanVote": "Can vote", + "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", + "MeetingReserved": "Max Bullets reserved for a meeting", + "AccurateCheckMode": "Can know specific role when tasks are not done", + "RandomActiveRoles": "Show random active roles in Fortune Teller hints", + "CamouflageCooldown": "Camouflage Cooldown", + "CamouflageDuration": "Camouflage Duration", + "NinjaMarkCooldown": "Mark Cooldown", + "NinjaAssassinateCooldown": "Assassinate Cooldown", + "NinjaModeDouble": "Double Click = Kill, Single Click = Mark", + "JudgeCanTrialnCrewKilling": "Can trial Crewmate Killing", + "JudgeCanTrialNeutralB": "Can trial Neutral Benign", + "JudgeCanTrialNeutralK": "Can trial Neutral Killing", + "JudgeCanTrialNeutralE": "Can trial Neutral Evil", + "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", + "JudgeCanTrialNeutralA": "Can trial Neutral Apocalypse", + "JudgeCanTrialSidekick": "Can trial Sidekick", + "JudgeCanTrialInfected": "Can trial Infected", + "JudgeCanTrialContagious": "Can trial Contagious", + "JudgeTryHideMsg": "Hide Judge's commands", + "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", + "JudgeCanTrialMadmate": "Can trial Madmates", + "JudgeCanTrialCharmed": "Can trial Charmed players", + "JudgeDead": "Sorry, you can't trial players after death.", + "JudgeTrialMax": "\nNo more trials left!", + "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", + "Judge_TrialKill": "{0} was judged.", + "Judge_TrialKillTitle": "COURT", + "Judge_TrialHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Judge_TrialNull": "Please choose a living player for the trial", + "VeteranSkillMaxOfUseage": "Max number of Alerts", + "SwooperCooldown": "Swoop Cooldown", + "SwooperDuration": "Swoop Duration", + "WraithCooldown": "Vanish Cooldown", + "WraithDuration": "Vanish Duration", + "BastionNotify": "A bomb was set off", + "EnteredBombedVent": "That vent was bombed!", + "BastionVentButtonText": "Bomb", + "BombsClearAfterMeeting": "Bombs clear after meetings", + "BastionMaxBombs": "(Initial) Maximum bombs", + "VentBombSuccess": "Bomb has been planted", + "LowLoadMode": "Low Load Mode", + "ShowLobbyCode": "Show lobby code in Discord status", + "BKProtectDuration": "Protection Duration", + "FollowerMaxBetTimes": "Maximum Number of Follows", + "FollowerBetCooldown": "Follow Cooldown", + "FollowerMaxBetCooldown": "Maximum Follow Cooldown", + "FollowerBetCooldownIncrese": "Increase Cooldown per 1 follow by", + "FollowerKnowTargetRole": "Follower knows their target's role", + "FollowerBetTargetKnowFollower": "Follower target knows who the Follower is", + "FortuneTellerHideVote": "Hide Fortune Teller's Votes", + "CultistCharmCooldown": "Charm Cooldown", + "CultistCharmCooldownIncrese": "Increases Charm Cooldown For Each Charm", + "CultistCharmMax": "Maximum Number Of Charm", + "CultistKnowTargetRole": "Know Charmed Player's Role", + "CultistTargetKnowOtherTarget": "Charmed players know each other", + "CultistCanCharmNeutral": "Neutral Roles can be Charmed", + "InfectiousBiteCooldown": "Infect Cooldown", + "KnowTargetRole": "Knows role of target", + "TargetKnowsLawyer": "Target knows their Lawyer", + "InfectiousBiteMax": "Maximum Infections", + "InfectiousKnowTargetRole": "Know infected player's role", + "InfectiousTargetKnowOtherTarget": "Infected players know each other", + "DoubleClickKill": "Double click to kill the target", + + "VirusInfectMax": "Maximum Number Of Spreads", + "VirusKnowTargetRole": "Know Contagious Player's Role", + "VirusTargetKnowOtherTarget": "Contagious players know each other", + "VirusKillInfectedPlayerAfterMeeting": "Contagious player dies after meeting", + "Virus_ContagiousCountMode": "Contagious players count as", + "Virus_ContagiousCountMode_None": "Nothing", + "Virus_ContagiousCountMode_Virus": "Virus", + "Virus_ContagiousCountMode_Original": "Original Team", + "VirusNoticeTitle": "[ Infected Corpse! ]", + "VirusNoticeMessage": "The body you reported was infected by the Virus! You are now part of Team Virus. Help the Virus win the game.", + "VirusNoticeMessage2": "The body you reported was infected by the Virus! Vote the Virus out during this meeting, or you will die.", + + "Cultist_CharmedCountMode": "Charmed players count as", + "Cultist_CharmedCountMode_None": "Nothing", + "Cultist_CharmedCountMode_Cultist": "Cultist", + "Cultist_CharmedCountMode_Original": "Original Team", + + "JackalCanWinBySabotageWhenNoImpAlive": "When all Impostors are dead, the Jackal wins by sabotage instead", + "JackalResetKillCooldownWhenPlayerGetKilled": "Reset kill cooldown if someone gets killed by another player", + "JackalResetKillCooldownOn": "Kill Cooldown On Reset", + "JackalCanRecruitSidekick": "Can recruit Sidekick", + "JackalSidekickRecruitLimit": "Maximum Number Of Recruits", + "Jackal_SidekickCountMode": "Sidekicks count as", + "Jackal_SidekickCountMode_None": "Nothing", + "Jackal_SidekickCountMode_Jackal": "Jackal", + "Jackal_SidekickCountMode_Original": "Original Team", + "Jackal_SidekickAssignMode": "Sidekick Assign Mode", + "Jackal_SidekickAssignMode_SidekickAndRecruit": "Sidekick+Recruit", + "Jackal_SidekickAssignMode_Sidekick": "Sidekick Only", + "Jackal_SidekickAssignMode_Recruit": "Recruit Only", + "JackalWinWithSidekick": "Jackal can win with Sidekick's team", + "Jackal_SidekickCanKillSidekick": "Sidekicks can kill other Sidekicks", + "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", + "JackalCanKillSidekick": "Jackal can kill Sidekick", + + "ImpCanBeNecroview": "Impostors can become Necroview", + "CrewCanBeNecroview": "Crewmates can become Necroview", + "NeutralCanBeNecroview": "Neutrals can become Necroview", + "ImpCanBeInLove": "Impostors can be in love", + "CrewCanBeInLove": "Crewmates can be in love", + "NeutralCanBeInLove": "Neutrals can be in love", + "ImpCanBeOblivious": "Impostors can become Oblivious", + "CrewCanBeOblivious": "Crewmates can become Oblivious", + "NeutralCanBeOblivious": "Neutrals can become Oblivious", + "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", + "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", + "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", + "HexesLookLikeSpells": "Hexes appear as spells", + "HexButtonText": "Hex", + "ObliviousBaitImmune": "Immune to Bait", + "ImpCanBeOnbound": "Impostors can become Onbound", + "CrewCanBeOnbound": "Crewmates can become Onbound", + "NeutralCanBeOnbound": "Neutrals can become Onbound", + + "ImpCanBeRebound": "Impostors can become Rebound", + "CrewCanBeRebound": "Crewmates can become Rebound", + "NeutralCanBeRebound": "Neutrals can become Rebound", + + "CrewCanBeMundane": "Crewmates can become Mundane", + "NeutralCanBeMundane": "Neutrals can become Mundane", + "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", + + "ImpCanBeUnreportable": "Impostors can become Disregarded", + "CrewCanBeUnreportable": "Crewmates can become Disregarded", + "NeutralCanBeUnreportable": "Neutrals can become Disregarded", + "PacifistCooldown": "Ability Cooldown", + "PacifistMaxOfUseage": "Max Number of Ability Uses", + "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", + "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", + + "PresidentAbilityUses": "Max Number of Ability Uses", + "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", + "HidePresidentEndCommand": "Hide President's commands", + "NeutralsSeePresident": "Neutrals can see revealed President", + "MadmatesSeePresident": "Madmates can see revealed President", + "ImpsSeePresident": "Impostors can see revealed President", + "PresidentDead": "Sorry, you can't force end the meeting after death.", + "PresidentEndMax": "No more force end meeting uses left!", + "PresidentRevealMax": "You have already revealed yourself...", + "PresidentRevealed": "[{0}] has chosen to reveal themselves as President!", + "GuessPresident": "President has revealed themselves. You can't guess them.", + "PresidentRevealTitle": "PRESIDENT REVEAL", + + "LuckyProbability": "Probability of surviving a kill", + "ImpCanBeLucky": "Impostors can become Lucky", + "CrewCanBeLucky": "Crewmates can become Lucky", + "NeutralCanBeLucky": "Neutrals can become Lucky", + "ImpCanBeFool": "Impostors can become Fool", + "CrewCanBeFool": "Crewmates can become Fool", + "NeutralCanBeFool": "Neutrals can become Fool", + "ImpCanBeDoubleShot": "Impostors can have Double Shot", + "CrewCanBeDoubleShot": "Crewmates can have Double Shot", + "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", + "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", + "DisableReportWhenCamouflageIsActive": "Disable body reporting when camouflage is active", + "CanUseCommsSabotage": "Can use comms sabotage", + "ModTag": "Moderator♥", + "ApplyModeratorList": "Apply Moderator List", + "VipTag": "VIP★", + "ApplyVipList": "Apply VIP List", + "AllowSayCommand": "Allow moderators to use /say command", + "KickCommandDisabled": "The kick command is currently disabled.", + "KickCommandNoAccess": "You do not have access to the kick command.", + "KickCommandInvalidID": "Invalid player ID specified.\nPlease use '/kick [playerID] [reason]' to kick a player.\nExample :- /kick 5 not following rules", + "KickCommandKickHost": "You are not permitted to kick the host.", + "KickCommandKickMod": "You are not permitted to kick other moderators.", + "KickCommandKicked": "was kicked from the game by ", + "KickCommandKickedRole": "Their role was", + "BanCommandDisabled": "The ban command is currently disabled.", + "BanCommandNoAccess": "You do not have access to the ban command.", + "BanCommandInvalidID": "Invalid player ID specified.\nPlease use '/ban [playerID] [reason]' to ban a player.\nExample :- /ban 5 not following rules ", + "BanCommandBanHost": "You are not permitted to ban the host.", + "BanCommandBanMod": "You are not permitted to ban other moderators.", + "BanCommandBanned": "was banned from the game by ", + "BanCommandBannedRole": "Their role was", + "BanCommandNoReason": "No reason specified.\nPlease use '/ban [playerID] [reason]\nExample :- /ban 5 not following rules", + "ColorCommandDisabled": "The modcolor command is currently disabled.", + "ColorCommandNoAccess": "You do not have access to the modcolor command.", + "ColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "ColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode]' to change the color of MODERATOR♥.\nExample :- /modcolor 33ccff", + "ColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/modcolor [hexcode][hexcode]' to change color of MODERATOR♥.\nExample :- /modcolor 33ccff ff99cc", + "VipColorCommandDisabled": "The vipcolor command is currently disabled.", + "VipColorCommandNoAccess": "You do not have access to the vipcolor command.", + "VipColorCommandNoLobby": "You can only use the command in Lobby when the game hasn't started.", + "VipColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode]' to change the color of VIP★.\nExample :- /vipcolor 33ccff", + "VipColorInvalidGradientCode": "Invalid hexcode specified.\nPlease use '/vipcolor [hexcode][hexcode]' to change color of VIP★.\nExample :- /vipcolor 33ccff ff99cc", + "TagColorInvalidHexCode": "Invalid hexcode specified.\nPlease use '/tagcolor [hexcode]' to change the color of your tag.\nExample :- /tagcolor ff00ff", + "midCommandDisabled": "The mid command is currently disabled.", + "midCommandNoAccess": "You do not have access to the mid command.", + "WarnCommandDisabled": "The warn command is currently disabled.", + "WarnCommandNoAccess": "You do not have access to the warn command.", + "WarnCommandInvalidID": "Invalid player ID specified.\nPlease use '/warn [playerID] [reason]' to warn a player. \nExample :- /warn 5 lava chatting", + "WarnCommandWarnHost": "You are not permitted to warn the host.", + + "WarnCommandWarnMod": "You are not permitted to warn other moderators.", + "WarnCommandWarned": "has been warned. There will be no more warnings given and appropriate action will be taken \n ", + "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", + "SayCommandDisabled": "The say command is currently disabled.", + "MessageFromModerator": "MODERATOR", + "DeathReason.Kill": "Kill", + "DeathReason.Vote": "Ejected", + "DeathReason.Suicide": "Suicide", + "DeathReason.Spell": "Spelled", + "DeathReason.Cursed": "Cursed", + "DeathReason.Hex": "Hexed", + "DeathReason.Bite": "Bitten", + "DeathReason.Poison": "Poisoned", + "DeathReason.Gambled": "Guessed", + "DeathReason.FollowingSuicide": "Heartbroken", + "DeathReason.Bombed": "Exploded", + "DeathReason.Misfire": "Misfire", + "DeathReason.Torched": "Burned", + "DeathReason.Sniped": "Sniped", + "DeathReason.Execution": "Executed", + "DeathReason.Fall": "Fall", + "DeathReason.Revenge": "Revenge", + "DeathReason.Eaten": "Eaten", + "DeathReason.Sacrifice": "Victim", + "DeathReason.Quantization": "Quantization", + "DeathReason.Overtired": "Overtired", + "DeathReason.Ashamed": "Ashamed", + "DeathReason.PissedOff": "Destroyed", + "DeathReason.Dismembered": "Dismembered", + "DeathReason.LossOfHead": "Strangled", + "DeathReason.Trialed": "Judged", + "DeathReason.Infected": "Infected", + "DeathReason.Jinx": "Jinxed", + "DeathReason.Pirate": "Plundered", + "DeathReason.Shrouded": "Shrouded", + "DeathReason.etc": "Other", + "DeathReason.Mauled": "Mauled", + "DeathReason.Hack": "Hacked", + "DeathReason.Curse": "Cursed", + "DeathReason.Drained": "Drained", + "DeathReason.Shattered": "Shattered", + "DeathReason.Trap": "Trapped", + "DeathReason.Targeted": "Targeted", + "DeathReason.Retribution": "Retribution", + "DeathReason.Slice": "Sliced", + "DeathReason.BloodLet": "Bleed", + "DeathReason.Armageddon": "Armageddon", + "DeathReason.Starved": "Starved", + "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", + "Alive": "Alive", + "Disconnected": "Disconnected", + "Win": " Wins!", + + "Last-": "Last ", + "Madmate-": "Madmate ", + "Recruit-": "Recruit ", + "Charmed-": "Charmed ", + "Soulless-": "Soulless ", + "Infected-": "Infected ", + "Contagious-": "Contagious ", + "Admired-": "Admired ", + + "DeputyHandcuffCooldown": "Handcuff Cooldown", + "DeputyHandcuffMax": "Maximum Handcuffs", + "DeputyHandcuffedPlayer": "Handcuffed target", + "HandcuffedByDeputy": "You were handcuffed!", + "DeputyInvalidTarget": "Target cannot be handcuffed", + "DeputyHandcuffText": "Handcuff", + "DeputyHandcuffCDForTarget": "Kill Cooldown for handcuffed player", + + "RejectShapeshift.AbilityWasUsed": "Ability was used", + + "EscapisMtarkedPosition": "You marked self-position", + + "InvestigateCooldown": "Investigate Cooldown", + "InvestigateMax": "Maximum Investigations", + "InvestigateRoundMax": "Maximum Investigations in one round", + + "Color.Red": "Red", + "Color.Green": "Green", + "Color.Gray": "Gray", + "InvestigatorInvestigatedPlayer": "Player Investigated", + "InvestigatorInvalidTarget": "Can not investigate", + "InvestigatorButtonText": "Check", + + "Investigator.Suspicion": "Suspicion", + "Investigator.Role": "Role", + "SabotageCooldownControl": "Sabotage Cooldown Control", + "SabotageCooldown": "Sabotage Cooldown", + "SabotageTimeControl": "Sabotage Duration Control", + "SkeldReactorTimeLimit": "The Skeld Reactor Time Limit", + "SkeldO2TimeLimit": "The Skeld O2 Time Limit", + "MiraReactorTimeLimit": "MIRA HQ Reactor Time Limit", + "MiraO2TimeLimit": "MIRA HQ O2 Time Limit", + "PolusReactorTimeLimit": "Polus Reactor Time Limit", + "AirshipReactorTimeLimit": "Airship Reactor Time Limit", + "FungleReactorTimeLimit": "The Fungle Reactor Time Limit", + "FungleMushroomMixupDuration": "The Fungle Mushroom Mixup Duration", + "CommandList": "★ Command list:", + "Command.now": "→ Display active Settings", + "Command.roles": "[RoleName] → Display Role description", + "Command.myrole": "→ Displays a description of your role", + "Command.lastresult": "→ Display match results", + "Command.winner": "→ Display winners", + "CommandOtherList": "● Other commands:", + "Command.color": "[Color] → Change your color", + "Command.rename": "[Name] → Change Host Name", + "Command.quit": "→ I don't want to enter this lobby anymore", + "CommandHostList": "▲ Host Commands:", + "Command.say": "[Content] → Send message as Host", + "Command.mw": "[Seconds] → Set the message waiting duration", + "Command.solvecover": "→ Fix an issue where role names overlap the messages", + "Command.kill": "[Player ID] → Kill assigned player", + "Command.exe": "[Player ID] → Eject assigned player", + "Command.level": "[Level] → Change your in-game level", + "Command.idlist": "→ Display a list of player IDs", + "Command.qq": "→ Lobby will be posted on QQ website (China only)", + "Command.dump": "→ Output Log to Desktop", + "Command.death": "→ Display info on how you died", + "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Capitan to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.", + + "Command.iconinfo": "→ Display info on in-meeting icons", + "Command.iconhelp": "→ Display info on in-meeting icons to everyone", + "Command.Poll": "→ Start a poll with up-to 5 choices", + "IconsTitle": "Icon Meanings⚠", + "Remaining.ImpostorCount": "Impostors left: {0}", + "Remaining.MadmateCount": "Madmates left: {0}", + "Remaining.NeutralCount": "Neutral Killers left: {0}", + "Remaining.ApocalypseCount": "Neutral Apocalypse left: {0}", + "EnableKillerLeftCommand": "Enable use of /kcount command", + "ShowMadmatesInLeftCommand": "Show Madmates (including add-ons)", + "ShowApocalypseInLeftCommand": "Show Neutral Apocalypse", + "SeeEjectedRolesInMeeting": "See ejected roles in meetings", + + "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", + "NemesisDeadMsg": "The death of the Nemesis means the beginning of the revenge. \nPlease use /rv + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /rv to get a list of player IDs", + "NemesisAliveKill": "Revenge for the Nemesis can only begin after their death.", + "NemesisKillDead": "Choose a living player to take revenge", + "NemesisKillSucceed": "[{0}] was killed by the Nemesis!", + "NemesisKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game", + "NemesisKillMax": "You've reached the maximum amount of kills, you can't kill anymore!", + + "CelebrityDead": "Shock! Celebrity[{0}]has unfortunately been mercilessly killed in the recent period!", + "CyberDead": "Oh no! It appears the Cyber, {0}, has died recently.", + "DetectiveNoticeVictim": "According to your investigation,\nthe victim ([{0}]) had the role [{1}]", + "DetectiveNoticeKiller": "\nThe killer's role is [{0}]", + "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer. This death is most likely suicide.", + "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", + "WorkaholicAdviceAlive": "It's not recommended to kill or vote [{0}] out. Doing so will help them finish their tasks quicker.", + "GuessDead": "Sorry, but it's impossible to guess roles after your death", + "GuessSuperStar": "The Super Star can't be guessed... you thought it would be that easy, right?", + "GuessNotifiedBait": "Bait can't be guessed because it was announced. You thought it would be that easy, right?", + "GuessGM": "Guessing the GM is impossible because they're already dead.... And why would you do that to the poor Host?", + "GuessGuardianTask": "You can't guess a Guardian who has finished their tasks.", + "GuessMarshallTask": "You can't guess a Marshall who has finished their tasks.", + "GuessObviousAddon": "Sorry, obvious add-ons cannot be guessed.", + "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", + "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", + "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", + "GuessKill": "{0} was guessed", + "GuessNull": "Please select an ID of a living player to guess their role", + "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "GGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", + "EGGuessMax": "You've reached the maximum guess limit. You can't guess anymore!", + "EGGuessSnitchTaskDone": "You thought you could guess the Snitch when all their tasks are done? Nice try. You're not getting out of this that easily.", + "GuessDoubleShot": "You guessed a role incorrectly, but you have Double Shot, so you get another chance!", + "LaughToWhoGuessSelf": "Tried to guess, who tried to self-guess! It's you! Ahah!", + "GuessDisabled": "Sorry, the host restricted guessing for your role.", + "GuessWorkaholic": "Sorry, you can't guess a revealed Workaholic as that would be unfair.", + "GuessDoctor": "Sorry, you can't guess a revealed Doctor as that would be unfair.", + "GuessMayor": "Sorry, you can't guess a revealed Mayor as that would be unfair.", + "GuessKnighted": "Sorry, Monarchs cannot guess Knighted.", + "GuessMonarch": "There's a knighted player alive, so the Monarch cannot be guessed.", + "GuessShielded": "Sorry, you can't guess the player who the Medic shields", + "MayorRevealWhenDoneTasks": "Mayor is revealed to everyone on task completion", + "MimicDeadMsg": "Mimic's hint: ", + "FortuneTellerCheck": "According to your fortune...", + "FortuneTellerCheckLimit": "Reminder: You have {0} fortunes left", + "FortuneTellerCheckSelfMsg": "Wow, you found yourself... All you see is a reflection.", + "FortuneTellerCheckReachLimit": "You've run out of fortunes.", + "FortuneTellerAlreadyCheckedMsg": "You've already checked the player", + "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", + "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", + + "MediumContactLimit": "Max number of contacts (ability uses)", + "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", + "MediumTitle": "MEDIUM", + "MediumHelp": "/ms yes to agree\n/ms no to disagree", + "MediumYes": "You thought you heard a quiet voice from another world affirming the answer to your question.", + "MediumNo": "You thought you heard a quiet voice from another world denying the answer to your question.", + "MediumDone": "You successfully responded to the Medium.", + "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", + "MediumNotifySelf": "You established contact with {0}. Please ask them questions and wait for them to respond.\n\nRemaining ability uses: {1}", + "MediumKnowPlayerDead": "Someone died somewhere", + + "ByBard": "by Bard", + "ByBardGetFailed": "Oops, I seem to be out of inspiration.", + "GangsterSuccessfullyRecruited": "You successfully recruited a player", + "GangsterRecruitmentFailure": "Target cannot be recruited", + "BeRecruitedByGangster": "The Gangster has recruited you", + "KamikazeHostage": "Can't hold target hostage", + "VeteranOnGuard": "Ability in use", + "VeteranOffGuard": "Ability expired, {0} uses remain", + "VeteranMaxUsage": "Ability use limit reached", + "GrenadierSkillInUse": "Ability in use", + "GrenadierSkillStop": "Ability expired", + "TicketsStealerGetTicket": "You've got {0} votes", + "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", + "CleanerCleanBody": "The body has been cleaned", + "QuickShooterStoraging": "Bullets stored successfully", + "PoisonerTargetDead": "Target died", + "BloodthirstAdded": "Your bloodthirst is now active!", + "WarlockNoTarget": "Manipulation failed due to no target", + "WarlockNoTargetYet": "You haven't marked a target.", + "WarlockTargetDead": "Manipulation failed due to target dead", + "WarlockControlKill": "Target died", + "OnCelebrityDead": "Warning: Celebrity death!", + "OnCyberDead": "Warning: Cyber died!", + "TeleportedInRndVentByDisperser": "Everyone was teleported to vents", + "TeleportedByTransporter": "Swapping places with: {0}", + "ErrorTeleport": "Teleport failed", + "EraseLimit": "Max Erases", + "EraserHideVote": "Hide Eraser Votes", + "EraserEraseMsgTitle": "ERASER", + "EraserEraseNotice": "You erased {0}.\nTheir role will be deactivated after the meeting.", + "EraserEraseBaseImpostorOrNeutralRoleNotice": "Oops, your target cannot be erased!", + "EraserEraseSelf": "Unfortunately, you can't erase yourself... Wait, why would you do that in the first place?!", + "EraserTryingGuessErasedPlayer": "You can't guess the role of the player you erased, except add-ons", + "LostRoleByEraser": "You lost your role because of the Eraser", + "KilledByScavenger": "The Scavenger killed you and thus teleported off-map", + "SnitchDoneTasks": "Call a meeting to find the impostors", + "SwooperCanVent": "Vent to turn invisible", + "SwooperInvisState": "You're invisible", + "SwooperInvisStateOut": "You're now visible", + "SwooperInvisInCooldown": "Swoop cooldown isn't up yet. Swooping failed", + "SwooperInvisStateCountdown": "Invisibility will expire after {0}s", + "SwooperInvisCooldownRemain": "Swoop Cooldown: {0}s", + "WraithCanVent": "Vent to turn invisible", + "WraithInvisState": "You are invisible", + "WraithInvisStateOut": "You are visible again", + "WraithInvisInCooldown": "Ability still on cooldown, vanish failed", + "WraithInvisStateCountdown": "Invisibility will expire in {0}s", + "WraithInvisCooldownRemain": "{0}s left in invisibility", + "WerewolfKillButtonText": "Maul", + "BKInProtect": "Currently immortal", + "BKProtectOut": "Shield expired", + "BKSkillTimeRemain": "You're immune for {0} seconds", + "BKSkillNotice": "Kill a player to enter immune status", + "BKOffsetKill": "Someone tried killing you", + "MedicKillerTryBrokenShieldTargetForMedic": "Someone tried killing the player you shielded!", + "MedicKillerTryBrokenShieldTargetForTarget": "Someone tried killing you!", + "FollowerBetPlayer": "You're now following your target", + "FollowerBetOnYou": "The Follower is now following you", + "CultistCharmedPlayer": "You successfully charmed a player", + "CharmedByCultist": "You have been charmed by the Cultist", + "CultistInvalidTarget": "Target cannot be charmed", + "KillBaitNotify": "You'll self-report in {0}s", + "InfectiousInvalidTarget": "Target cannot be infected", + "BittenByInfectious": "The Infectious infected you!", + "InfectiousBittenPlayer": "You successfully infected a player", + "GuessNotAllowed": "Sorry, your role does not have access to guessing.", + "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", + "GuessSpecter": "You can't guess a Specter. That allows them to win!", + "PacifistOnGuard": "Ability used, {0} uses remain", + "PacifistMaxUsage": "Ability use limit reached", + "PacifistSkillNotify": "Pacifist reset your kill cooldown", + "BeRecruitedByJackal": "The Jackal has recruited you", + "CoronerTrackRecorded": "Track recorded", + "CoronerNoTrack": "Nothing to track", + "CoronerIsTrackingYou": "The Coroner is tracking you!", + "CoronerReportButtonText": "Track", + "MerchantAddonDelivered": "Add-on sold", + "MerchantAddonSell": "The Merchant sold you a new Add-on", + "MerchantAddonSellFail": "Could not sell an Add-on", + "BribedByMerchant": "The Merchant bribed you. You can't kill him", + "BribedByMerchant2": "You cannot guess the Merchant after he bribed you.", + "MerchantKillAttemptBribed": "An attempted killing was averted by bribery", + "TrapTrapsterBody": "Trap Trapster's body", + "TrapConsecutiveBodies": "Trap consecutive bodies", + "HauntedByEvilSpirit": "Haunted by an Evil Spirit", + "MonarchKnightCooldown": "Knight Cooldown", + "MonarchKnightMax": "Maximum Knights", + "HideAdditionalVotesForKnighted": "Hide additional vote for Knighted players", + "MonarchKnightedPlayer": "You successfully knighted a player!", + "KnightedByMonarch": "A Monarch has knighted you!", + "MonarchInvalidTarget": "Target cannot be knighted", + "GhostTransformTitle": "Your Role Has Transformed!", + "SpiritcallerNoticeTitle": "YOU TURNED INTO AN EVIL SPIRIT ", + "SpiritcallerNoticeMessage": "The Spiritcaller has killed you and turned you into an Evil Spirit. Your task now is to help the Spiritcaller to victory by using your spook button to hinder other players or to protect the Spiritcaller. Use /m for more information.", + "OverseerRevealCooldown": "Reveal Cooldown", + "OverseerRevealTime": "Reveal Time", + "OverseerVision": "Overseer Vision", + "MerchantMaxSell": "Max number of Add-ons to sell", + "MerchantMoneyPerSell": "Amount of money earned for selling an Add-on", + "MerchantMoneyRequiredToBribe": "Amount of money required to bribe a killer", + "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", + "MerchantTargetCrew": "Can sell to Crewmates", + "MerchantTargetImpostor": "Can sell to Impostors", + + "MerchantTargetNeutral": "Can sell to Neutrals", + "MerchantSellHelpful": "Can sell Helpful Add-ons", + "MerchantSellHarmful": "Can sell Harmful Add-ons", + "MerchantSellMixed": "Can sell Mixed Add-ons", + "MerchantSellExperimental": "Can sell experimental Add-ons", + "MerchantSellHarmfulToEvil": "Can sell Harmful Add-ons only to Evil", + "MerchantSellHelpfulToCrew": "Can sell Helpful Add-ons only to Crew", + "MerchantSellOnlyEnabledAddons": "Can sell only enabled Add-ons", + + "SpiritcallerSpiritMax": "Maximum number of Evil Spirits", + "SpiritcallerSpiritAbilityCooldown": "Evil Spirit ability cooldown", + "SpiritcallerFreezeTime": "Evil Spirit ability freeze time", + "SpiritcallerProtectTime": "Evil Spirit ability protect time", + "SpiritcallerCauseVision": "Evil Spirit ability caused vision", + "SpiritcallerCauseVisionTime": "Evil Spirit ability caused vision time", + "Message.SetToSeconds": "Set to [{0}] seconds.", + "Message.MessageWaitHelp": "Specify the first argument in seconds.", + "Message.TemplateNotFoundHost": "No templates.txt matching {0} were found", + "Message.TemplateNotFoundClient": "The Host doesn't have a template called {0}", + "Message.SyncButtonLeft": "There are {0} more emergency buttons left", + "Message.Executed": "{0} was executed", + "Message.HideGameSettings": "The host has hidden the game settings.", + "Message.NowOverrideText": "Please enter the root folder of the game.\\Language\\English.dat. Change this text in the dat file \nIf you don't need this feature or want to display regular /n messages. \nPlease disable [Enable only custom /n messages in the settings.]", + "Message.NoDescription": "No description", + "Message.KickedByDenyName": "{0} was kicked because its name matched {1}", + "Message.BannedByBanList": "{0} was banned because they were banned in the past.", + "Message.BannedByEACList": "{0} has been banned because he is in the EAC list of Banned people.", + "Message.DumpfileSaved": "The log file was successfully saved to the desktop, filename: {0}", + "Message.DumpcmdUsed": "{0} used /dump command.", + "Message.KickedByInvalidFriendCode": "{0} was kicked because their friend code is invalid.", + "Message.TempBannedByInvalidFriendCode": "{0} was temporarily banned because their friend code is invalid.", + "Message.AddedPlayerToBanList": "Added {0} to the ban list", + "Message.KickWhoSayStart": "{0} has been kicked by the system. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayStart": "{0} has been warned: {1} times \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.KickStartAfterWarn": "{0} has received {1} warnings, he will be kicked. \nThe lobby host doesn't want to see messages where the player asks to start", + "Message.WarnWhoSayBanWord": "{0}, stop sending banned words!", + "Message.WarnWhoSayBanWordTimes": "{0} has been warned: {1} times \nif you continue you will be kicked", + "Message.KickWhoSayBanWordAfterWarn": "[{0}] received {1} warnings.\nHe was expelled for forbidden words", + "Message.KickedByEAC": "[{0}]Kicked by EAC, reason:{1}", + "Message.BannedByEAC": "[{0}]Banned by EAC, reason:{1}", + "Message.NoticeByEAC": "[{0}]Detected:{1}", + "Message.TempBannedByEAC": "[{0}]Temporary Banned by EAC, reason:{1}", + "Message.TempBannedForSpamQuitting": "{0} was temporary banned because of spamming quits", + "Message.KickedByWhiteList": "{0} kicked because their friendcode was not found in WhiteList.txt", + "Message.SetLevel": "Your game level is set to: {0}", + "Message.SetColor": "Your color is set to: {0}", + "Message.SetName": "Your name is set to: {0}", + "Message.AllowLevelRange": "The game level can be set in the range: 0-100", + "Message.AllowNameLength": "Nickname can be set length: 1-10", + "Message.OnlyCanUseInLobby": "ERROR\n\nSorry, this command can only be used in the lobby", + "Message.CanNotUseInLobby": "ERROR\n\nSorry, this command cannot be used in the lobby", + "Message.CanNotUseByHost": "ERROR\n\nSorry, Host can't use this command", + "Message.TryFixName": "An attempt was made to fix hidden message content due to roles", + "Message.CanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.PlayerQuitForever": "{0} decided to leave voluntarily \nSorry for the bad gaming experience \nI really worked hard to make progress", + "Message.MadmateSelfVoteModeNotify": "Please note: The current Madness generation mode is [{0}]\n Voting for yourself means you want to be Madmate. If you meet the conditions to become Madmate and there are still spaces left, you will immediately become Madmate", + "Message.HostLeftGameInGame": "★Warning★ Host left the game, and the game wouldn't start normally next time. Please exit the lobby or wait until the new Host opens a lobby.", + "Message.HostLeftGameInLobby": "★Warning★ Host left the game, and the game wouldn't start normally next time. If the new Host has TOHE, you need to re-enter the lobby to play normally.", + "Message.HostLeftGameNewHostIsMod": "★Warning★ Original Host left the game and {0} become the new Host! \nThe room is still modded, start a game and end it immediately to reset the lobby!", + "Message.HostLeftGameNewHostIsNotMod": "★Warning★ Original Host left the game and {0} become the new Host. \nBut it's not modded. Please exit the lobby or wait until the new Host opens a lobby.", + "Message.LobbyShared": "The lobby has successfully been shared!", + "Message.LobbyShareFailed": "TOHE-Chan does not seem to be online (failed to share lobby)", + "Message.YTPlanDisabled": "ERROR\n\nPlease enable {0} in the Settings", + "Message.YTPlanSelected": "In the next game, your role will be {0}", + "Message.YTPlanSelectFailed": "You cannot be assigned as {0}.\nIt may be because you don't have this role enabled, or this role does not support being assigned.", + "Message.YTPlanCanNotFindRoleThePlayerEnter": "Could not find the role you searching\nUse command /r to show role list", + "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. If the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", + "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", + "Message.MaxPlayers": "Maximum players set to ", + "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", + "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", + "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", + "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", + "Message.MeCommandNoPermission": "You are not allowed to use /me command for others", + + "PollTitle": "〖 Poll 〗", + "PollResultTitle": "Poll Results", + "Poll.Result": "And... The winner was {0} with {1} votes!\n\nRunner ups:", + "Poll.Tied": "Uh oh, The vote was tied between {0}, all having {1} votes.", + "Poll.MissingPlayers": "You can't start a poll with yourself dummy ;3", + + "Poll.Begin": "You may vote using /pv {answer}, ps: a number also works.", + "Poll.TimeInfo": "The results will be final in 2 minuttes", + "Poll.OnlyInLobby": "<#ab4f75>Sorry, this command may only be used in lobby", + + "Poll.Inactive": "There isn't any active poll currently.", + "Poll.AlreadyVoted": "You already cast your vote, so it won't be counted.", + + "Poll.VotingInfo": "Use /pv {answer} to vote, answer can be a character or a number.", + "Poll.YouVoted": "You have voted for {0}, which has now {1} votes.", + "PollUsage": "To create a poll type \n/poll {Question}? {Answer A} {Answer B} \n{Answer C (Optional)} {Answer D (Optional)} {Answer E (Optional)}\nIt is important you end the question with a ? \n\nUse /poll Replay to replay the latest poll", + "Replay": "Replay", + + "EnableGadientTags": "Enable Gradient Tags (can cause disconnect issues)", + "Warning.GradientTags": "Warning:\n\nHost has enabled gradient tags. This feature is not recommended to use because it can cause disconnect issues", + "WarningTitle": "Warning!", + "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", + "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map, the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", + + "AntiBlackoutProtectionTitle": "Anti Blackout", + "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normally", + "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in the last meeting.\n", + "DisableAntiBlackoutProtects": "Disable AntiBlackout Protects (Recommended for testing)", + + + + "Warning.InvalidRpc": "Kicked {0} because an invalid RPC was received.\nPlease check that no mods other than TOHE are installed.", + "Warning.NoModHost": "TOHE is not installed on the host", + "Warning.MismatchedVersion": "{0} has a different version of {1}", + "Warning.AutoExitAtMismatchedVersion": "The host has no or a different version of {0}\nYou will be kicked in {1}", + "Warning.CanNotUseBepInExConsole": "The use of the console is prohibited\nso your console has been off", + "Error.MeetingException": "Error: {0}\r\nPlease use SHIFT+M+ENTER to end the meeting", + "Error.InvalidRoleAssignment": "Error: Invalid role found for a player during role assignment({0})", + "Error.InvalidColor": "Error: Only default colors are available", + "Error.InvalidColorPreventStart": "Other players are not allowed to use other colors. Otherwise, it will result in a serious error", + "ErrorLevel1": "Bugs may occur.", + "ErrorLevel2": "This may be a bug.", + "ErrorLevel3": "This version shouldn't have been released.", + "TerminateCommand": "Abort Command", + "ERR-000-000-0": "No Error", + "ERR-000-900-0": "Test Error Lv.0", + "ERR-000-910-1": "Test Error Lv.1", + "ERR-000-920-2": "Test Error Lv.2", + "ERR-000-930-3": "Test Error Lv.3", + "ERR-000-804-1": "Sorry, TOHE temporarily not support the Vanilla HnS, so mod unloaded", + "ERR-001-000-3": "Main dictionary has duplicated keys.", + "ERR-002-000-1": "Unsupported Among Us version. Please update Among Us", + "DefaultSystemMessageTitle": "SYSTEM MESSAGE", + "MessageFromTheHost": "HOST MESSAGE", + "MessageFromEAC": "EAC", + "DetectiveNoticeTitle": "INVESTIGATION", + "SleuthNoticeTitle": "SLEUTH", + "GuessKillTitle": "GUESSING INFO", + "CelebrityNewsTitle": "CELEBRITY", + "CyberNewsTitle": "CYBER", + "GodAliveTitle": "GOD ", + "WorkaholicAliveTitle": "WORKAHOLIC", + "BaitAliveTitle": "BAIT", + "MessageFromKPD": "KARPED1EM ", + "MessageFromSponsor": "SPONSOR MESSAGE ", + "MessageFromDev": "DEVELOPER MESSAGE ", + "FortuneTellerCheckMsgTitle": "FORTUNE TELLER", + "MimicMsgTitle": "MIMIC", + "MorticianCheckTitle": "CORPSE EXAMINATION", + "NemesisRevengeTitle": "NEMESIS", + "RetributionistRevengeTitle": "RETRIBUTIONIST", + "TabVanilla.GameSettings": "Game Settings", + "TabGroup.SystemSettings": "System Settings", + "TabGroup.ModSettings": "Mod Settings", + "TabGroup.ModifierSettings": "Game Modifiers", + "TabGroup.CrewmateRoles": "Crewmate Roles", + "TabGroup.NeutralRoles": "Neutral Roles", + "TabGroup.ImpostorRoles": "Impostor Roles", + "TabGroup.Addons": "Add-Ons", + "TabMenuDescription_General": "Here you can configure the functions that are in the mod", + "TabMenuDescription_Roles&AddOns": "Here you can add, remove and change the settings of all roles or add-ons in the mod", + "Experimental.Roles": "★ Experimental Roles (NOTICE: Use with caution, as these require testing)", + "ActiveRolesList": "Active Roles List", + "ForExample": "Example Use", + "updateButton": "Update", + "updatePleaseWait": "Please Wait...", + "updateManually": "Update failed.\nPlease try again or Update Manually.", + "updateInProgress": "Updating...", + "deletingFiles": "Deleting update files...", + "updateRestart": "Update Finished!\nPlease restart the game.", + "CanNotJoinPublicRoomNoLatest": "You can't join public rooms without the latest version.\nPlease Update.", + "ModBrokenMessage": "The MOD file is damaged.\nPlease reinstall.", + "UnsupportedVersion": "Unsupported Among Us version.\nPlease Update Among Us", + "DisabledByProgram": "The program has disabled public rooms", + "EnterVentToWin": "Enter Vent to Win!!", + "EatenByPelican": "You're swallowed, waiting for the Pelican to die or a meeting", + "FireworkerPutPhase": "{0} Fireworker Left", + "FireworkerWaitPhase": "Wait for it...", + "FireworkerReadyFirePhase": "Fire!", + "EnterVentWinCountDown": "Enter vent within {0} seconds to win!", + "On": "ON", + "Off": "OFF", + "ColoredOn": "ON", + "ColoredOff": "OFF", + "CurrentActiveSettingsHelp": "Current Active Settings Help", + "WitchCurrentMode": "Current Mode", + "WitchModeKill": "Kill", + "WitchModeSpell": "Spell", + "HexMasterModeHex": "Hex", + "HexMasterModeKill": "Kill", + "PoisonerPoisonButtonText": "Poison", + "WitchModeDouble": "Double Click = Kill, Single Click = Spell", + "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", + "BountyCurrentTarget": "Current Target", + "Roles": "Roles", + "Settings": "Settings", + "Addons": "Add-Ons", + "LastResult": "★ Match Results", + "LastEndReason": "★ End Reason", + "KillLog": "Kill Log", + "Maximum": "Max", + "RoleRate": "ON", + "RoleOn": "ALWAYS", + "RoleOff": "OFF", + "Chance0": "0%", + "Chance5": "5%", + "Chance10": "10%", + "Chance15": "15%", + "Chance20": "20%", + "Chance25": "25%", + "Chance30": "30%", + "Chance35": "35%", + "Chance40": "40%", + "Chance45": "45%", + "Chance50": "50%", + "Chance55": "55%", + "Chance60": "60%", + "Chance65": "65%", + "Chance70": "70%", + "Chance75": "75%", + "Chance80": "80%", + "Chance85": "85%", + "Chance90": "90%", + "Chance95": "95%", + "Chance100": "100%", + "Preset": "Preset", + "Preset_1": "Preset 1", + "Preset_2": "Preset 2", + "Preset_3": "Preset 3", + "Preset_4": "Preset 4", + "Preset_5": "Preset 5", + "Standard": "Standard", + "GameMode": "Game Mode", + "PressTabToNextPage": "Press Tab or Number for Next Page...", + "RoleSummaryText": "Role Summary:", + "doOverride": "Override %role%'s Tasks", + "assignCommonTasks": "%role% has Common Tasks", + "roleLongTasksNum": "Amount of Long Tasks for %role%", + "roleShortTasksNum": "Amount of Short Tasks for %role%", + "Format.Players": "{0}", + "Format.Seconds": "{0}s", + "Format.Percent": "{0}%", + "Format.Times": "{0}", + "Format.Multiplier": "{0}x", + "Format.Votes": "{0}", + "Format.Pieces": "{0}", + "Format.Health": "{0}", + "Format.Level": "{0}", + "KillButtonText": "Kill", + "ReportButtonText": "Report", + "VentButtonText": "Vent", + "SabotageButtonText": "Sabotage", + "SniperSnipeButtonText": "Snipe", + "FireworkerExplosionButtonText": "Detonate", + "FireworkerInstallAtionButtonText": "Install", + "MercenarySuicideButtonText": "Suicide Timer", + "WarlockCurseButtonText": "Curse", + "NinjaShapeshiftText": "Kill", + "NinjaMarkButtonText": "Mark", + "WitchSpellButtonText": "Spell", + "VampireBiteButtonText": "Bite", + "MinerTeleButtonText": "Warp", + "ArsonistDouseButtonText": "Douse", + "PuppeteerOperateButtonText": "Manipulate", + "WarlockShapeshiftButtonText": "Spell", + "BountyHunterChangeButtonText": "Swap", + "EvilTrackerChangeButtonText": "Track", + "InnocentButtonText": "Frame", + "PelicanButtonText": "Eat", + "DeceiverButtonText": "Cheat", + "PursuerButtonText": "Trick", + "GangsterButtonText": "Recruit", + "RevolutionistDrawButtonText": "Win over", + "HaterButtonText": "Hatred", + "MedicalerButtonText": "Protect", + "DemonButtonText": "Attack", + "SoulCatcherButtonText": "Teleport", + "LightningButtonText": "Evaporate", + "ProvocateurButtonText": "Greet", + "ButcherButtonText": "Dismember", + "BomberShapeshiftText": "Explode", + "QuickShooterShapeshiftText": "Keep", + "CamouflagerShapeshiftTextBeforeDisguise": "Disguise", + "CamouflagerShapeshiftTextAfterDisguise": "Duration", + "AnonymousShapeshiftText": "Hack", + "DefaultShapeshiftText": "Shift", + "CleanerReportButtonText": "Clean", + "SwooperVentButtonText": "Swoop", + "SwooperRevertVentButtonText": "Expose", + "WraithVentButtonText": "Vanish", + "WraithRevertVentButtonText": "Expose", + "VectorVentButtonText": "Hop", + "VeteranVentButtonText": "Alert", + "GrenadierVentButtonText": "Flash", + "MayorVentButtonText": "Button", + "SheriffKillButtonText": "Shoot", + "UndertakerButtonText": "Mark", + "ArsonistVentButtonText": "Ignite", + "RevolutionistVentButtonText": "Revolution", + "FollowerKillButtonText": "Follow", + "PacifistVentButtonText": "Reset", + "CultistKillButtonText": "Charm", + "InfectiousKillButtonText": "Infect", + "MonarchKillButtonText": "Knight", + "OverseerKillButtonText": "Reveal", + "DisabledBySettings": "Disabled by Settings", + "Disabled": "Disabled", + "FailToTrack": "Failed To Track", + "KillCount": "Kills: {0}", + "CantUse.lastroles": "Unable to use /lastroles during a game.", + "CantUse.killlog": "Unable to use /killlog during a game.", + "CantUse.lastresult": "Unable to use /lastresult during a game.", + "IllegalColor": "Please enter the correct color", + "DisableUseCommand": "The Host's settings do not allow this command to be used.", + "SureUse.quit": "We will kick you and block you from entering this lobby again. This setting is irreversible. If you really want it, please send the command /qt {0}", + "PlayerIdList": "List of player IDs: ", + "CancelStartCountDown": "The starting countdown was canceled", + "RestTOHESetting": "TOHE settings have been restored to default", + "FPSSetTo": "FPS Set To: {0}", + "HostKillSelfByCommand": "The lobby Host decided to commit suicide", + "SyncCustomSettingsRPC": "Synchronized RPC", + "Mode": "Mode", + "Target": "Target", + "PlayerInfo": "Player Info", + "NoInfoExists": "No Info Exists", + "PlayerLeftByAU-Anticheat": "{0} was banned by the Innersloth anti-cheat.", + "PlayerLeftByError": "Game will auto-end to prevent black screens.", + "MsgKickOtherPlatformPlayer": "{0} was kicked due to playing on {1}", + "KickBecauseLowLevel": "{0} was kicked because their level was too low", + "TempBannedBecauseLowLevel": "{0} was temporarily banned because their level was too low", + "KickBecauseDiffrentVersionOrMod": "{0} was kicked because they had a different version of the mod", + + "FFADisplayScore": "Ranking: {0} Score: {1}", + "FFATimeRemain": "Time Remaining: {0} second(s)", + + "GameOver": "Game Over", + "TOHEOptions": "TOHE Options", + "Cancel": "Cancel", + "Back": "Back", + "Yes": "Yes", + "No": "No", + + "AntiBlackOutLoggerSendInGame": "Because of an unknown error, the game will end to prevent a black screen.", + "AntiBlackOutNotifyInLobby": "An error occurred to prevent a black screen. Do a «/dump» and send the logs to the discord server TOHE in «bug-reports» and we will try to fix it.", + + "EndWhenPlayerBug": "End the game when a modded player gets a critical error (While loading)", + "AntiBlackOutRequestHostToForceEnd": "You were the reason for the black screen. The game will end", + "AntiBlackOutHostRejectForceEnd": "You were the reason for the black screen, and the host is not going to end the game\nYou will be disconnected soon", + + "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred. To prevent a black screen, turn off [{1}] in settings.", + "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, the game will end to prevent a black screen.", + "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", + + "NextPage": "Next Page", + "PreviousPage": "Previous Page", + "EAC.CheatDetected.EAC": "Cheating usage detected (Using AUM)", + "PressF1ShowMainRoleDes": "Press F1: Show Role Description", + "PressF2ShowAddRoleDes": "Press F2: Show Add-on Description", + "PressF3ShowRoleSettings": "Press F3: Show Role Settings", + "PressF4ShowAddOnsSettings": "Press F4: Show Add-ons Settings", + "FakeTask": "Fake Tasks:", + "PVP.ATK": "Attack", + "PVP.DF": "Defend", + "PVP.RCO": "Recover", + "SettingsAreLoading": "Loading\nsettings...", + "EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.", + "EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.", + "ExiledJester": "You're all fools!\n{0} the {1} laughing out loud tricked you into ejecting them.\nGG!", + "JesterMeetingLoose": "\r\nBut it cannot win until meeting number {0}", + "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", + "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", + "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", + "IsGood": "{0} was a good guy", + "BelongTo": "{0} belongs to {1}", + "PlayerIsRole": "{0} was The {1}", + "PlayerExiled": "{0} was ejected", + "NoImpRemain": "0 Impostors remain", + "OneImpRemain": "1 Impostor remains", + "TwoImpRemain": "2 Impostors remain", + "ThreeImpRemain": "3 Impostors remain", + "ImpRemain": "{0} Impostors remaining", + "NeutralRemain": "\n{0} Neutral Killers remain", + "OneNeutralRemain": "\n{0} Neutral Killer remains", + "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", + "GameOverReason.HumansByTask": "The Crewmates completed all tasks", + "GameOverReason.HumansDisconnect": "Crewmates disconnected", + "GameOverReason.ImpostorByVote": "The Crewmates were ejected", + "GameOverReason.ImpostorByKill": "The Impostors killed everyone", + "GameOverReason.ImpostorBySabotage": "Crewmates failed to fix a critical sabotage", + "GameOverReason.ImpostorDisconnect": "Impostors disconnected", + "FortuneTellerCheck.TaskDone": "[{0}]Role -[{1}]", + "DevAndSpnTitle": "TOHE family", + "FortuneTellerCheck.Null": "{0} is a role that is not listed.\nThis message should not appear normally.", + "FortuneTellerCheck.Result": "{0} is either one of the following roles:-\n{1}", + "SunnyboyChance": "Sunnyboy Chance", + "BardChance": "Bard Chance", + "SkeldChance": "Chance that the map is The Skeld", + "MiraChance": "Chance that the map is MIRA HQ", + "PolusChance": "Chance that the map is Polus", + "DleksChance": "Chance that the map is dlekS ehT", + "AirshipChance": "Chance that the map is Airship", + "FungleChance": "Chance that the map is The Fungle", + "UseMoreRandomMapSelection": "Use a more random map selection", + "CamouflageMode.Default": "Default", + "CamouflageMode.Host": "Host", + "CamouflageMode.Random": "Random", + "CamouflageMode.OnlyRandomColor": "Only Random Color", + "CamouflageMode.Karpe": "KARPED1EM", + "CamouflageMode.Lauryn": "Lauryn", + "CamouflageMode.Moe": "Moe", + "CamouflageMode.Pyro": "Pyro", + "CamouflageMode.ryuk": "ryuk", + "CamouflageMode.Gurge44": "Gurge44", + "CamouflageMode.TommyXL": "TommyXL", + "CamouflageMode.Sarha": "Sarha", + "DeathCmd.HeyPlayer": "Hey ", + "DeathCmd.YouAreRole": ", looks like you're the ", + "DeathCmd.NotDead": "You haven't died yet, this can only be used after you die\n\nCheck back again after you've been brutally murdered", + "DeathCmd.KillerName": "You were killed by ", + "DeathCmd.KillerRole": "Their role is ", + "DeathCmd.DeathReason": "Your cause of death was ", + "DeathCmd.YourName": "You are ", + "DeathCmd.YourRole": "Your role is ", + "DeathCmd.Ejected": "You were ejected during a meeting", + "DeathCmd.Misfired": "You misfired.", + "DeathCmd.Shrouded": "You were shrouded by a Shroud and didn't make a kill, so you suicided.", + "DeathCmd.Lovers": "Your lover had died.", + + "RpsCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rps X to play Rock Paper Scissors with the system. X can be 0 (rock), 1 (paper) or 2 (scissors). \n\nExample :- /rps 0", + "RpsDraw": "I choose {0}\n\nWow, what an intense battle of wits we just had! It's almost as if we're equally matched in this game of sheer luck and randomness.", + "RpsLose": "I choose {0}\n\nWell, well, well, looks like I've managed to outsmart a human again in this highly complex game of Rock, Paper, Scissors. I guess my unbeatable powers strike again! ", + "RpsWin": "I choose {0}\n\nOh, congratulations! You must have a crystal ball hidden behind that screen to beat me at Rock, Paper, Scissors. Or maybe I have the world's worst luck algorithm.", + + "CoinFlipCommandInfo": "This Command can only be used when in the lobby or after you die.", + "CoinFlipResult": "Drumroll, please... After an intense battle of gravity and randomness, the coin has decided to grace us with its presence! And the majestic winner is... (wait for it) ... the one and only... {0}! Who could have seen that coming?! Clearly, a momentous occasion in the history of coin flips.", + + "GNoCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /gno X to play guess a number. X can be a number between 0 and 99 (both included). \n\nYou get maximum of 7 tries to guess the number.\n\n Example:- /gno 10", + "GNoLost": "Oh, you were so close! Just one more guess: you might have deciphered the Da Vinci code! By the way, the secret number was... {0}! But hey, you were only off by a few billion possibilities. Better luck next time, Sherlock! ", + "GNoLow": "Oh, you're really nailing this! It's so low. I almost need a shovel to dig it up!\nYou have {0} guesses left!", + "GNoHigh": "Oh, absolutely! You're getting warmer. In fact, it's so high that I need a telescope to see it from here! \nYou have {0} guesses left!", + "GNoWon": "Oh, how did you ever figure that out? It's almost like you're a mind reader! Congratulations, you're a genius! You found the secret number with {0} guesses left!", + + "RandCommandInfo": "This Command can only be used when in the lobby or after you die.\n\ntype /rand X Y to get a number between X and Y, inclusive. \nX and Y can be any number between 0 and 2147483647, including both numbers.\nX must be less than Y.\n\nExample:- /rand 0 99", + "RandResult": "Congratulations, your random number is {0}! Wasn't that fun?", + + "8BallTitle": "The Magic 8 Ball Reveals...", + "8BallYes": "Yes", + "8BallNo": "No", + "8BallMaybe": "Maybe", + "8BallTryAgainLater": "Ask again later", + "8BallCertain": "It is certain", + "8BallNotLikely": "Outlook not so good", + "8BallLikely": "Outlook good", + "8BallDontCount": "Don't count on it", + "8BallStop": "Stop using an 8Ball in an Among Us mod", + "8BallPossibly": "Possibly", + "8BallProbably": "Probably", + "8BallProbablyNot": "Probably not", + "8BallBetterNotTell": "Better not tell you now", + "8BallCantPredict": "Cannot predict now", + "8BallWithoutDoubt": "Without a doubt", + "8BallWithDoubt": "Very doubtful", + + "ChanceToMiss": "Chance to miss a kill", + + "SoulCollectorPointsToWin": "Required number of souls", + "SoulCollectorTarget": "You have predicted the death of {0}", + "SoulCollectorTitle": "SOUL COLLECTOR", + "SoulCollector_CollectOwnSoulOpt": "Can collect their own soul", + "SoulCollectorSelfVote": "Host settings do not allow you to collect your own soul", + "SoulCollectorToDeath": "You have become Death!!!", + "SoulCollectorTransform": "Now Soul Collector has become Death, Destroyer of Worlds and Horseman of the Apocalypse!

Find them and vote them out before they bring forth Armageddon!", + "GetPassiveSouls": "Gain a passive soul every round", + "PassiveSoulGained": "You have gained a passive soul from the underworld.", + "SoulCollectorTargetUsed": "You've already targeted someone this round!", + "SoulCollectorSoulGained": "Soul gained", + "SoulCollectorCanVent": "Soul Collector can Vent", + "DeathMeetingTimeIncrease": "Increased Meeting time when Death exists", + "SoulCollectorMeetingDeath": "Your target has died during the meeting. You have gained a soul.", + "SoulCollectorKillButtonText": "Predict", + + "ApocalypseIsNigh": "[ The Apocalypse Is Nigh! ]", + "ApocalypseImmune": "This player is immune because they are invincible!", + "BakerToFamine": "You have become Famine!!!", + "BakerTransform": "The Baker has transformed into Famine, Horseman of the Apocalypse! A famine has begun!", + "BakerAlreadyBreaded": "That player already has bread!", + "BakerBreadUsedAlready": "You've already given a player bread this round!", + "BakerBreaded": "Player given bread", + "BakerBreadNeededToTransform": "Required number of bread to become Famine", + "BakerCantBreadApoc": "You cannot give other Apocalypse members bread!", + "BakerKillButtonText": "Bread", + "BakerRevealBread": "Reveal", + "BakerRoleblockBread": "Roleblock", + "BakerBarrierBread": "Barrier", + "BakerCurrentBread": "Current Bread: ", + "BakerSwitchBread": "Bread Switched to: ", + "BakerCanVent": "Baker can Vent", + "BakerBreadGivesEffects": "Bread gives additional effects", + "FamineKillButtonText": "Starve", + "FamineStarveCooldown": "Famine starve cooldown", + "FamineCantStarveApoc": "You cannot starve other Apocalypse members!", + "FamineAlreadyStarved": "That player has already been starved!", + "FamineStarved": "Player starved", + + "ChronomancerKillCooldown": "Ability Charge Time", + "ChronomancerDecreaseTime": "Slaughter Decrease Time (lower is faster)", + "ChronomancerStartMassacre": "SLAUGHTER: ACTIVATED", + "ChronomancerVisionMassacre": "Vision When In Slaughter", + + "ShamanButtonText": "Voodoo", + "ShamanTargetAlreadySelected": "You have already selected a voodoo doll in this round", + "Shaman_KillerCannotMurderChosenTarget": "The killer cannot murder chosen target", + "VoodooCooldown": "Voodoo Cooldown", + + "AdminWarning": "Admin Table in use!", + "VitalsWarning": "Vitals in use!", + "DoorlogWarning": "Doorlogs in use!", + "CameraWarning": "Cameras in use!", + "MinWaitAutoStart": "Minutes to wait before auto-starting", + "MaxWaitAutoStart": "Force start when Lobby Timer (in minutes) goes below", + "PlayerAutoStart": "Minimum Player Threshold to auto-start", + "AutoStartTimer": "Initial countdown for auto-starting", + "ImmediateAutoStart": "Immediately start the game when reaching certain conditions", + "ImmediateStartTimer": "Initial countdown for Immediate-starting", + "StartWhenPlayersReach": "Immediately Start when we have enough players above", + "StartWhenTimerLowerThan": "Immediately Start when Lobby Timer goes below", + "AutoPlayAgainCountdown": "Delay before re-entering lobby", + "AutoPlayAgain": "Auto Play Again", + "AutoRehost": "Auto Re-Host on Bad Disconnect", + "CountdownText": "Rejoining lobby in {0}s", + "TimeMasterSkillDuration": "Time Shield Duration", + "TimeMasterSkillCooldown": "Time Shield Cooldown", + "TimeMasterOnGuard": "Time Shield is active!", + "TimeMasterSkillStop": "Time Shield has ended!", + "TimeMasterVentButtonText": "Time Shield", + "BodyCannotBeReported": "Body could not be reported", + "BurstKillDelay": "Burst Kill Delay", + "BurstNotify": "That was a Burst! Get in a vent or die.", + "ImpCanBeBurst": "Impostors can become Burst", + "CrewCanBeBurst": "Crewmates can become Burst", + "NeutralCanBeBurst": "Neutrals can become Burst", + "BurstFailed": "Burst failed to bomb you", + "ShroudButtonText": "Shroud", + "ShroudCooldown": "Shroud Cooldown", + "Message.Shrouded": "One or more players were shrouded by a Shroud!\n\nGet rid of the Shroud or all shrouded players will suicide!", + "LudopathRandomKillCD": "Maximum kill cooldown", + "UnderdogMaximumPlayersNeededToKill": "Maximum players needed to start killing", + "GodfatherTargetCountMode": "Killer turns into", + "GodfatherCount_Refugee": "Refugee", + "GodfatherCount_Madmate": "Madmate", + "MissChance": "Chance To Miss", + "IncreaseByOneIfConvert": "Increase The KillCount +1 If a Crew Is Converted", + "HawkMissed": "Missed!", + "HawkCanKillNum": "Max Slices", + "HawkKillMax": "You've run out of ability uses", + "HawkKillTooManyDead": "Too many people are dead", + "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", + "BloodMoonCanKillNum": "Max BloodLettings", + "BloodMoonTimeTilDie": "Time Until Death", + "DeathTimer": "Death In: {DeathTimer}s", + "BerserkerKillCooldown": "Berserker kill cooldown", + "BerserkerMax": "Max level that Berserker can reach", + "BerserkerHasImpostorVision": "Berserker Has Impostor Vision", + "WarHasImpostorVision": "War Has Impostor Vision", + "BerserkerCanVent": "Berserker Can Vent", + "WarCanVent": "War Can Vent", + "BerserkerOneCanKillCooldown": "Unlock lower kill cooldown", + "BerserkerOneKillCooldown": "Kill cooldown after unlocking", + "BerserkerTwoCanScavenger": "Unlock scavenged kills", + "BerserkerThreeCanBomber": "Unlock bombed kills", + "BerserkerFourCanNotKill": "Become War", + "BerserkerMaxReached": "Maximum level reached!", + "BerserkerLevelChanged": "Increased level to {0}", + "BerserkerLevelRequirement": "Level requirement for unlock", + "KilledByBerserker": "Killed by Berserker", + "BerserkerToWar": "You have become War!!!", + "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", + "WarKillCooldown": "War kill cooldown", + + "ImpCanBeUnlucky": "Impostors can become Unlucky", + "CrewCanBeUnlucky": "Crewmates can become Unlucky", + "NeutralCanBeUnlucky": "Neutrals can become Unlucky", + "BlackmailerSkillCooldown": "Blackmail Cooldown", + "BlackmailerMax": "Maximum times blackmailed players may speak", + "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", + "BlackmaileKillTitle": "BLACKMAILER", + "UnluckyTaskSuicideChance": "Chance to suicide from doing tasks", + "UnluckyKillSuicideChance": "Chance to suicide from killing", + "UnluckyVentSuicideChance": "Chance to suicide from venting", + "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", + "UnluckyOpenDoorSuicideChance": "Chance to suicide from opening a door", + "ImpCanBeVoidBallot": "Impostors can become VoidBallot", + "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", + "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", + "ImpCanBeAware": "Impostors can become Aware", + "NeutralCanBeAware": "Neutrals can become Aware", + "CrewCanBeAware": "Crewmates can become Aware", + "AwareKnowRole": "Knows the role of the player", + "AwareInteracted": "{0} tried to reveal your role.", + "AwareTitle": "AWARE MESSAGE", + "LighterVentButtonText": "Light", + "LighterSkillCooldown": "Light Cooldown", + "LighterSkillDuration": "Light Duration", + "LighterVisionNormal": "Increased Vision", + "LighterVisionOnLightsOut": "Increased Vision During Lights Out", + "LighterSkillInUse": "Ability in use", + "LighterSkillStop": "Ability expired", + "StealthDarkened": "Darkened: {0}", + "StealthExcludeImpostors": "Ignore Impostors when Blinding", + "StealthDarkenDuration": "Blinding Duration", + "PenguinAbductTimerLimit": "Dragging Time", + "PenguinMeetingKill": "Kill the target if a meeting starts during dragging", + "PenguinKillButtonText": "Drag", + "PenguinTimerText": "Drag Timer", + "PenguinTargetOnCheckMurder": "You are grabbed. Try to escape that first!", + "WitnessTime": "Max Time after killing where killer appears red", + "WitnessButtonText": "Examine", + "WitnessFoundInnocent": "✓", + "WitnessFoundKiller": "⚠", + "SwapperMax": "Maximum swaps", + "CanSwapSelfVotes": "Can exchange your own votes.", + "SwapperTrialMax": "You've reached the maximum amount of swaps!\nYou can't swap votes anymore.", + "CantSwapSelf": "Can't exchange of one's own vote", + "SwapVote": "The votes of {0} and {1} were swapped!", + "SwapDead": "Sorry, you can't swap votes after death.", + "SwapNull": "Please choose the ID of a living player to swap votes with. Use 253 to clear swaps", + "SwapHelp": "Command Format: /sw [playerID] to select the target\nYou can see the player IDs next to the player names or use /id to see the player ID list.\nUse /swap 253 to clear your previous swap", + "Swap1": "Swap target 1 selected", + "Swap2": "Swap target 2 selected", + "CancelSwap": "Cleared your previous swap!", + "CancelSwapDueToTarget": "Cleared your previous swap because one or more of your targets is dead.", + "Swap1=Swap2": "The target you input is the same as Swap target 1.\nPls input a different one", + "SwapTitle": "SWAPPER", + "SwapperTryHideMsg": "Try to hide Swapper's command", + "SwapperPreResult": "Currently, you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", + "ImpCanBeFragile": "Impostors can become Fragile", + "NeutralCanBeFragile": "Neutrals can become Fragile", + "CrewCanBeFragile": "Crewmates can become Fragile", + "ImpCanKillFragile": "Impostors can force kill Fragile", + "NeutralCanKillFragile": "Neutrals can force kill Fragile", + "CrewCanKillFragile": "Crewmates can force kill Fragile", + "FragileKillerLunge": "Killer lunges on kill", + "CrusaderSkillLimit": "Maximum Crusades", + "CrusaderSkillCooldown": "Crusade Cooldown", + "CrusaderKillButtonText": "Crusade", + "JailorKillButtonText": "Jail", + "AgitaterKillButtonText": "Pass", + "HasSerialKillerBuddy": "Has Serial Killer buddy", + "ChanceToSpawn": "Chance to spawn", + "ChanceToSpawnAnother": "Chance to spawn another", + "BloodthirstKillCD": "Bloodthirst Kill Cooldown", + "BloodthirstPlayerCount": "Max players alive for Bloodthirst", + "ReflectHarmfulInteractions": "Reflect harmful interactions", + + "ImpCanBeDiseased": "Impostors can become Diseased", + "NeutralCanBeDiseased": "Neutrals can become Diseased", + "CrewCanBeDiseased": "Crewmates can become Diseased", + "DiseasedCDOpt": "Increase the cooldown by", + "DiseasedCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeAntidote": "Impostors can become Antidote", + "NeutralCanBeAntidote": "Neutrals can become Antidote", + "CrewCanBeAntidote": "Crewmates can become Antidote", + "AntidoteCDOpt": "Decrease the cooldown by", + "AntidoteCDReset": "Cooldown returns to normal after a meeting", + + "ImpCanBeRadar": "Impostors can become Radar", + "NeutralCanBeRadar": "Neutrals can become Radar", + "CrewCanBeRadar": "Crewmates can become Radar", + + "ImpCanBeGlow": "Impostors can become Glow", + "NeutralCanBeGlow": "Neutrals can become Glow", + "CrewCanBeGlow": "Crewmates can become Glow", + "GlowRadius": "Glow Radius", + "GlowVisionOthers": "Vision Boost for nearby Players", + "GlowVisionSelf": "Vision Boost for Glow", + + "ImpCanBeStubborn": "Impostors can become Stubborn", + "NeutralCanBeStubborn": "Neutrals can become Stubborn", + "CrewCanBeStubborn": "Crewmates can become Stubborn", + + "ImpCanBeAvanger": "Impostors can become Avenger", + "NeutralCanBeAvanger": "Neutrals can become Avenger", + "CrewCanBeAvanger": "Crewmates can become Avenger", + "ImpCanBeSleuth": "Impostors can become Sleuth", + "CrewCanBeSleuth": "Crewmates can become Sleuth", + "NeutralCanBeSleuth": "Neutrals can become Sleuth", + "SleuthCanKnowKillerRole": "Can find the role of the killer", + "SleuthNoticeKiller": "\nThe killer's role is {0}.", + "SleuthNoticeVictim": "{0}'s role is {1}.", + "SleuthNoticeKillerNotFound": "\nThe killer could not be identified, this was possibly a suicide.", + "BomberDiesInExplosion": "Bomber dies in their explosion", + "ImpostorsSurviveBombs": "Impostors survive bombs", + + "PunchingBagKillMax": "Amount of attacks needed to win", + "GuessPunchingBag": "You just tried to guess a Punching Bag!\nThey're now one step closer to winning!", + "GuessPunchingBagAgain": "You just tried to guess a Punching Bag again!\n\nIt no longer counts your attacks by guessing", + "PunchingBagKill": "You were attacked!", + "SelfGuessPunchingBag": "You can't self-guess as a Punching Bag, you cheater!", + "GuessPunchingBagBlocked": "Punching Bag cannot guess due to self-guessing.", + "EradicatePunchingBag": "You just tried to terminate punching bag, that is not allowed.", + + "RememberCooldown": "Imitate Cooldown", + "RefugeeKillCD": "Refugee's Kill Cooldown", + "RememberedNeutralKiller": "You remembered you were a neutral killer!", + "RememberedMaverick": "You remembered you were a Maverick!", + "RememberedPursuer": "You remembered you were a Pursuer!", + "RememberedFollower": "You remembered you were a Follower!", + "RememberedAmnesiac": "You failed to remember your role.", + "RememberedImitator": "You remembered you were an Imitator.", + "RememberedImpostor": "You remembered you were an Impostor!", + "RememberedCrewmate": "You remembered you were a crewmate!", + "ImitatorImitated": "An Imitator imitated your role!", + "ImitatorInvalidTarget": "Imitation failed", + "RememberButtonText": "Remember", + "ImitatorKillButtonText": "Imitate", + "IncompatibleNeutralMode": "If neutral is incompatible, turn into", + "RememberedYourRole": "An Amnesiac remembered your role!", + "YouRememberedRole": "You remembered who you were!", + + "BanditStealMode": "Steal Mode", + "BanditStealMode_OnMeeting": "On Meeting", + "BanditStealMode_Instantly": "Instantly", + "BanditMaxSteals": "Maximum Steals", + "BanditCanStealBetrayalAddon": "Can Steal Betrayal Add-ons", + "BanditCanStealImpOnlyAddon": "Can Steal Impostor Only Addons", + "Bandit_NoStealableAddons": "Could not steal add-on from the player", + "BanditStealCooldown": "Steal cooldown", + + "DoppelMaxSteals": "Maximum Steals", + "DoppelCurrentVictimCanSeeRolesAsDead": "Last victim can see role and add-on info of alive players as a ghost", + + "NecromancerRevengeTime": "Necromancy time", + "NecromancerRevenge": "You have {0}s to kill {1}", + "NecromancerSuccess": "Necromancy complete! You live to see another day.", + "NecromancerHide": "Venting is disabled, hide from the Necromancer!", + "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", + "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", + "RetributionistKillMax": "You've reached the maximum amount of kills. You can't kill anymore!", + "RetributionistKillDead": "Choose a living player to kill.", + "RetributionistKillSucceed": "{0} was killed by the Retributionist!", + "RetributionistKillDisable": "You can't retribute until your tasks are done.", + "CanOnlyRetributeWithTasksDone": "Can only retribute on task completion", + "RetributionistCanKillNum": "Max retributions", + "RetributionistKillTooManyDead": "Too many players are dead. You can't retribute.", + "MinimumPlayersAliveToRetri": "Minimum players alive to retribute", + "MinimumNoKillerEjectsToKill": "Minimum meetings passed with no killer ejects to kill", + "ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion", + + "TwisterCooldown": "Twist Cooldown", + "TwisterButtonText": "Twist", + "TwisterHideTwistedPlayerNames": "Hide who the players swap places with", + "InstigatorAbilityLimit": "Ability Use Count", + "InstigatorKillsPerAbilityUse": "Kills per Ability use", + + "CrewCanFindCaptain": "Crewmates can find Captain", + "MadmateCanFindCaptain": "Madmates can find Captain", + "ReducedSpeed": "Reduced speed", + "ReducedSpeedTime": "Time duration for reduced speed", + "CaptainCanTargetNB": "Captain can target Neutral Benign", + "CaptainCanTargetNE": "Captain can target Neutral Evil", + "CaptainCanTargetNC": "Captain can target Neutral Chaos", + "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", + "CaptainCanTargetNK": "Captain can target Neutral Killer", + "CaptainSpeedReduced": "Captain reduced your speed", + "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", + "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", + + "InspectorTryHideMsg": "Hide Inspector's commands", + "MaxInspectCheckLimit": "Max inspections per game", + "InspectCheckLimitPerMeeting": "Max inspections per meeting", + "InspectCheckTargetKnow": "Targets know they were checked by Inspector", + "InspectCheckOtherTargetKnow": "Targets know who they were checked with", + "InspectorDead": "You can not use your power after death", + "InspectCheckMax": "Max inspections per game reached!\nYou can not use your power anymore.", + "InspectCheckRound": "Max inspections per round reached!\nYou can check again in the next round.", + "InspectCheckSelf": "HA!! You thought it would be this easy. You can not check yourself", + "InspectCheckReveal": "HA! You thought it would be this easy. You can not check a role that is revealed", + "InspectCheckTitle": "INSPECTOR ", + "InspectCheckTrue": "{0} and {1} are in the same team!", + "InspectCheckFalse": "{0} and {1} are NOT in the same team!", + "InspectCheckTargetMsg": " were checked by Inspector.", + "InspectCheckHelp": "Instructions: /cmp [Player ID 1] [Player ID 2] \nExample: /cmp 1 5 \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", + "InspectCheckNull": "Please select an ID of a living player to check their team", + "InspectCheckBaitCountMode": "Bait counts as revealing role if Bait reveal on first meeting is on", + "InspectCheckRevealTarget": "When tasks are done, the target knows the team of the other target", + "InspectorTargetReveal": " Looks like {0} is aligned with team {1}", + + "EgoistCountMode.Original": "Original", + "EgoistCountMode.Neutral": "Neutral", + + "JailerJailCooldown": "Jail cooldown", + "JailerMaxExecution": "Maximum executions", + "JailerNBCanBeExe": "Can execute Neutral Benign", + "JailerNCCanBeExe": "Can execute Neutral Chaos", + "JailerNECanBeExe": "Can execute Neutral Evil", + "JailerNKCanBeExe": "Can execute Neutral Killing", + "JailerNACanBeExe": "Can execute Neutral Apocalypse", + "JailerCKCanBeExe": "Can execute Crew Killing", + "JailerTargetAlreadySelected": "You have already selected a target", + "SuccessfullyJailed": "Target successfully jailed", + "CantGuessJailed": "You can not guess the target", + "JailedCanOnlyGuessJailer": "You have been jailed. You can only guess Jailer.", + "CanNotTrialJailed": "You can not trial the target.", + "notifyJailedOnMeeting": "Notify jailed player when a meeting starts", + "JailedNotifyMsg": "The Jailer has jailed you. No one can guess or judge you. You can only guess The Jailer.\n\nIf Jailer votes you, you will be executed after the meeting ends.", + "JailerTitle": "Jailer", + + "CopyCatCopyCooldown": "Copy cooldown", + "CopyCatRoleChange": "Your role has been changed to {0}", + "CopyCatCanNotCopy": "You can not copy the target's role", + "CopyButtonText": "Copy", + "CopyCrewVar": "Can copy evil variants of crew roles", + "CopyTeamChangingAddon": "Can copy team changing add-on", + + "MaxCleanserUses": "Max cleanses", + "CleansedCanGetAddon": "Cleansed player can get Add-on", + "CleanserTitle": "CLEANSER", + "CleanserRemoveSelf": "You can not cleanse yourself", + "CleanserCantRemove": "Oops! the player can not be cleansed.", + "CleanserRemovedRole": "{0} has been cleansed. All their Add-ons will be removed after the meeting.", + "LostAddonByCleanser": "The cleanser removed all your Add-ons", + + "MaxProtections": "Max protections", + "KeeperHideVote": "Hide Keeper's vote", + "KeeperProtect": "You chose to protect {0}, your vote has been returned", + "KeeperTitle": "Keeper", + + "MaulRadius": "Maul Radius", + "ImpCanBeAutopsy": "Impostors can become Autopsy", + "CrewCanBeAutopsy": "Crewmates can become Autopsy", + "NeutralCanBeAutopsy": "Neutrals can become Autopsy", + "ImpCanBeCyber": "Impostors can become Cyber", + "CrewCanBeCyber": "Crewmates can become Cyber", + "NeutralCanBeCyber": "Neutrals can become Cyber", + "ImpKnowCyberDead": "Impostors know if Cyber died", + "CrewKnowCyberDead": "Crewmates know if Cyber died", + "NeutralKnowCyberDead": "Neutrals know if Cyber died", + "CyberKnown": "Everyone can see Cyber", + "ImpCanBeInfluenced": "Impostors can become Influenced", + "CrewCanBeInfluenced": "Crewmates can become Influenced", + "NeutralCanBeInfluenced": "Neutrals can become Influenced", + "ImpCanBeBewilder": "Impostors can become Bewilder", + "CrewCanBeBewilder": "Crewmates can become Bewilder", + "NeutralCanBeBewilder": "Neutrals can become Bewilder", + "KillerGetBewilderVision": "Killer gets Bewilder's vision", + "ImpCanBeOiiai": "Impostors can be OIIAI", + "CrewCanBeOiiai": "Crewmates can be OIIAI", + "NeutralCanBeOiiai": "Neutrals can be OIIAI", + "OiiaiCanPassOn": "OIIAI can pass on to the killer", + "NeutralChangeRolesForOiiai": "Neutrals turns to ", + "LostRoleByOiiai": "You got erased by OIIAI!", + "ImpCanBeLoyal": "Impostors can become Loyal", + "CrewCanBeLoyal": "Crewmates can become Loyal", + "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", + "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", + "SheriffCanBeMadmate": "Sheriff can become Madmate", + "MayorCanBeMadmate": "Mayor can become Madmate", + "NGuesserCanBeMadmate": "Nice Guesser can become Madmate", + "SnitchCanBeMadmate": "Snitch can become Madmate", + "JudgeCanBeMadmate": "Judge can become Madmate", + "MarshallCanBeMadmate": "Marshall can become Madmate", + "GanRetributionistCanBeMadmate": "Retributionist can be converted", + "RetributionistCanBeMadmate": "Retributionist can become Madmate", + "OverseerCanBeMadmate": "Overseer can become Madmate", + "GanSheriffCanBeMadmate": "Sheriff can be converted", + "GanMayorCanBeMadmate": "Mayor can be converted", + "GanNGuesserCanBeMadmate": "Nice Guesser can be converted", + "GanJudgeCanBeMadmate": "Judge can be converted", + "GanMarshallCanBeMadmate": "Marshall can be converted", + "GanOverseerCanBeMadmate": "Overseer can be converted", + "RascalAppearAsMadmate": "Appear As Madmate On Ejection", + + "CouncillorDead": "Sorry, you can't murder from the dead.", + "CouncillorMurderMaxMeeting": "Sorry, you've reached the maximum amount of murders for the meeting.", + "CouncillorMurderMaxGame": "Sorry, you've reached the maximum amount of murders for the game.", + "Councillor_LaughToWhoMurderSelf": "Hahaha, who would've thought someone was stupid enough to murder themselves?\n\nGuess it happens to be... YOU!", + "Councillor_MurderKill": "{0} was murdered.", + "Councillor_MurderHelp": "Command: /tl [player ID]\nYou can see the players' IDs before the players' names.\nOr use /id to view the list of all player IDs.", + "Councillor_MurderNull": "Please choose a living player to murder.", + "Councillor_MurderKillTitle": "WICKED COURT ", + "CouncillorMakeEvilJudgeClear": "Show Trial as Councillor Murder", + "Councillor_CannotMurderImpTeam": "Sorry, you can not murder your teammate.", + "Councillor_SuicideForMurderImps": "You died because you are trying to murder your team members.", + "CouncillorMurderLimitPerMeeting": "Maximum Kills Per Meeting", + "CouncillorMurderLimitPerGame": "Maximum Kills Per Game", + "CouncillorCanMurderMadmate": "Can Murder Madmates", + "CouncillorCanMurderImpostor": "Can Murder Impostors", + "CouncillorSuicideOnJudgeImpTeam": "Suicide when judge Impostors Team Wrongly", + "CouncillorCanMurderTaskDoneSnitch": "Can Murder Snitch with All Tasks Done", + "CouncillorTryHideMsg": "Try to hide Councillor's commands", + + "DazzlerDazzled": "You were dazzled by the Dazzler!", + "DazzlerCauseVision": "Reduced vision", + "DazzlerDazzleLimit": "Max number of players affected by reduced vision", + "DazzlerResetDazzledVisionOnDeath": "Reset vision of dazzled players on death/eject", + "DazzleCooldown": "Dazzle Cooldown", + "DazzleButtonText": "Dazzle", + + "MoleVentButtonText": "Dig", + "MoleVentCooldown": "Dig cooldown", + + "AddictVentButtonText": "Get Fix", + "AddictInvulnerbilityTimeAfterVent": "Invulnerability Time", + "AddictSpeedWhileInvulnerble": "Movement speed while Invulnerable", + + "AddictFreezeTimeAfterInvulnerbility": "Time the Addict gets frozen in place after Invulnerability", + "AlchemistShieldDur": "Resistance Potion Duration", + "AlchemistInvisDur": "Invisibility Potion Duration", + "AlchemistVision": "Night Vision", + "AlchemistVisionOnLightsOut": "Night Vision During Lights Sabotage", + "AlchemistVisionDur": "Night Vision Potion Duration", + "AlchemistSpeed": "Speed Potion Boost", + "AlchemistVentButtonText": "Drink", + "AlchemistGotShieldPotion": "Potion of Resistance: Grants a temporary shield", + "AlchemistGotSightPotion": "Potion of Night Vision: Gives temporary enhanced vision", + "AlchemistGotQFPotion": "Potion of Fixing: Allows you to fix one sabotage instantly", + "AlchemistGotTPPotion": "Potion of Warping: Teleports you to a random player", + "AlchemistGotSuicidePotion": "Potion of Poison: Poisons you", + "AlchemistGotSpeedPotion": "Potion Of Speed: Hastens you", + "AlchemistGotBloodthirstPotion": "Potion of Harming: Kill the next player you touch", + "AlchemistGotInvisibility": "Potion of Invisibility: Become Invisible", + "NoPotion": "You have no potions", + + "StoreShield": "Potion of Resistance", + "StoreSuicide": "Potion of Poison", + "StoreTP": "Potion of Warping", + "StoreSP": "Potion Of Speed", + "StoreQF": "Potion of Fixing", + "StoreBL": "Potion of Harming", + "StoreNS": "Potion of Night Vision", + "StoreINV": "Potion of Invisibility", + "StoreNull": "None", + "PotionStore": "Potion in store: ", + "WaitQFPotion": "\nPotion of Fixing waiting for use", + + "AlchemistShielded": "Potion of Resistance started", + "AlchemistHasVision": "Potion of Night Vision started", + "AlchemistShieldOut": "Potion of Resistance ended", + "AlchemistVisionOut": "Potion of Night Vision ended", + "AlchemistPotionBloodthirst": "You gained bloodthirst", + "AlchemistHasSpeed": "Potion Of Speed started", + "AlchemistSpeedOut": "Potion Of Speed ended", + + "DeathpactDuration": "Death Pact duration", + "DeathPactCooldown": "Death Pact Assign Cooldown", + "DeathpactNumberOfPlayersInPact": "Number of players in Death Pact", + "DeathpactShowArrowsToOtherPlayersInPact": "Show arrows leading to other players in Death Pact", + "DeathpactReduceVisionWhileInPact": "Reduce vision for players in Death Pact", + "DeathpactVisionWhileInPact": "Vision for players in Death Pact", + "DeathpactKillPlayersInDeathpactOnMeeting": "Kill players in Death Pact on meeting", + "DeathpactPlayersInDeathpactCanCallMeeting": "Players in active Death Pact can call meeting", + "DeathpactActiveDeathpact": "Find {0} in {1} seconds.", + "DeathpactCouldNotAddTarget": "Target can't be added to Death Pact.", + "DeathpactComplete": "Death Pact was concluded.", + "DeathpactExecuted": "Death Pact was executed.", + "DeathpactAverted": "Death Pact was averted.", + "DeathpactButtonText": "Assign", + "DevourerHideNameConsumed": "Hide the names of consumed players", + "DevourCooldown": "Devour Cooldown", + "DevourerButtonText": "Devour", + "DollMasterPossessionButtonText": "Possess", + "DollMasterUnPossessionButtonText": "UnPossess", + "DollMaster_PossessedTarget": "Possessed target", + "DollMaster_CannotPossessImpTeammate": "Unable to possess teammate", + "DollMaster_CouldNotSwapWithTarget": "Unable to possess player", + "DollMaster_CanNotSwapWithDeadTarget": "Possesing a dead player isn't possible", + "DollMaster_MainBody": "Main Body", + "DollMaster_Doll": "Doll", + "DollMaster_UnableToUseAbility": "Unable to use your ability on player", + "Doppelganger_RoleInfo": "Spoofed Role: {0}", + "EatenByDevourer": "The Devourer ate your skin", + "DevourerEatenSkin": "Target skin is eaten", + "DevouredName": "Devoured", + "PitfallTrapCooldown": "Trap Cooldown", + "PitfallMaxTrapCount": "Number of Traps that can be set", + "PitfallTrapMaxPlayerCount": "Number of Players that can be caught per Trap", + "PitfallTrapDuration": "Time the Trap remains active", + "PitfallTrapRadius": "Trap Radius", + "PitfallTrapFreezeTime": "Trap freeze time", + "PitfallTrapCauseVision": "Trap caused vision", + "PitfallTrapCauseVisionTime": "Trap caused vision time", + "PitfallTrap": "You have fallen into a trap!", + "ConsigliereDivinationMaxCount": "Maximum Reveals", + "RitualMaxCount": "Maximum Reveals", + "CleanserHideVote": "Hide Cleanser's vote", + "OracleSkillLimit": "Maximum Uses", + "OracleHideVote": "Hide Oracle's vote", + "OracleCheckReachLimit": "You're out of uses!", + "OracleCheckSelfMsg": "You can't even trust yourself, huh?", + "OracleCheckLimit": "Reminder: You have {0} uses left", + "OracleCheckMsgTitle": "ORACLE ", + "OracleCheck.NotCrewmate": "Appears not to be a crewmate", + "OracleCheck.Crewmate": "Appears to be a crewmate", + "OracleCheck.Neutral": "Appears to be a neutral", + "OracleCheck.Impostor": "Appears to be an Impostor", + "OracleCheck": "Target Results:", + "FailChance": "Chance of showing incorrect result", + "OracleCheckAddons": "Oracle checks add-ons", + "ChameleonCanVent": "Vent to disguise", + "ChameleonInvisState": "You are disguising!", + "ChameleonInvisStateOut": "Your disguise ended", + "ChameleonInvisInCooldown": "Ability still on cooldown, disguise failed", + "ChameleonInvisStateCountdown": "Disguise will expire in {0}s", + "ChameleonInvisCooldownRemain": "Disguise Cooldown: {0}s", + "ChameleonCooldown": "Disguise Cooldown", + "ChameleonDuration": "Disguise Duration", + "ChameleonRevertDisguise": "Expose", + "ChameleonDisguise": "Disguise", + "KillCooldownAfterCleaning": "Kill Cooldown On Clean", + "KillCooldownAfterStoneGazing": "Kill Cooldown On Stone Gaze", + "MedusaStoneBody": "Body stoned", + "MedusaReportButtonText": "Stone", + + "CursedSoulCurseCooldown": "Soul Snatch Cooldown", + "CursedSoulCurseCooldownIncrese": "Soul Snatch Cooldown Increase", + "CursedSoulCurseMax": "Maximum Soul Snatches", + "CursedSoulKnowTargetRole": "Know the roles of Soulless players", + "CursedSoulCanCurseNeutral": "Neutral roles have souls", + "CursedSoulKillButtonText": "Snatch", + "SoullessByCursedSoul": "A Cursed Soul snatched your soul", + "CursedSoulSoullessPlayer": "Soul snatched", + "CursedSoulInvalidTarget": "No soul found", + + "AdmireCooldown": "Admire Cooldown", + "AdmirerKnowTargetRole": "Know the roles of Admired players", + "AdmirerSkillLimit": "Skill Limit", + "AdmireButtonText": "Admire", + "AdmirerAdmired": "The Admirer admired you!", + "AdmiredPlayer": "Player admired", + "AdmirerInvalidTarget": "Target cannot be admired", + + "SpiritualistNoticeTitle": "SPIRITUALIST ", + "SpiritualistNoticeMessage": "The Spiritualist has an arrow pointing to you!\nYou can use them to a killer or frame a crewmate", + "SpiritualistShowGhostArrowForSeconds": "Ghost arrow duration", + "SpiritualistShowGhostArrowEverySeconds": "Ghost arrow interval", + "EnigmaClueStage1Tasks": "Number of Tasks to complete to see Stage 1 Clues", + "EnigmaClueStage2Tasks": "Number of Tasks to complete to see Stage 2 Clues", + "EnigmaClueStage3Tasks": "Number of Tasks to complete to see Stage 3 Clues", + "EnigmaClueStage2Probability": "Probability to see Stage 2 Clues", + "EnigmaClueStage3Probability": "Probability to see Stage 3 Clues", + "EnigmaClueGetCluesWithoutReporting": "Enigma can get Clues without reporting a dead body", + "EnigmaClueHat1": "The Killer wears a Hat!", + "EnigmaClueHat2": "The Killer does not wear a Hat!", + "EnigmaClueHat3": "The Killer wears {0} as a Hat!", + "EnigmaClueSkin1": "The Killer wears a Skin!", + "EnigmaClueSkin2": "The Killer does not wear a Skin!", + "EnigmaClueSkin3": "The Killer wears {0} as a Skin!", + "EnigmaClueVisor1": "The Killer wears a Visor!", + "EnigmaClueVisor2": "The Killer does not wear a Visor!", + "EnigmaClueVisor3": "The Killer wears {0} as a Visor!", + "EnigmaCluePet1": "The Killer does have a Pet!", + "EnigmaCluePet2": "The Killer does not have a Pet!", + "EnigmaCluePet3": "The Killer has {0} as a Pet!", + "EnigmaClueName1": "The Name of the Killer contains the letter {0} or the letter {1}!", + "EnigmaClueName2": "The Name of the Killer contains the letter {0}!", + "EnigmaClueName3": "The Name of the Killer contains the letter {0} and the letter {1}!", + "EnigmaClueNameLength1": "The Name of the Killer has a Length between {0} and {1} letters!", + "EnigmaClueNameLength2": "The Name of the Killer has a Length of {0} letters!", + "EnigmaClueColor1": "The Killer has a light color!", + "EnigmaClueColor2": "The Killer has a dark color!", + "EnigmaClueColor3": "The Killer's color is {0}!", + "EnigmaClueLocation": "The Last Room the Killer was in is {0}!", + "EnigmaClueStatus1": "The Killer is currently inside a Vent!", + "EnigmaClueStatus2": "The Killer is currently on a Ladder!", + "EnigmaClueStatus3": "The Killer is already Dead!", + "EnigmaClueStatus4": "The Killer is still Alive!", + "EnigmaClueRole1": "The Killer is an Impostor!", + "EnigmaClueRole2": "The Killer is a Neutral!", + "EnigmaClueRole3": "The Killer is a Crewmate!", + "EnigmaClueRole4": "The Killer's Role is {0}!", + "EnigmaClueLevel1": "The Killer's Level is above 50!", + "EnigmaClueLevel2": "The Killer's Level is below 50!", + "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", + "EnigmaClueLevel4": "The Killer's Level is {0}!", + "EnigmaClueFriendCode": "The Killer's Friendcode is {0}!", + "EnigmaClueHatTitle": "Enigma Hat Clue!", + "EnigmaClueVisorTitle": "Enigma Visor Clue!", + "EnigmaClueSkinTitle": "Enigma Skin Clue!", + "EnigmaCluePetTitle": "Enigma Pet Clue!", + "EnigmaClueNameTitle": "Enigma Name Clue!", + "EnigmaClueNameLengthTitle": "Enigma Name Length Clue!", + "EnigmaClueColorTitle": "Enigma Color Clue!", + "EnigmaClueLocationTitle": "Enigma Location Clue!", + "EnigmaClueStatusTitle": "Enigma Status Clue!", + "EnigmaClueRoleTitle": "Enigma Role Clue!", + "EnigmaClueLevelTitle": "Enigma Level Clue!", + "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", + + "VotesPerKill": "Votes gained for each kill", + "PickpocketGetVote": "You've got {0} votes", + "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", + "VultureNumberOfReportsToWin": "Bodies needed to win", + "VultureReportBody": "Body eaten!", + "VultureEatButtonText": "Consume", + "VultureReportCooldown": "Eat Cooldown", + "VultureMaxEatenInOneRound": "Maximum eaten bodies possible per round", + "VultureCooldownUp": "Eat Cooldown finished", + + "GhastlyPossessCD": "Possess Cooldown", + "GhastlyMaxPossessions": "Max Possessions", + "GhastlyPossessionDuration": "Possession Duration", + "GhastlySpeed": "Ghastly Speed", + "GhastlyKillAllies": "Ghastly cannot possess allies", + "GhastlyCannotPossessTarget": "Couldn't Possess Target", + "GhastlyChooseTarget": "Now: Choose Target", + "GhastlyNoMorePossess": "You've run out of possessions!'", + "GhastlyNotUrTarget": "That is not your target", + "GhastlyYouvePosses": "You've Been Possessed!", + "GhastlyPossessedUser": "You have possessed: {0}", + "GhastlyExpired": "{0} is no longer possessed", + + "TasksMarkPerRound": "Number of tasks that can be marked in one round", + "TaskinatorBombPlanted": "Bomb has been planted", + + "ShieldDuration": "Shield duration", + "ShieldIsOneTimeUse": "Shield breaks after one kill attempt", + "BenefactorTaskMarked": "Task marked successfully", + "BenefactorTargetGotShield": "You got a shield by Benefactor", + + "PirateTryHideMsg": "Hide Pirate's commands", + "SuccessfulDuelsToWin": "Number of successful duels needed to win", + "PirateMeetingMsg": "Duel with your target.\n\nDuel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nYou win the Duel if you choose the same option as the target", + "PirateTargetMeetingMsg": "The Pirate chose t' duel ye!\nDuel wit' honor or die o' shame.\n\n Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nIf the Pirate chooses the same option as you or you don't participate, you'll die", + "PirateTitle": "PIRATE ", + "PirateTargetAlreadyChosen": "Yarr! Ye've already chosen a target.", + "PirateDead": "Ye be dead. Ye cannot duel anymore.", + "DuelAlreadyDone": "Ye 'ave already chosen an option fer the duel.", + "DuelDone": "Ye 'ave chosen yer option fer the Duel.\nWait fer the meetin' to end to see the result.", + "DuelHelp": "Duel command:\n⦿ /duel 0\n⦿ /duel 1\n⦿ /duel 2\n\nAs Pirate, try to choose the same number as the target.\nAs the target, try to choose a different number than the Pirate", + "PirateDuelButtonText": "Duel", + "DuelCooldown": "Duel Cooldown", + "Rock": "Rock", + "Paper": "Paper", + "Scissors": "Scissors", + "Heads": "Heads", + "Tails": "Tails", + "SpyRedNameDur": "Colored Name Duration", + "SpyInteractionBlocked": "Block kill button interaction", + "AgitaterBombCooldown": "Agitator bomb cooldown", + "AgitaterPassCooldown": "Bomb pass cooldown", + "BombExplodeCooldown": "Bomb explode cooldown", + "AgitaterPassNotify": "Bomb successfully passed", + "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", + "AgitaterCanGetBombed": "Agitator can get bomb", + "AgitaterAutoReportBait": "Agitator Auto Report Bait", + + "SeekerPointsToWin": "Number of points required to win", + "SeekerTagCooldown": "Tag Cooldown", + "SeekerNotify": "Your target is {0}", + "SeekerTargetNotify": "You are Seekers target!! Hide before they tag you", + "SeekerKillButtonText": "Tag", + + "PixiePointsToWin": "Number of points required to win", + "MaxTargets": "Maximum number of targets per round", + "MarkCooldown": "Mark cooldown", + "PixieSuicide": "Pixie suicides if the target is not voted out", + "PixieMaxTargetReached": "You have already selected all the targets this round", + "PixieTargetAlreadySelected": "Target is already selected", + "PixieButtonText": "Mark", + + "PlagueBearerCooldown": "Plague cooldown", + "PestilenceCooldown": "Pestilence Kill cooldown", + "PestilenceCanVent": "Pestilence Can Vent", + "PestilenceHasImpostorVision": "Pestilence Has Impostor Vision", + "PlagueBearerAlreadyPlagued": "Player has already been plagued", + "PlagueBearerToPestilence": "You have turned into Pestilence!!", + "GuessPestilence": "You just tried to guess Pestilence!\n\nSorry, Pestilence killed you.", + "PestilenceTransform": "A Plague has consumed the Crew, transforming the Plaguebearer into Pestilence, Horseman of the Apocalypse!", + "RomanticBetCooldown": "Pick Partner Cooldown", + "RomanticProtectCooldown": "Protect Cooldown", + "RomanticBetPlayer": "You picked your partner", + "RomanticBetOnYou": "The Romantic chose you as their Partner!", + "VengefulKCD": "Vengeful Romantic Kill Cooldown", + "VengefulCanVent": "Vengeful Romantic Can Vent", + "RuthlessKCD": "Ruthless Romantic Kill Cooldown", + "RuthlessCanVent": "Ruthless Romantic Can Vent", + "RomanticProtectPartner": "Your partner is under protection", + "RomanticIsProtectingYou": "The Romantic is protecting you", + "ProtectingOver": "Shield expired", + "RomanticProtectDuration": "Protect Duration", + "RomanticKnowTargetRole": "Romantic knows their target's role", + "RomanticBetTargetKnowRomantic": "Target knows who the Romantic is", + "RomanticPartnerButtonText": "Pick Partner", + "RomanticProtectButtonText": "Protect", + + "GuessMasterMisguess": "{0} misguessed", + "GuessMasterTargetRole": "Someone tried to guess {0}", + "GuessMasterTitle": "Guess Master ", + + "DoomsayerAmountOfGuessesToWin": "Amount of Guesses to win", + "DCanGuessImpostors": "Can Guess Impostors", + "DCanGuessCrewmates": "Can Guess Crewmates", + "DCanGuessNeutrals": "Can Guess Neutrals", + "DCanGuessAdt": "Can Guess Add-Ons", + "DoomsayerAdvancedSettings": "Advanced Settings", + "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", + "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", + "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", + "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting": "Misguessing role prevents guessing roles until next meeting", + "DoomsayerTryHideMsg": "Hide Doomsayer's commands", + "DoomsayerCantGuess": "Sorry, you can only guess the roles in the next meeting.", + "DoomsayerCorrectlyGuessRole": "You guessed the role correctly!\nBut the player didn't die because the Host settings don't allow them to die", + "DoomsayerNotCorrectlyGuessRole": "You didn't correctly guess the role!\nBut you didn't die because the Host's settings don't allow you to die", + "DoomsayerGuessCountMsg": "You correctly guessed {0} roles", + "DoomsayerGuessCountTitle": "DOOMSAYER", + "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same role or add-on that you guessed before", + + "EveryoneCanKnowMini": "Everyone can see the Mini", + "CanBeEvil": "Mini can be an Impostor", + "EvilMiniSpawnChances": "Probability of Mini being an Impostor", + "GuessMini": "Sorry, you can't hurt a kid Mini.", + "GrowUpDuration": "Time required to grow (s)", + "MajorCooldown": "Kill Cooldown when over 18", + "UpDateAge": "Display age change in real-time", + "Cantkillkid": "You can't kill a Mini that hasn't grown up.", + "CantEat": "You can't eat a Mini that hasn't grown up", + "CantShroud": "You can't control a Mini that hasn't grown up.", + "CantBoom": "You can't blow yourself up with a Mini that hasn't grown up.", + "CantRecruit": "You can't recruit a Mini that hasn't grown up.", + "CantDuel": "You can't duel a Mini that hasn't grown up.", + "CantMark": "You can't mark a Mini that hasn't grown up.", + "CantBlood": "You can't blood a Mini that hasn't grown up.", + "CantPosses": "You can't possess a Mini that hasn't grown up.", + "ExiledNiceMini": "You ejected a Nice Mini before they grew up.\nYou all lose", + "MiniUp": "You're a year older!", + "MiniMisGuessed": "You are supposed to misguess to death!\nHowever you are still a kid, so you are free of guilt while you can no longer guess.\nYou can guess again after you have grown up.", + "MiniGuessMax": "You have misguessed, so you are no longer allowed to guess!", + "CountMeetingTime": "Meeting time can continue to grow", + "YouKillRandomizer1": "You kill Randomizer, Self-report!", + "YouKillRandomizer2": "You kill Randomizer, Cannot move!", + "YouKillRandomizer3": "You kill Randomizer, Kill CD change to 600s!", + "YouKillRandomizer4": "You kill Randomizer, Triggered Random Revenge!", + "MadmateCanBeHurried": "Madmate can be Hurried on game start", + "TaskBasedCrewCanBeHurried": "Task-based Crews can be Hurried", + "HurriedCanBeConverted": "Hurried can be recruited in the game (excludes madmate)", + "Developer": "Developer", + "Sponsor": "Sponsor", + "Booster": "Server Booster", + "Translator": "Translator", + "NoAccess": "Unauthorized Access!\n\n Please open up a ticket in the discord server to know more (discord.gg/tohe)", + "DCNotify.Hacking": "You were banned for hacking.\n\nPlease stop.", + "DCNotify.Banned": "You were banned from this lobby.\n\nContact the host if this was a mistake.", + "DCNotify.Kicked": "You were kicked from this lobby.\n\nYou may still rejoin.", + "DCNotify.DCFromServer": "You disconnected from the server.\r\nThis could be an issue with either the servers or your network.", + "DCNotify.GameNotFound": "This lobby code is invalid.\n\nCheck the code and/or server and try again.", + "DCNotify.GameStarted": "This lobby is currently in-game.\n\nWait for it to end or find a different lobby.", + "DCNotify.GameFull": "This lobby is currently full.\n\nCheck with the host to see if you may join.", + "DCNotify.IncorrectVersion": "This lobby does not support your Among Us version.", + "DCNotify.Inactivity": "The lobby closed due to inactivity.", + "DCNotify.Auth": "You are not authenticated.\n\nYou may need to restart your game.", + "DCNotify.DupeLogin": "An instance of your account is already present in this lobby.", + "DCNotify.InvalidSettings": "Game settings have been detected to be invalid.\n\nEnter local play to reset them, then try again.", + "ModeDescribe.SoloKombat": "Current mode is [Solo PVP]\nNo role assignment. Everyone has HP and can use the kill button to cause damage to other players. The player with the highest number of kills wins at the end of the game.", + "RoleType.VanillaRoles": "★ Vanilla Roles", + "RoleType.ImpKilling": "★ Impostor Killing Roles", + "RoleType.ImpSupport": "★ Impostor Support Roles", + "RoleType.ImpConcealing": "★ Impostor Concealing Roles", + "RoleType.ImpHindering": "★ Impostor Hindering Roles", + "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", + "RoleType.Madmate": "★ Madmate Roles", + "RoleType.CrewSupport": "★ Crewmate Support Roles", + "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", + "RoleType.CrewPower": "★ Crewmate Power Roles", + "RoleType.CrewKilling": "★ Crewmate Killing Roles", + "RoleType.CrewBasic": "★ Crewmate Basic Roles", + "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", + "RoleType.NeutralEvil": "★ Neutral Evil Roles", + "RoleType.NeutralBenign": "★ Neutral Benign Roles", + "RoleType.NeutralChaos": "★ Neutral Chaos Roles", + "RoleType.NeutralKilling": "★ Neutral Killing Roles", + "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", + "RoleType.Harmful": "★ Harmful Add-ons", + "RoleType.Support": "★ Supportive Add-ons", + "RoleType.Helpful": "★ Helpful Add-ons", + "RoleType.Mixed": "★ Mixed Add-ons", + "RoleType.Misc": "★ Miscellaneous Add-ons", + "RoleType.Impostor": "★ Impostor Add-ons", + "RoleType.Neut": "★ Neutral Add-ons", + "SubType.Impostor": "★ Impostors", + "SubType.Shapeshifter": "★ Shapeshifters", + "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", + "SubType.Madmate": "★ Madmates", + "SubType.CrewmateKilling": "★ Crewmate Killings", + "SubType.Crewmate": "★ Regular Crewmates", + "SubType.New": "★ New!", + "CrewmateRoles": "★ Crewmate Roles ★", + "ImpostorRoles": "★ Impostor Roles ★", + "NeutralRoles": "★ Neutral Roles ★", + "AddonRoles": "★ Add-ons ★", + "WinnerRoleText.Impostor": "Impostors Win!", + "WinnerRoleText.Crewmate": "Crewmates Win!", + "WinnerRoleText.Apocalypse": "Apocalypse Wins!", + "WinnerRoleText.Terrorist": "Terrorist Wins!", + "WinnerRoleText.Jester": "Jester Wins!", + "WinnerRoleText.Lovers": "Lovers Win!", + "WinnerRoleText.Executioner": "Executioner Wins!", + "WinnerRoleText.Arsonist": "Arsonist Wins!", + "WinnerRoleText.Revolutionist": "Revolutionist Wins!", + "WinnerRoleText.Jackal": "Jackals Win!", + "WinnerRoleText.God": "God Wins!", + "WinnerRoleText.Vector": "Vector Wins!", + "WinnerRoleText.Innocent": "Innocent Wins!", + "WinnerRoleText.Pelican": "Pelican Wins!", + "WinnerRoleText.Youtuber": "YouTuber Wins!", + "WinnerRoleText.Necromancer": "Necromancer Wins!", + "WinnerRoleText.Egoist": "Egoists Win!", + "WinnerRoleText.Demon": "Demon Wins!", + "WinnerRoleText.Stalker": "Stalker Wins!", + "WinnerRoleText.Workaholic": "Workaholic Wins!", + "WinnerRoleText.Collector": "Collector Wins!", + "WinnerRoleText.BloodKnight": "Blood Knight Wins!", + "WinnerRoleText.Poisoner": "Poisoner Wins!", + "WinnerRoleText.Huntsman": "Huntsman Wins!", + "WinnerRoleText.HexMaster": "Hex Master Wins!", + "WinnerRoleText.Cultist": "Cultist Wins!", + "WinnerRoleText.Wraith": "Wraith Wins!", + "WinnerRoleText.SerialKiller": "Serial Killers Win!", + "WinnerRoleText.Juggernaut": "Juggernaut Wins!", + "WinnerRoleText.Infectious": "Infectious Wins!", + "WinnerRoleText.Virus": "Virus Wins!", + "WinnerRoleText.Specter": "Specter Wins!", + "WinnerRoleText.Jinx": "Jinx Wins!", + "WinnerRoleText.CursedSoul": "Cursed Soul Wins!", + "WinnerRoleText.PotionMaster": "Potion Master Wins!", + "WinnerRoleText.Pickpocket": "Pickpocket Wins!", + "WinnerRoleText.Traitor": "Traitor Wins!", + "WinnerRoleText.Vulture": "Vulture Wins!", + "WinnerRoleText.Medusa": "Medusa Wins!", + "WinnerRoleText.Spiritcaller": "Spiritcaller Wins!", + "WinnerRoleText.Glitch": "Glitch Wins!", + "WinnerRoleText.Pestilence": "Pestilence Wins!", + "WinnerRoleText.PlagueBearer": "Plaguebearer Wins!", + "WinnerRoleText.PunchingBag": "Punching Bag Wins!", + "WinnerRoleText.Doomsayer": "Doomsayer Wins!", + "WinnerRoleText.Pirate": "Pirate Wins!", + "WinnerRoleText.Shroud": "Shroud Wins!", + "WinnerRoleText.Werewolf": "Werewolf Wins!", + "WinnerRoleText.Seeker": "Seeker Wins!", + "WinnerRoleText.Occultist": "Occultist Wins!", + "WinnerRoleText.SoulCollector": "Soul Collector Wins!", + "WinnerRoleText.NiceMini": "Nice Mini Wins!", + "WinnerRoleText.Mini": "Nice Mini was killed", + "WinnerRoleText.Bandit": "Bandit Wins!", + "WinnerRoleText.RuthlessRomantic": "Ruthless Romantic Wins!", + "WinnerRoleText.Solsticer": "Solsticer Wins!", + "WinnerRoleText.Pyromaniac": "Pyromaniac Wins!", + "WinnerRoleText.Doppelganger": "Doppelganger Wins!", + "WinnerRoleText.Quizmaster": "Quizmaster Wins!", + "WinnerRoleText.Agitater": "Agitator Wins!", + "AdditionalWinnerRoleText.Sidekick": "Sidekick", + "AdditionalWinnerRoleText.Taskinator": "Taskinator", + "AdditionalWinnerRoleText.Opportunist": "Opportunist", + "AdditionalWinnerRoleText.Lawyer": "Lawyer", + "AdditionalWinnerRoleText.Hater": "Hater", + "AdditionalWinnerRoleText.Provocateur": "Provocateur", + "AdditionalWinnerRoleText.Sunnyboy": "Sunnyboy", + "AdditionalWinnerRoleText.Follower": "Follower", + "AdditionalWinnerRoleText.Pursuer": "Pursuer", + "AdditionalWinnerRoleText.Jester": "Jester", + "AdditionalWinnerRoleText.Lovers": "Lovers", + "AdditionalWinnerRoleText.Executioner": "Executioner", + "AdditionalWinnerRoleText.Specter": "Specter", + "AdditionalWinnerRoleText.Maverick": "Maverick", + "AdditionalWinnerRoleText.Shaman": "Shaman", + "AdditionalWinnerRoleText.Pixie": "Pixie", + "AdditionalWinnerRoleText.NiceMini": "Nice Mini", + "AdditionalWinnerRoleText.Romantic": "Romantic", + "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", + "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", + "ErrorEndText": "An error occurred", + "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", + "ForceEnd": "Aborted", + "EveryoneDied": "Everyone died", + "ForceEndText": "Host has aborted the game", + "NiceMiniDied": "Nice Mini was killed", + "HaterMisFireKillTarget": "Hater kills target when misfiring", + "HaterChooseConverted": "Select add-ons that Hater can kill", + "HaterCanKillMadmate": "Can kill madmate", + "HaterCanKillCharmed": "Can kill charmed", + "HaterCanKillLovers": "Can kill lovers", + "HaterCanKillSidekick": "Can kill jackal team", + "HaterCanKillEgoist": "Can kill egoist", + "HaterCanKillInfected": "Can kill infected team", + "HaterCanKillContagious": "Can kill virus team", + "HaterCanKillAdmired": "Can kill admirer", + "HorseMode": "Enable to become a horse", + "LongMode": "Enable to have a long neck", + "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", + + + "FFA": "Free For All", + "ModeFFA": "Gamemode: FFA", + "ModeDescribe.FFA": "In the FFA (Free For All) gamemode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", + "KillerInfoLong": "In the FFA (Free For All) game mode, everyone is a killer, and everyone can kill anyone. The last player alive wins!\n\nSome random events make this even more fun in the meantime!", + "FFA_GameTime": "Maximum Game Length", + "FFA_KCD": "Kill Cooldown", + "FFA_DisableVentingWhenTwoPlayersAlive": "Prevent venting when only 2 players are alive", + "FFA_EnableRandomAbilities": "Enable Random Events", + "FFA_ShieldDuration": "Shield Duration", + "FFA_IncreasedSpeed": "Increased Speed", + "FFA_DecreasedSpeed": "Decreased Speed", + "FFA_ModifiedSpeedDuration": "Modified Speed Duration", + "FFA_LowerVision": "Lowered Vision", + "FFA_ModifiedVisionDuration": "Lowered Vision Duration", + "FFA_EnableRandomTwists": "Enable Random Swaps from time to time", + "FFA-Event-GetShield": "You have a temporary shield!", + "FFA-Event-GetIncreasedSpeed": "You have a temporary speed boost!", + "FFA-Event-GetLowKCD": "You got a lower kill cooldown!", + "FFA-Event-GetHighKCD": "You got a higher kill cooldown", + "FFA-Event-GetLowVision": "You have lower vision temporarily", + "FFA-Event-GetDecreasedSpeed": "You have decreased speed temporarily", + "FFA-Event-GetTP": "You got teleported to a random vent!", + "FFA-Event-RandomTP": "Everyone was swapped with someone", + "FFA-NoVentingBecauseTwoPlayers": "There are only 2 players alive, stop hiding in vents!", + "FFA-NoVentingBecauseKCDIsUP": "Your kill cooldown is up, don't hide in vents!", + "FFA_DisableVentingWhenKCDIsUp": "Prevent players whose kill cooldown is up from venting", + "FFA_TargetIsShielded": "The player you tried to kill is shielded!", + "FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt", + "FFA_ShieldBroken": "Someone tried to kill you, your shield is now broken!", + "Killer": "FREE FOR ALL", + "KillerInfo": "Kill Everyone to Win", + + "Hide&SeekTOHE": "Hide & Seek", + "MenuTitle.Hide&Seek": "Hide & Seek Settings", + "NumImpostorsHnS": "Num Impostors", + + "EveryOneKnowSolsticer": "Every One Know who is Solsticer", + "SolsticerKnowItsKiller": "Solsticer knows the role of whom used the kill button on it", + "SolsticerSpeed": "Movement speed of Solsticer", + "SolsticerRemainingTaskWarned": "Remaining tasks to be known", + "SAddTasksPreDeadPlayer": "How many extra short tasks Solsticer gets when a player dies", + "SolsticerMurdered": "{0} attempted to murder you!", + "MurderSolsticer": "You stopped Solsticer this round!", + "SolsticerMurderMessage": "{0} used kill button on you last round! Its role is {1}!", + "SolsticerOnMeeting": "You witnessed too many deaths! Next round you will have {0} more short task!", + "SolsticerTitle": "Solsticer", + "GuessSolsticer": "Sorry, but you can not guess Solsticer!", + "VoteSolsticer": "Sorry, but you can not vote Solsticer!", + "SolsticerTasksReset": "Your tasks get reset!", + "SolsticerMisGuessed": "You just misguessed! You are no longer allowed to guess.", + "SolsticerGuessMax": "Because you already misguessed, you are no longer allowed to guess.", + + "VoteDead": "The player you voted for was exiled before the meeting concluded. Your vote was rescinded.", + + "ImpCanBeSilent": "Impostors can become Silent", + "CrewCanBeSilent": "Crewmates can become Silent", + "NeutralCanBeSilent": "Neutrals can become Silent", + "LastMessageReplay": "Last System Message Replay", + "Contributor": "Contributor", + + "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", + "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", + + "ImpCanBeSusceptible": "Impostors can become Susceptible", + "CrewCanBeSusceptible": "Crewmates can become Susceptible", + "NeutralCanBeSusceptible": "Neutrals can become Susceptible", + + "Quizmaster": "Quizmaster", + "QuizmasterInfo": "Quiz people to kill them in meetings", + "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will have \"?!\" next to their name. The player will die if they answer the question wrong or doesn't answer. The player will live if the Quizmaster is killed/ejected in the same meeting.\nThe Quizmaster cannot mark multiple people in the same round", + "QuizmasterKillButtonText": "Quiz", + + "QuizmasterChat.MarkedBy": "You've been marked by the Quizmaster\nTo survive you have to answer correct to this question:\n\n{QMQUESTION}", + "QuizmasterChat.MarkedPublic": "{QMTARGET} has been marked by the Quizmaster\nTo survive {QMTARGET} have to answer correct to their question!", + "QuizmasterChat.Answers": "Answers\nA: {QMA}\nB: {QMB}\nC: {QMC}\n\nTo answer just type /answer [answer letter]\n\nIf you need to recheck the answer and questions just do /qmquiz", + "QuizmasterChat.CorrectTarget": "Correct", + "QuizmasterChat.Correct": "{QMTARGET} got the right answer!\nYou can now mark someone else!", + "QuizmasterChat.CorrectPublic": "{QMTARGET} got the Quizmaster's question answer correct and survived!\nBeware of the Quizmaster!", + "QuizmasterChat.WrongTarget": "Wrong\nYour answer was {QMWRONG}\nThe correct answer was {QMRIGHT}\n\nThe Quizmaster was {QM}", + "QuizmasterChat.Wrong": "{QMTARGET} got the wrong answer and died!\nYou can now mark someone else!", + "QuizmasterChat.WrongPublic": "{QMTARGET} got the Quizmaster's question answer wrong and died!\nBeware of the Quizmaster!", + "QuizmasterChat.Marked": "You've marked {QMTARGET}\nIf {QMTARGET} doesn't answer by the end of the meeting or answer wrong {QMTARGET} will die\n\nQuestion for {QMTARGET} => {QMQUESTION}", + "QuizmasterChat.Title": "Quizmaster Information", + "QuizmasterChat.CantAnswer": "As the quizmaster, you can't answer questions", + "QuizmasterChat.AnswerNotValid": "Your answer must be A, B, or C", + "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", + + "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", + "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", + "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", + "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", + "QuizmasterSettings.CanGiveQuestionsAboutPastGames": "Can Give Questions About Past Games", + + "Quizmaster.None": "None", + + "QuizmasterSabotages.Lights": "Lights", + "QuizmasterSabotages.Reactor": "Reactor", + "QuizmasterSabotages.Communications": "Communications", + "QuizmasterSabotages.O2": "O2", + "QuizmasterSabotages.MushroomMixup": "Mushroom Mixup", + "QuizmasterAnswers.One": "One", + "QuizmasterAnswers.Two": "Two", + "QuizmasterAnswers.Three": "Three", + "QuizmasterAnswers.Four": "Four", + "QuizmasterAnswers.Five": "Five", + "QuizmasterAnswers.Pacifist": "Pacifist", + "QuizmasterAnswers.Vampire": "Vampire", + "QuizmasterAnswers.Snitch": "Snitch", + "QuizmasterAnswers.Vigilante": "Vigilante", + "QuizmasterAnswers.Jackal": "Jackal", + "QuizmasterAnswers.Mole": "Mole", + "QuizmasterAnswers.Sniper": "Sniper", + "QuizmasterAnswers.Coven": "Coven", + "QuizmasterAnswers.Sabotuer": "Saboteur", + "QuizmasterAnswers.Sorcerers": "Sorcerers", + "QuizmasterAnswers.Killer": "Killer", + "QuizmasterAnswers.Edition": "Edition", + "QuizmasterAnswers.Experimental": "Experimental", + "QuizmasterAnswers.Enhanced": "Enhanced", + "QuizmasterAnswers.Edited": "Edited", + + "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", + "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", + "QuizmasterQuestions.LastEjectedPlayerColor": "What was the color of the player that was last ejected?", + "QuizmasterQuestions.LastReportPlayerColor": "What was the color of the body that was last reported before this meeting?", + "QuizmasterQuestions.LastButtonPressedPlayerColor": "Who called the last meeting before this meeting?", + "QuizmasterQuestions.MeetingPassed": "How many meetings have passed so far?", + "QuizmasterQuestions.HowManyFactions": "How many factions are in the game?", + "QuizmasterQuestions.BasisOfRole": "What's the basis of {QMRole}?", + "QuizmasterQuestions.FactionOfRole": "What's the faction of {QMRole}?", + "QuizmasterQuestions.FactionRemovedName": "What faction used to be in the game but was removed in an update later?", + "QuizmasterQuestions.HowManyDiedFirstRound": "How many people died round one?", + "QuizmasterQuestions.ButtonPressedBefore": "How many people pressed the emergency button before this meeting?", + "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", + "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", + "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", + "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", + "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", + + "DeathReason.WrongAnswer": "Wrong Quiz Answer", + + "TPCooldown": "Teleport Cooldown", + "RiftsTooClose": "Location too close to the first rift", + "RiftCreated": "Rift made successfully", + "RiftsDestroyed": "All rifts Destroyed", + "RiftRadius": "Rift Radius", + + "TiredVision": "Vision When Tired", + "TiredSpeed": "Speed When Tired", + "TiredDur": "Tired Duration", + "ImpCanBeTired": "Impostors can become Tired", + "CrewCanBeTired": "Crewmates can become Tired", + "NeutralCanBeTired": "Neutrals can become Tired", + + "TiredNotify": "Zzz..", + + "PlagueDoctorInfectLimit": "Infect Limit", + "PlagueDoctorInfectWhenKilled": "Infect Killer When Killed", + "PlagueDoctorInfectTime": "Infect Time", + "PlagueDoctorInfectDistance": "Infect Distance", + "PlagueDoctorInfectInactiveTime": "Delay Infection After Start The Game And After Meetings", + "PlagueDoctorCanInfectSelf": "Can Infect Self", + "PlagueDoctorCanInfectVent": "Can Infect While In Vent", + "WinnerRoleText.PlagueDoctor": "Plague Scientist Wins!", + + "StatueSlow": "Statue Slowness", + "StatuePeopleToSlow": "People Needed To Slow", + + "ImpCanBeStatue": "Impostors can become Statue", + "CrewCanBeStatue": "Crewmates can become Statue", + "NeutralCanBeStatue": "Neutrals can become Statue", + + "WardenIncreaseSpeed": "Increase Speed By", + "WardenWarn": "DANGER! RUN!", + + "MinionAbilityTime": "Ability Duration", + "Minion_Blind": "blinded" } From 97ec00151e61324ca80b4413cec4d66249dd18a0 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:52:42 +0200 Subject: [PATCH 152/778] move --- Patches/ChatBubblePatch.cs | 19 ------------------- Patches/TextBoxPatch.cs | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index a87996c6d7..998f2d3ed5 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -31,22 +31,3 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe } } -//Thanks https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Fixes/CursorPosPatch.cs - -/// -/// "Fixes" an issue where empty TextBoxes have wrong cursor positions. -/// -[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] -internal static class CursorPosPatch -{ - public static bool Prefix(TextMeshPro self, ref Vector2 __result) - { - if (self.textInfo == null || self.textInfo.lineCount == 0 || self.textInfo.lineInfo[0].characterCount <= 0) - { - __result = self.GetTextInfo(" ").lineInfo.First().lineExtents.max; - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index aedf90dfdb..e0aca7148f 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -1,4 +1,6 @@ using System; +using TMPro; +using UnityEngine; namespace TOHE.Patches; @@ -69,6 +71,29 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp return false; } } + +//Thanks https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Fixes/CursorPosPatch.cs + +/// +/// "Fixes" an issue where empty TextBoxes have wrong cursor positions. +/// +[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] +internal static class CursorPosPatch +{ + public static bool Prefix(TextMeshPro self, ref Vector2 __result) + { + if (self.textInfo == null || self.textInfo.lineCount == 0 || self.textInfo.lineInfo[0].characterCount <= 0) + { + __result = self.GetTextInfo(" ").lineInfo.First().lineExtents.max; + return false; + } + + return true; + } +} + + + /* Originally by KARPED1EM. Reference: https://github.com/KARPED1EM/TownOfNext/blob/TONX/TONX/Patches/TextBoxPatch.cs */ /*[HarmonyPatch(typeof(TextBoxTMP))] public class TextBoxPatch From ae71917144fdf2333549f1d00ef9054427dade98 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 5 Aug 2024 01:53:52 +0800 Subject: [PATCH 153/778] Some changes --- Modules/ExtendedPlayerControl.cs | 15 ++++++++------- Modules/OptionHolder.cs | 4 ++-- Modules/Utils.cs | 2 -- Patches/ChatCommandPatch.cs | 4 ++-- Roles/Crewmate/Cleanser.cs | 1 - Roles/Crewmate/FortuneTeller.cs | 1 - Roles/Crewmate/Keeper.cs | 1 - Roles/Crewmate/Oracle.cs | 1 - Roles/Impostor/Eraser.cs | 1 - Roles/Neutral/Jester.cs | 3 +++ 10 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 85204ce32e..e380d46414 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -69,14 +69,15 @@ public static ClientData GetClient(this PlayerControl player) return null; } } - public static void RPCCastVote(this byte playerId, byte suspectIdx) + public static void RpcCastVote(this PlayerControl player, byte suspectIdx) { - if(!GameStates.IsMeeting) + if (!GameStates.IsMeeting) { - var player = Utils.GetPlayerById(playerId); - Logger.Info($"Cancelled RPCCastVote for {player?.GetRealName()} because there is no meeting", "ExtendedPlayerControls..RPCCastVote"); + Logger.Info($"Cancelled RpcCastVote for {player?.Data.PlayerName} because there is no meeting", "ExtendedPlayerControls..RPCCastVote"); return; } + if (player == null) return; + var playerId = player.PlayerId; if (AmongUsClient.Instance.AmHost) { @@ -86,9 +87,9 @@ public static void RPCCastVote(this byte playerId, byte suspectIdx) { var writer = CustomRpcSender.Create("Cast Vote", SendOption.Reliable); writer.AutoStartRpc(MeetingHud.Instance.NetId, (byte)RpcCalls.CastVote) - .Write(playerId) - .Write(suspectIdx) - .EndRpc(); + .Write(playerId) + .Write(suspectIdx) + .EndRpc(); writer.SendMessage(); } } diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index e040bea514..0b48315be2 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1866,10 +1866,10 @@ private static System.Collections.IEnumerator CoLoadOptions() WhenTie = StringOptionItem.Create(60745, "WhenTie", tieModes, 0, TabGroup.ModSettings, false) .SetParent(VoteMode) .SetGameMode(CustomGameMode.Standard); - EnableVoteCommand = BooleanOptionItem.Create(60746, "EnableVote", true, TabGroup.GameSettings, false) + EnableVoteCommand = BooleanOptionItem.Create(60746, "EnableVote", true, TabGroup.ModSettings, false) .SetColor(new Color32(147, 241, 240, byte.MaxValue)) .SetGameMode(CustomGameMode.Standard); - ShouldVoteCmdsSpamChat = BooleanOptionItem.Create(60747, "ShouldVoteSpam", false, TabGroup.GameSettings, false) + ShouldVoteCmdsSpamChat = BooleanOptionItem.Create(60747, "ShouldVoteSpam", false, TabGroup.ModSettings, false) .SetParent(EnableVoteCommand) .SetGameMode(CustomGameMode.Standard); // 其它设定 diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ebc001098c..1ac5c8b1cb 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -231,8 +231,6 @@ public static void SetVisionV2(this IGameOptions opt) } return; } - - public static void RPCCastVote(this PlayerControl voter, PlayerControl voteTarget) => ExtendedPlayerControl.RPCCastVote(voter.PlayerId, voteTarget.PlayerId); public static void TargetDies(PlayerControl killer, PlayerControl target) { diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 91cf3bcd6a..0ee88d6030 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -401,7 +401,7 @@ public static bool Prefix(ChatController __instance) } if (GameStates.IsMeeting) { - ExtendedPlayerControl.RPCCastVote(PlayerControl.LocalPlayer.PlayerId, (byte)arg); + PlayerControl.LocalPlayer.RpcCastVote((byte)arg); } break; @@ -2714,7 +2714,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can } if (GameStates.IsMeeting) { - ExtendedPlayerControl.RPCCastVote(player.PlayerId, (byte)arg); + player.RpcCastVote((byte)arg); } break; diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index a1cdd8c647..c2d584690e 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -16,7 +16,6 @@ internal class Cleanser : RoleBase private static OptionItem CleanserUsesOpt; private static OptionItem CleansedCanGetAddon; - private static OptionItem HidesVote; //private static OptionItem AbilityUseGainWithEachTaskCompleted; private readonly HashSet CleansedPlayers = []; diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 26fe25e4fc..1ad018f3f1 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -21,7 +21,6 @@ internal class FortuneTeller : RoleBase private static OptionItem CheckLimitOpt; private static OptionItem AccurateCheckMode; - private static OptionItem HidesVote; private static OptionItem ShowSpecificRole; private static OptionItem AbilityUseGainWithEachTaskCompleted; private static OptionItem RandomActiveRoles; diff --git a/Roles/Crewmate/Keeper.cs b/Roles/Crewmate/Keeper.cs index 28a5a1bb35..e3fcd6b65e 100644 --- a/Roles/Crewmate/Keeper.cs +++ b/Roles/Crewmate/Keeper.cs @@ -20,7 +20,6 @@ internal class Keeper : RoleBase //==================================================================\\ private static OptionItem KeeperUsesOpt; - private static OptionItem HidesVote; private static readonly HashSet keeperTarget = []; private static readonly Dictionary keeperUses = []; diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index 5d3be6bf42..e240e195b7 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -20,7 +20,6 @@ internal class Oracle : RoleBase //==================================================================\\ private static OptionItem CheckLimitOpt; - private static OptionItem HidesVote; private static OptionItem FailChance; private static OptionItem OracleAbilityUseGainWithEachTaskCompleted; private static OptionItem ChangeRecruitTeam; diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index 0800ce529c..4fb930c3d1 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -17,7 +17,6 @@ internal class Eraser : RoleBase //==================================================================\\ private static OptionItem EraseLimitOpt; - public static OptionItem HideVoteOpt; private static readonly HashSet didVote = []; private static readonly HashSet PlayerToErase = []; diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 852bb77c61..a26858d438 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -30,6 +30,8 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); JesterHasImpostorVision = BooleanOptionItem.Create(Id + 4, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); + HideJesterVote = BooleanOptionItem.Create(Id + 5, GeneralOption.HideVote, true, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); MeetingsNeededForJesterWin = IntegerOptionItem.Create(Id + 6, "MeetingsNeededForWin", new(0, 10, 1), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Times); @@ -52,6 +54,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) opt.SetVision(JesterHasImpostorVision.GetBool()); } + public override bool HideVote(PlayerVoteArea votedPlayer) => HideJesterVote.GetBool(); public override bool OnCheckStartMeeting(PlayerControl reporter) => JesterCanUseButton.GetBool(); public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) From b9d01d933dab01d90fe78a83ad2120b87b130e51 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 5 Aug 2024 02:03:28 +0800 Subject: [PATCH 154/778] Fix bugs --- Patches/MeetingHudPatch.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 6cba427252..b57f14e276 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -644,10 +644,10 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl var voter = Utils.GetPlayerById(srcPlayerId); if (voter == null || !voter.IsAlive()) return false; - var target = Utils.GetPlayerById(suspectPlayerId); + var target = GetPlayerById(suspectPlayerId); if (target == null && suspectPlayerId < 253) { - Utils.SendMessage(GetString("VoteDead"), srcPlayerId); + SendMessage(GetString("VoteDead"), srcPlayerId); __instance.RpcClearVote(voter.GetClientId()); return false; } //Vote a disconnect player @@ -658,7 +658,8 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (!voter.GetRoleClass().HasVoted) { voter.GetRoleClass().HasVoted = true; - Utils.SendMessage("VoteNotUseAbility", voter.PlayerId); + SendMessage(GetString("VoteNotUseAbility"), voter.PlayerId); + __instance.RpcClearVote(voter.GetClientId()); return false; } } @@ -667,7 +668,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl { if (!target.IsAlive() || target.Data.Disconnected) { - Utils.SendMessage(GetString("VoteDead"), srcPlayerId); + SendMessage(GetString("VoteDead"), srcPlayerId); __instance.RpcClearVote(voter.GetClientId()); Swapper.CheckSwapperTarget(suspectPlayerId); return false; @@ -682,7 +683,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl { // Attempts to set thumbsdown color to the same as playerrole to signify player ability used on (only for modded client) PlayerVoteArea pva = MeetingHud.Instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); - Color color = Utils.GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); + Color color = GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); pva.ThumbsDown.set_color_Injected(ref color); } return false; @@ -693,13 +694,13 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl case CustomRoles.Dictator: if (target.Is(CustomRoles.Solsticer)) { - Utils.SendMessage(GetString("VoteSolsticer"), srcPlayerId); + SendMessage(GetString("VoteSolsticer"), srcPlayerId); __instance.RpcClearVote(voter.GetClientId()); return false; } if (!target.IsAlive()) { - Utils.SendMessage(GetString("VoteDead"), srcPlayerId); + SendMessage(GetString("VoteDead"), srcPlayerId); __instance.RpcClearVote(voter.GetClientId()); return false; } //patch here so checkend is not triggered From c1c178d950c06165e5dfe4f0e6aa20e07f977c8d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 5 Aug 2024 02:19:46 +0800 Subject: [PATCH 155/778] Fix Duplicate ID & Fix cancel vote --- Modules/OptionHolder.cs | 2 +- Patches/MeetingHudPatch.cs | 16 +++++++++------- Roles/Crewmate/Captain.cs | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 0b48315be2..ed4de32573 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -889,7 +889,7 @@ private static System.Collections.IEnumerator CoLoadOptions() CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralKilling).ForEach(r => r.SetupCustomOption()); - TextOptionItem.Create(10000015, "RoleType.NeutralApocalypse", TabGroup.NeutralRoles) + TextOptionItem.Create(10000115, "RoleType.NeutralApocalypse", TabGroup.NeutralRoles) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(127, 140, 141, byte.MaxValue)); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index b57f14e276..9255754d47 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -677,15 +677,17 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (!voter.GetRoleClass().HasVoted && !voter.GetRoleClass().CheckVote(voter, target)) { - Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); + Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch.RoleBase.CheckVote"); + voter.GetRoleClass().HasVoted = true; __instance.RpcClearVote(voter.GetClientId()); - if (target != null) - { + + if (target != null) + { // Attempts to set thumbsdown color to the same as playerrole to signify player ability used on (only for modded client) - PlayerVoteArea pva = MeetingHud.Instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); - Color color = GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); - pva.ThumbsDown.set_color_Injected(ref color); - } + PlayerVoteArea pva = MeetingHud.Instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); + Color color = GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); + pva.ThumbsDown.set_color_Injected(ref color); + } return false; } diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index 644ead6fe2..038dd17c9e 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -47,8 +47,8 @@ public override void SetupCustomOption() CaptainCanTargetNC = BooleanOptionItem.Create(Id + 18, "CaptainCanTargetNC", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNE = BooleanOptionItem.Create(Id + 19, "CaptainCanTargetNE", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNK = BooleanOptionItem.Create(Id + 20, "CaptainCanTargetNK", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); - CaptainCanTargetNA = BooleanOptionItem.Create(Id + 22, "CaptainCanTargetNA", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); - OverrideTasksData.Create(Id + 21, TabGroup.CrewmateRoles, CustomRoles.Captain); + CaptainCanTargetNA = BooleanOptionItem.Create(Id + 21, "CaptainCanTargetNA", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); + OverrideTasksData.Create(Id + 22, TabGroup.CrewmateRoles, CustomRoles.Captain); } public override void Init() From 18df4c569bbbdc5b950bc2e93a17da7e78e8284e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 5 Aug 2024 02:31:55 +0800 Subject: [PATCH 156/778] Some changes --- Patches/ChatCommandPatch.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 0ee88d6030..78fb087677 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -369,7 +369,6 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); break; case "/vote": - canceled = true; subArgs = args.Length != 2 ? "" : args[1]; if (subArgs == "" || !int.TryParse(subArgs, out int arg)) break; @@ -386,6 +385,12 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); break; } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + ChatManager.SendPreviousMessagesToAll(); + } + if (arg != 253) // skip { if (plr == null || !plr.IsAlive()) @@ -2679,7 +2684,6 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can break; case "/vote": - canceled = true; subArgs = args.Length != 2 ? "" : args[1]; if (subArgs == "" || !int.TryParse(subArgs, out int arg)) break; @@ -2697,7 +2701,11 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); break; } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) ChatManager.SendPreviousMessagesToAll(); + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + ChatManager.SendPreviousMessagesToAll(); + } if (arg != 253) // skip { @@ -2707,7 +2715,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can break; } } - if(!player.IsAlive()) + if (!player.IsAlive()) { Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); break; From fe657d9c03f7af7f0e4108cb3e9e25b922cc9ffe Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 5 Aug 2024 02:32:24 +0800 Subject: [PATCH 157/778] Fix --- Patches/ChatCommandPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 78fb087677..f870806ed5 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -388,7 +388,6 @@ public static bool Prefix(ChatController __instance) if (Options.ShouldVoteCmdsSpamChat.GetBool()) { canceled = true; - ChatManager.SendPreviousMessagesToAll(); } if (arg != 253) // skip From cb04084ae10975e85bd6bd25d9c8f70515a43be3 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:08:57 +0000 Subject: [PATCH 158/778] Fix those pesky conflicts --- Patches/MeetingHudPatch.cs | 42 +++-- Roles/Impostor/DoubleAgent.cs | 289 ++++++++++++++++------------------ 2 files changed, 165 insertions(+), 166 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 56761a65bb..bd59cefb40 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -649,6 +649,17 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } //Vote a disconnect player + // Return vote to player if uses checkvote and wants to vote normal without using his abilities. + if (suspectPlayerId == 253 && voter.GetRoleClass()?.IsMethodOverridden("CheckVote") == true) + { + if (!voter.GetRoleClass().HasVoted) + { + voter.GetRoleClass().HasVoted = true; + Utils.SendMessage("VoteNotUseAbility", voter.PlayerId); + return false; + } + } + if (target != null && suspectPlayerId < 253) { if (!target.IsAlive() || target.Data.Disconnected) @@ -659,6 +670,21 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } + + if (!voter.GetRoleClass().HasVoted && !voter.GetRoleClass().CheckVote(voter, target)) + { + Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch..RoleBase.CheckVote"); + __instance.RpcClearVote(voter.GetClientId()); + if (target != null) + { + // Attempts to set thumbsdown color to the same as playerrole to signify player ability used on (only for modded client) + PlayerVoteArea pva = MeetingHud.Instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); + Color color = Utils.GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); + pva.ThumbsDown.set_color_Injected(ref color); + } + return false; + } + switch (voter.GetCustomRole()) { case CustomRoles.Dictator: @@ -675,20 +701,6 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl return false; } //patch here so checkend is not triggered break; - case CustomRoles.Keeper: - if (!Keeper.OnVotes(voter, target)) - { - __instance.RpcClearVote(voter.GetClientId()); - return false; - } - break; - case CustomRoles.DoubleAgent: - if (!DoubleAgent.OnVotes(voter, target)) - { - __instance.RpcClearVote(voter.GetClientId()); - return false; - } - break; } } @@ -698,7 +710,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl public static void Postfix(MeetingHud __instance) { // Prevent double check end voting - if (GameStates.IsMeeting && MeetingHud.Instance.state == MeetingHud.VoteStates.Discussion) + if (GameStates.IsMeeting && MeetingHud.Instance.state is MeetingHud.VoteStates.Discussion or MeetingHud.VoteStates.NotVoted or MeetingHud.VoteStates.Voted) { __instance.CheckForEndVoting(); //For stuffs in check for end voting to work diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 8fcfe6ef15..1ccc308626 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -1,59 +1,59 @@ -using UnityEngine; -using static TOHE.Options; -using static TOHE.Translator; -using TOHE.Roles.Core; -using TOHE.Roles.Crewmate; -using TOHE.Modules; -using TOHE.Roles.Neutral; -using Hazel; -using InnerNet; -using System; - -namespace TOHE.Roles.Impostor; -internal class DoubleAgent : RoleBase -{ - //===========================SETUP================================\\ - private const int Id = 28600; - private static readonly List playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); - public override bool IsEnable => HasEnabled; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; +using UnityEngine; +using static TOHE.Options; +using static TOHE.Translator; +using TOHE.Roles.Core; +using TOHE.Roles.Crewmate; +using TOHE.Modules; +using TOHE.Roles.Neutral; +using Hazel; +using InnerNet; +using System; + +namespace TOHE.Roles.Impostor; +internal class DoubleAgent : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 28600; + private static readonly List playerIdList = []; + public static bool HasEnabled => playerIdList.Any(); + public override bool IsEnable => HasEnabled; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorSupport; //==================================================================\\ private static readonly List createdButtonsList = []; - private static readonly List CurrentBombedPlayers = []; - private static float CurrentBombedTime = float.MaxValue; - public static bool BombIsActive = false; + private static readonly List CurrentBombedPlayers = []; + private static float CurrentBombedTime = float.MaxValue; + public static bool BombIsActive = false; public static bool CanBombInMeeting = true; public static bool StartedWithMoreThanOneImp = false; public static OptionItem DoubleAgentCanDiffuseBombs; - private static OptionItem ClearBombedOnMeetingCall; - private static OptionItem CanUseAbilityInCalledMeeting; - private static OptionItem BombExplosionTimer; - private static OptionItem ExplosionRadius; + private static OptionItem ClearBombedOnMeetingCall; + private static OptionItem CanUseAbilityInCalledMeeting; + private static OptionItem BombExplosionTimer; + private static OptionItem ExplosionRadius; private static OptionItem ChangeRoleToOnLast; - private enum ChangeRolesSelectOnLast - { - Role_NoChange, - Role_Random, + private enum ChangeRolesSelectOnLast + { + Role_NoChange, + Role_Random, Role_AdmiredImpostor, // Team Crewmate - Role_Traitor, // Team Neutral - Role_Trickster, // Team Impostor as Crewmate - } - public static readonly CustomRoles[] CRoleChangeRoles = - [ - 0, // NoChange - 0, // Random + Role_Traitor, // Team Neutral + Role_Trickster, // Team Impostor as Crewmate + } + public static readonly CustomRoles[] CRoleChangeRoles = + [ + 0, // NoChange + 0, // Random CustomRoles.ImpostorTOHE, // Team Crewmate CustomRoles.Traitor, // Team Neutral - CustomRoles.Trickster, // Team Impostor as Crewmate - ]; - - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.DoubleAgent); + CustomRoles.Trickster, // Team Impostor as Crewmate + ]; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.DoubleAgent); DoubleAgentCanDiffuseBombs = BooleanOptionItem.Create(Id + 10, "DoubleAgentCanDiffuseBombs", true, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); ClearBombedOnMeetingCall = BooleanOptionItem.Create(Id + 11, "DoubleAgentClearBombOnMeetingCall", true, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); CanUseAbilityInCalledMeeting = BooleanOptionItem.Create(Id + 12, "DoubleAgentCanUseAbilityInCalledMeeting", false, TabGroup.ImpostorRoles, false).SetParent(ClearBombedOnMeetingCall); @@ -62,36 +62,36 @@ public override void SetupCustomOption() ExplosionRadius = FloatOptionItem.Create(Id + 14, "DoubleAgentExplosionRadius", new(0.5f, 2f, 0.1f), 1.0f, TabGroup.ImpostorRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]) .SetValueFormat(OptionFormat.Multiplier); - ChangeRoleToOnLast = StringOptionItem.Create(Id + 15, "DoubleAgentChangeRoleTo", EnumHelper.GetAllNames(), 1, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); - } - public override void Init() + ChangeRoleToOnLast = StringOptionItem.Create(Id + 15, "DoubleAgentChangeRoleTo", EnumHelper.GetAllNames(), 1, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.DoubleAgent]); + } + public override void Init() + { + ClearBomb(); + playerIdList.Clear(); + CurrentBombedPlayers.Clear(); + CurrentBombedTime = float.MaxValue; + BombIsActive = false; + StartedWithMoreThanOneImp = false; + CanBombInMeeting = true; + } + + public override void Add(byte playerId) { - ClearBomb(); - playerIdList.Clear(); - CurrentBombedPlayers.Clear(); - CurrentBombedTime = float.MaxValue; - BombIsActive = false; - StartedWithMoreThanOneImp = false; - CanBombInMeeting = true; - } - - public override void Add(byte playerId) - { - playerIdList.Add(playerId); - CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); - if (Main.AllAlivePlayerControls.Count(player => player.Is(Custom_Team.Impostor)) > 1) - StartedWithMoreThanOneImp = true; - } - + playerIdList.Add(playerId); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); + if (Main.AllAlivePlayerControls.Count(player => player.Is(Custom_Team.Impostor)) > 1) + StartedWithMoreThanOneImp = true; + } + public static void ClearBomb() - { - CurrentBombedPlayers.Clear(); - CurrentBombedTime = 999f; + { + CurrentBombedPlayers.Clear(); + CurrentBombedTime = 999f; BombIsActive = false; - } - - // On vent diffuse Bastion & Agitator Bomb if DoubleAgentCanDiffuseBombs is enabled. - // Dev Note: Add role check for OnCoEnterVentOthers and make BombedVents public in Bastion.cs. + } + + // On vent diffuse Bastion & Agitator Bomb if DoubleAgentCanDiffuseBombs is enabled. + // Dev Note: Add role check for OnCoEnterVentOthers and make BombedVents public in Bastion.cs. public override void OnEnterVent(PlayerControl pc, Vent vent) { if (DoubleAgentCanDiffuseBombs.GetBool()) @@ -118,14 +118,11 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) }, 0.5f, "Boot Player from vent: " + vent.Id); } } - } - + } + public override bool CanUseKillButton(PlayerControl pc) => false; - // Use vote for Non-Modded! - // Plant bomb on first vote that isn't another imposter or self. - // Dev Note: Add this to CastVotePatch in MeetingHudPatch.cs like it is for keeper. - public static bool OnVotes(PlayerControl voter, PlayerControl target) + public override bool CheckVote(PlayerControl voter, PlayerControl target) { if (voter.IsModClient()) return true; if (!CanBombInMeeting) return true; @@ -139,8 +136,8 @@ public static bool OnVotes(PlayerControl voter, PlayerControl target) CurrentBombedPlayers.Add(target.PlayerId); BombIsActive = true; return false; - } - return true; + } + return true; } // Clear active bombed players on meeting call if ClearBombedOnMeetingCall is enabled. @@ -161,9 +158,9 @@ public override void AfterMeetingTasks() { CurrentBombedTime = BombExplosionTimer.GetFloat() + 1f; CanBombInMeeting = true; - } - - // If enabled and if DoubleAgent is last Impostor become set role. + } + + // If enabled and if DoubleAgent is last Impostor become set role. public override void OnFixedUpdate(PlayerControl pc) { if (ChangeRoleToOnLast.GetValue() != 0 && StartedWithMoreThanOneImp && GameStates.IsInTask && !GameStates.IsMeeting && !GameStates.IsExilling) @@ -203,9 +200,9 @@ public override void OnFixedUpdate(PlayerControl pc) if (CurrentBombedPlayers.Any(playerId => Utils.GetPlayerById(playerId) == null)) // If playerId is a null Player clear bomb. ClearBomb(); - } - - // Active bomb timer update and check. + } + + // Active bomb timer update and check. private void OnFixedUpdateOthers(PlayerControl player) { if (!CurrentBombedPlayers.Contains(player.PlayerId)) return; @@ -238,12 +235,12 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Item1 != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); } } - } - - // Players go bye bye ¯\_(ツ)_/¯ + } + + // Players go bye bye ¯\_(ツ)_/¯ private void BoomBoom(PlayerControl player) { - if (player.inVent) player.MyPhysics.RpcBootFromVent(GetPlayerVentId(player)); + if (player.inVent) player.MyPhysics.RpcBootFromVent(player.GetPlayerVentId()); foreach (PlayerControl target in Main.AllAlivePlayerControls) // Get players in radius of bomb that are not in a vent. { @@ -272,26 +269,16 @@ private static void PlaySoundForAll(string Sound) } } - // Get vent Id that the player is in. - private static int GetPlayerVentId(PlayerControl pc) - { - if (!(ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Ventilation, out var systemType) && - systemType.TryCast() is VentilationSystem ventilationSystem)) - return 0; - - return ventilationSystem.PlayersInsideVents.TryGetValue(pc.PlayerId, out var playerIdVentId) ? playerIdVentId : 0; - } - - // Set bomb mark on player. + // Set bomb mark on player. public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { if (seen == null ) return string.Empty; if (CurrentBombedPlayers.Contains(seen.PlayerId)) return Utils.ColorString(Color.red, "Ⓑ"); // L Rizz :) return string.Empty; - } - - - // Set timer for Double Agent Modded Clients. + } + + + // Set timer for Double Agent Modded Clients. public override string GetLowerText(PlayerControl player, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { if (player == null) return string.Empty; @@ -300,72 +287,72 @@ public override string GetLowerText(PlayerControl player, PlayerControl seen = n } // Send bomb timer to Modded Clients when active. - private void SendRPC() - { - var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.None, -1); - writer.WriteNetObject(_Player); - writer.Write((int)CurrentBombedTime); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - // Receive and set bomb timer from Host when active. - public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) - { - CurrentBombedTime = reader.ReadInt32(); + private void SendRPC() + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.None, -1); + writer.WriteNetObject(_Player); + writer.Write((int)CurrentBombedTime); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + // Receive and set bomb timer from Host when active. + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + CurrentBombedTime = reader.ReadInt32(); } // Use button for Modded! - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - class StartMeetingPatch - { - public static void Postfix(MeetingHud __instance) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.DoubleAgent) && PlayerControl.LocalPlayer.IsAlive() && CanBombInMeeting && !BombIsActive) - CreatePlantBombButton(__instance); - } - } - public static void CreatePlantBombButton(MeetingHud __instance) - { - foreach (var pva in __instance.playerStates) - { - var pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; - if (pc.GetCustomRole().GetCustomRoleTeam() == Custom_Team.Impostor || PlayerControl.LocalPlayer == pc) continue; - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = UnityEngine.Object.Instantiate(template, pva.transform); - targetBox.name = "PlantBombButton"; + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.DoubleAgent) && PlayerControl.LocalPlayer.IsAlive() && CanBombInMeeting && !BombIsActive) + CreatePlantBombButton(__instance); + } + } + public static void CreatePlantBombButton(MeetingHud __instance) + { + foreach (var pva in __instance.playerStates) + { + var pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + if (pc.GetCustomRole().GetCustomRoleTeam() == Custom_Team.Impostor || PlayerControl.LocalPlayer == pc) continue; + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = UnityEngine.Object.Instantiate(template, pva.transform); + targetBox.name = "PlantBombButton"; targetBox.transform.localPosition = new Vector3(-0.35f, 0.03f, -1.31f); - createdButtonsList.Add(targetBox); - SpriteRenderer renderer = targetBox.GetComponent(); - renderer.sprite = CustomButton.Get("DoubleAgentPocketBomb"); - PassiveButton button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => DestroyButtons(targetBox))); + createdButtonsList.Add(targetBox); + SpriteRenderer renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("DoubleAgentPocketBomb"); + PassiveButton button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => DestroyButtons(targetBox))); button.OnClick.AddListener((Action)(() => PlantBombOnClick(pva.TargetPlayerId /*, __instance*/))); - button.OnClick.AddListener((Action)(() => CustomSoundsManager.Play("Line"))); - } + button.OnClick.AddListener((Action)(() => CustomSoundsManager.Play("Line"))); + } } - private static void PlantBombOnClick(byte targetId /*, MeetingHud __instance*/) + private static void PlantBombOnClick(byte targetId /*, MeetingHud __instance*/) { if (BombIsActive) return; CurrentBombedTime = 999f; CurrentBombedPlayers.Add(targetId); - BombIsActive = true; - } - + BombIsActive = true; + } + private static void DestroyButtons(GameObject pressedButton) { - foreach (var button in createdButtonsList.Where(button => button != pressedButton)) + foreach (var button in createdButtonsList.Where(button => button != pressedButton)) UnityEngine.Object.Destroy(button); createdButtonsList.Clear(); pressedButton.GetComponent().enabled = false; - Transform highlightTransform = pressedButton.transform.Find("ControllerHighlight"); - GameObject highlightObject = highlightTransform?.gameObject; + Transform highlightTransform = pressedButton.transform.Find("ControllerHighlight"); + GameObject highlightObject = highlightTransform?.gameObject; highlightObject?.SetActive(false); - } + } } // FieryFlower was here ඞ \ No newline at end of file From fa6ae7398cfc0accbb00c5d3d4285fcbe7664e61 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:11:44 -0400 Subject: [PATCH 159/778] yeah --- Roles/Neutral/Baker.cs | 2 +- Roles/Neutral/Berserker.cs | 2 +- Roles/Neutral/SoulCollector.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 8db1a72d3d..009f01d857 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -371,7 +371,7 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { - if (TransformedNeutralApocalypseCanBeGuessed.GetBool()) + if (!TransformedNeutralApocalypseCanBeGuessed.GetBool()) { guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); return true; diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 48a1852429..ba287c2e9c 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -195,7 +195,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { - if (TransformedNeutralApocalypseCanBeGuessed.GetBool()) + if (!TransformedNeutralApocalypseCanBeGuessed.GetBool()) { guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); return true; diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 0b0955dc34..fe943b1b38 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -225,7 +225,7 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { - if (TransformedNeutralApocalypseCanBeGuessed.GetBool()) + if (!TransformedNeutralApocalypseCanBeGuessed.GetBool()) { guesser.ShowInfoMessage(isUI, GetString("GuessImmune")); return true; From 216d5c03fad1e98d6c6dadd527aa847b87278430 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:18:19 -0400 Subject: [PATCH 160/778] /apocalypseinfo --- Patches/ChatCommandPatch.cs | 9 ++++++++- Patches/MeetingHudPatch.cs | 2 +- Resources/Lang/en_US.json | 2 ++ Roles/Neutral/Berserker.cs | 6 +++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index f870806ed5..6b63fb1086 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -181,7 +181,10 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); break; - + case "/apocalypseinfo": + canceled = true; + Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; case "/rn": case "/rename": @@ -2010,6 +2013,10 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); break; + case "/apocalypseinfo": + Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + case "/rn": case "/rename": case "/renomear": diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9255754d47..71acb6018a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -905,7 +905,7 @@ public static void NotifyRoleSkillOnMeetingStart() }; if (roleMessage != "") - AddMsg(roleMessage, 255, Utils.ColorString(Utils.GetRoleColor(role), GetString("ApocalypseIsNigh"))); + AddMsg(roleMessage, 255, ColorString(GetRoleColor(role), GetString("ApocalypseIsNigh"))); }, 3f, $"{role} Apocalypse Notify"); } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 2bf4262054..724927c936 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2268,6 +2268,8 @@ "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", "Message.MaxPlayers": "Maximum players set to ", "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", + "ApocalypseInfoTitle": "Neutral Apocalypse Info:", + "Message.ApocalypseInfo": "Every role of the <#ff174f>Apocalypse Team has their own objective to carry out in order to transform.\n<#2B0804>Transformed <#ff174f>Apocalypse members have a drastic change on the game and are immortal (except for being voted), but everyone will be notified that they have transformed.\n\nRoles: <#e5f6b4>Plaguebearer, <#A675A1>Soul Collector, <#bf9f7a>Baker, <#cc0044>Berserker\nTransformed: <#343136>Pestilence, <#644661>Death, <#83461c>Famine, <#2B0804>War\n\nApocalypse members can see eachother's roles and ability icons.\nLike Neutral Killers, Apocalypse members keep the game going as well, have fun!", "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 48a1852429..29747c16f6 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -105,12 +105,12 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (BerserkerKillMax[killer.PlayerId] < BerserkerMax.GetInt()) { BerserkerKillMax[killer.PlayerId]++; - killer.Notify(string.Format(Translator.GetString("BerserkerLevelChanged"), BerserkerKillMax[killer.PlayerId])); + killer.Notify(string.Format(GetString("BerserkerLevelChanged"), BerserkerKillMax[killer.PlayerId])); Logger.Info($"Increased the lvl to {BerserkerKillMax[killer.PlayerId]}", "CULTIVATOR"); } else { - killer.Notify(Translator.GetString("BerserkerMaxReached")); + killer.Notify(GetString("BerserkerMaxReached")); Logger.Info($"Max level reached lvl = {BerserkerKillMax[killer.PlayerId]}", "CULTIVATOR"); } @@ -131,7 +131,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t target.SetRealKiller(killer); killer.SetKillCooldownV2(); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Berserker), Translator.GetString("KilledByBerserker"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Berserker), GetString("KilledByBerserker"))); noScav = false; } From 310bc082d96859cfec7a6b3dc902f457de5f548b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 6 Aug 2024 11:42:04 +0800 Subject: [PATCH 161/778] Some fix --- Modules/RPC.cs | 4 +-- Modules/Utils.cs | 6 ++--- Patches/ExilePatch.cs | 2 +- Patches/IntroPatch.cs | 44 +++++++++++++++---------------- Patches/PlayerJoinAndLeftPatch.cs | 13 ++++----- Roles/Neutral/Baker.cs | 11 +++++--- Roles/Neutral/Berserker.cs | 5 ++-- Roles/Neutral/SoulCollector.cs | 21 +++++++++------ 8 files changed, 55 insertions(+), 51 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 99d4343fc1..90dff4e382 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -849,8 +849,8 @@ public static void SendDeathReason(byte playerId, PlayerState.DeathReason deathR public static void GetDeathReason(MessageReader reader) { var playerId = reader.ReadByte(); - var deathReason = (PlayerState.DeathReason)reader.ReadInt32(); - Main.PlayerStates[playerId].deathReason = deathReason; + var deathReason = reader.ReadInt32(); + Main.PlayerStates[playerId].deathReason = (PlayerState.DeathReason)deathReason; Main.PlayerStates[playerId].IsDead = true; } public static void ForceEndGame(CustomWinner win) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 1ac5c8b1cb..206fe955bc 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1712,9 +1712,8 @@ public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => private static readonly StringBuilder TargetMark = new(20); public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || OnPlayerLeftPatch.StartingProcessing) return; if (Main.AllPlayerControls == null) return; - if (GameStates.IsHideNSeek) return; //Do not update NotifyRoles during meetings if (GameStates.IsMeeting && !GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) return; @@ -1729,9 +1728,8 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon } public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost) return Task.CompletedTask; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || OnPlayerLeftPatch.StartingProcessing) return Task.CompletedTask; if (Main.AllPlayerControls == null) return Task.CompletedTask; - if (GameStates.IsHideNSeek) return Task.CompletedTask; //Do not update NotifyRoles during meetings if (GameStates.IsMeeting && !GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) return Task.CompletedTask; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 6bda151cab..7c9182018e 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -19,7 +19,7 @@ public static void Postfix(ExileController __instance) } catch (Exception error) { - Utils.ThrowException(error); + Logger.Error($"Error after exiled: {error}", "WrapUp"); } finally { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 364f292ca8..54be7108d6 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -256,11 +256,23 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections __instance.overlayHandle.color = Palette.ImpostorRed; return false; } - else if (PlayerControl.LocalPlayer.Is(Custom_Team.Neutral) && !role.IsMadmate()) + else if (PlayerControl.LocalPlayer.IsNeutralApocalypse()) + { + var apocTeam = new Il2CppSystem.Collections.Generic.List(); + apocTeam.Add(PlayerControl.LocalPlayer); + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.IsNeutralApocalypse() && pc != PlayerControl.LocalPlayer) + apocTeam.Add(pc); + } + teamToDisplay = apocTeam; + } + else if (PlayerControl.LocalPlayer.Is(Custom_Team.Neutral)) { teamToDisplay = new Il2CppSystem.Collections.Generic.List(); teamToDisplay.Add(PlayerControl.LocalPlayer); } + if (PlayerControl.LocalPlayer.Is(CustomRoles.Executioner)) { var exeTeam = new Il2CppSystem.Collections.Generic.List(); @@ -283,17 +295,6 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections } teamToDisplay = lawyerTeam; } - if (PlayerControl.LocalPlayer.IsNeutralApocalypse()) - { - var apocTeam = new Il2CppSystem.Collections.Generic.List(); - apocTeam.Add(PlayerControl.LocalPlayer); - foreach (var pc in Main.AllAlivePlayerControls) - { - if (pc.IsNeutralApocalypse() && pc != PlayerControl.LocalPlayer) - apocTeam.Add(pc); - } - teamToDisplay = apocTeam; - } return true; } @@ -564,16 +565,15 @@ public static void Postfix() { _ = new LateTask(() => { - Main.UnShapeShifter.Do(x => - { - var PC = Utils.GetPlayerById(x); - var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); - PC.RpcShapeshift(randomplayer, false); - PC.RpcRejectShapeshift(); - PC.ResetPlayerOutfit(force: true); - Main.GameIsLoaded = true; - - }); + Main.UnShapeShifter.Do(x => + { + var PC = Utils.GetPlayerById(x); + var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); + PC.RpcShapeshift(randomplayer, false); + PC.RpcRejectShapeshift(); + PC.ResetPlayerOutfit(force: true); + Main.GameIsLoaded = true; + }); }, 3f, "Set UnShapeShift Button"); } diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index ffb1768a2e..cb644e9959 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -300,8 +300,11 @@ public static void Postfix(/*AmongUsClient __instance,*/ [HarmonyArgument(0)] Cl [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] class OnPlayerLeftPatch { + public static bool StartingProcessing = false; static void Prefix([HarmonyArgument(0)] ClientData data) { + StartingProcessing = true; + if (GameStates.IsInGame) { Main.PlayerStates[data.Character.PlayerId].Disconnected = true; @@ -378,14 +381,6 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client //Utils.DoNotifyRoles(SpecifyTarget: data.Character, ForceLoop: true); } - try - { - if (AmongUsClient.Instance.AmHost) - data.Character.RpcSetName(data.Character.GetRealName(isMeeting: true)); - } - catch - { } - AntiBlackout.OnDisconnect(data.Character.Data); PlayerGameOptionsSender.RemoveSender(data.Character); } @@ -522,6 +517,8 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client Logger.Error(error.ToString(), "OnPlayerLeftPatch.Postfix"); //Logger.SendInGame("Error: " + error.ToString()); } + + StartingProcessing = false; } } [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 8db1a72d3d..b3f034343f 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -65,6 +65,9 @@ public override void Add(byte playerId) CanUseAbility = true; StarvedNonBreaded = false; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); + + if (!Main.ResetCamPlayerList.Contains(playerId)) + Main.ResetCamPlayerList.Add(playerId); } private static (int, int) BreadedPlayerCount(byte playerId) @@ -110,7 +113,7 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); if (BarrierList[seer.PlayerId].Contains(seen.PlayerId)) { sb.Append(ColorString(GetRoleColor(CustomRoles.Baker), "●") + ColorString(GetRoleColor(CustomRoles.Medic), "✚")); @@ -256,7 +259,8 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr } public override void AfterMeetingTasks() { - BarrierList[playerIdList.First()].Clear(); + if (playerIdList.Any()) + BarrierList[playerIdList.First()].Clear(); } public override void OnFixedUpdate(PlayerControl player) { @@ -304,10 +308,9 @@ internal class Famine : RoleBase //==================================================================\\ public override void Add(byte playerId) { - - if (!AmongUsClient.Instance.AmHost) return; if (!Main.ResetCamPlayerList.Contains(playerId)) Main.ResetCamPlayerList.Add(playerId); + CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 48a1852429..b4f6a91b7e 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -81,6 +81,9 @@ public override void Add(byte playerId) { BerserkerKillMax[playerId] = 0; PlayerIds.Add(playerId); + + if (!Main.ResetCamPlayerList.Contains(playerId)) + Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -175,8 +178,6 @@ internal class War : RoleBase public override void Add(byte playerId) { - - if (!AmongUsClient.Instance.AmHost) return; if (!Main.ResetCamPlayerList.Contains(playerId)) Main.ResetCamPlayerList.Add(playerId); } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 0b0955dc34..e65fd2fb8d 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -4,7 +4,6 @@ using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; -using static TOHE.Utils; namespace TOHE.Roles.Neutral; internal class SoulCollector : RoleBase @@ -50,6 +49,9 @@ public override void Add(byte playerId) SoulCollectorPoints.TryAdd(playerId, 0); CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); + + if (!Main.ResetCamPlayerList.Contains(playerId)) + Main.ResetCamPlayerList.Add(playerId); } public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid"); @@ -163,12 +165,17 @@ public override void AfterMeetingTasks() { SoulCollectorTarget[playerId] = byte.MaxValue; } - PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); - if (SoulCollectorPoints[sc.PlayerId] >= SoulCollectorPointsOpt.GetInt() && !sc.Is(CustomRoles.Death)) + if (playerIdList.Any()) { - sc.RpcSetCustomRole(CustomRoles.Death); - sc.Notify(GetString("SoulCollectorToDeath")); - sc.RpcGuardAndKill(sc); + PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); + if (sc == null) return; + + if (SoulCollectorPoints[sc.PlayerId] >= SoulCollectorPointsOpt.GetInt() && !sc.Is(CustomRoles.Death)) + { + sc.RpcSetCustomRole(CustomRoles.Death); + sc.Notify(GetString("SoulCollectorToDeath")); + sc.RpcGuardAndKill(sc); + } } } public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) @@ -207,8 +214,6 @@ internal class Death : RoleBase public override void Add(byte playerId) { - - if (!AmongUsClient.Instance.AmHost) return; if (!Main.ResetCamPlayerList.Contains(playerId)) Main.ResetCamPlayerList.Add(playerId); } From be9bef7bf7247ede40115aa35550fc6585e5b447 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:10:22 +0200 Subject: [PATCH 162/778] update --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 176923dde1..558bae375b 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3634,7 +3634,7 @@ "LastMessageReplay": "Last System Message Replay", "Contributor": "Contributor", - "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", + "dbConnect.InitFailure": "Error while connecting to TOHE API, this could be caused by your internett connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", "ImpCanBeSusceptible": "Impostors can become Susceptible", From 50a775a3ec91fce9ee01979405af0777b37ee5ca Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:11:57 +0200 Subject: [PATCH 163/778] update --- Resources/Lang/en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 2bf4262054..9ffd3c68fb 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1313,7 +1313,7 @@ "MenuTitle.TaskSettings": "★ Task Management ★", "MenuTitle.Guessers": "★ Guesser Mode ★", "MenuTitle.GuesserModeRoles": "★ Add-ons for Guesser Mode ★", - + "ShieldPersonDiedFirst": "Shield player who dead first in the last game", "ShowShieldedPlayerToAll": "Reveal shielded player to all", "RemoveShieldOnFirstDead": "Remove shield on first death", @@ -3644,7 +3644,7 @@ "LastMessageReplay": "Last System Message Replay", "Contributor": "Contributor", - "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", + "dbConnect.InitFailure": "Error while connecting to TOHE API, this could be caused by your internett connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", "ImpCanBeSusceptible": "Impostors can become Susceptible", From 0888458f56e950c9dc31d1dcb1c8f327d4c0a35f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:14:34 +0200 Subject: [PATCH 164/778] fix --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9ffd3c68fb..6d485c0cbc 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3644,7 +3644,7 @@ "LastMessageReplay": "Last System Message Replay", "Contributor": "Contributor", - "dbConnect.InitFailure": "Error while connecting to TOHE API, this could be caused by your internett connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", + "dbConnect.InitFailure": "Error while connecting to TOHE API, this could be caused by your internet connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", "ImpCanBeSusceptible": "Impostors can become Susceptible", From 81d35c884e283e84e78ac91723383affd4a5f797 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:20:16 +0200 Subject: [PATCH 165/778] Revert "update" This reverts commit be9bef7bf7247ede40115aa35550fc6585e5b447. --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 558bae375b..176923dde1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3634,7 +3634,7 @@ "LastMessageReplay": "Last System Message Replay", "Contributor": "Contributor", - "dbConnect.InitFailure": "Error while connecting to TOHE API, this could be caused by your internett connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", + "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", "ImpCanBeSusceptible": "Impostors can become Susceptible", From ab302e78be13bc578a7c8fccaaddaf9e52a1fb31 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:03:54 +0200 Subject: [PATCH 166/778] fix --- Modules/dbConnect.cs | 4 ++-- Resources/Lang/en_US.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index 6dec39860d..a2b7086055 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -94,11 +94,11 @@ private static void HandleFailure(FailedConnectReason errorReason) // Show waring message if (GameStates.IsLobby || GameStates.InGame) { - DestroyableSingleton.Instance.ShowPopUp(GetString("dbConnect.InitFailure")); + DestroyableSingleton.Instance.ShowPopUp(GetString("dbConnect.InitFailurePublic")); } else { - DestroyableSingleton.Instance.ShowCustom(GetString("dbConnect.InitFailure")); + DestroyableSingleton.Instance.ShowCustom(GetString("dbConnect.InitFailurePublic")); } } else diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6d485c0cbc..9f008332ac 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3644,7 +3644,8 @@ "LastMessageReplay": "Last System Message Replay", "Contributor": "Contributor", - "dbConnect.InitFailure": "Error while connecting to TOHE API, this could be caused by your internet connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", + "dbConnect.InitFailure": "Error while connecting to TOHE API, please check your network connection and retry login!", + "dbConnect.InitFailurePublic": "Error while connecting to TOHE API, this could be caused by your internet connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", "ImpCanBeSusceptible": "Impostors can become Susceptible", From e683e5f1517c0c2b6e4abc73b3cf1e16417d4687 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 6 Aug 2024 23:23:48 +0800 Subject: [PATCH 167/778] Clear usings & fix null errors --- Modules/Zoom.cs | 4 +- Patches/ChatControlPatch.cs | 3 +- Patches/ControlPatch.cs | 1 - Patches/MeetingHudPatch.cs | 138 +++++++++++++-------------- Roles/(Ghosts)/Impostor/Bloodmoon.cs | 1 - Roles/AddOns/Common/Radar.cs | 2 +- Roles/Impostor/Bomber.cs | 1 - Roles/Impostor/Pitfall.cs | 1 - Roles/Neutral/Baker.cs | 14 +-- Roles/Neutral/PlagueBearer.cs | 2 +- Roles/Neutral/SoulCollector.cs | 2 +- 11 files changed, 80 insertions(+), 89 deletions(-) diff --git a/Modules/Zoom.cs b/Modules/Zoom.cs index bc0bcdfcc8..acbc64762e 100644 --- a/Modules/Zoom.cs +++ b/Modules/Zoom.cs @@ -78,8 +78,8 @@ public static void OnFixedUpdate() public static class Flag { - private static List OneTimeList = []; - private static List FirstRunList = []; + private static readonly List OneTimeList = []; + private static readonly List FirstRunList = []; public static void Run(Action action, string type, bool firstrun = false) { if (OneTimeList.Contains(type) || (firstrun && !FirstRunList.Contains(type))) diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs index c24ec85d57..a6296833d6 100644 --- a/Patches/ChatControlPatch.cs +++ b/Patches/ChatControlPatch.cs @@ -1,5 +1,4 @@ using AmongUs.Data; -using System; using UnityEngine; namespace TOHE; @@ -12,7 +11,7 @@ class ChatControllerUpdatePatch public static void Prefix() { if (AmongUsClient.Instance.AmHost && DataManager.Settings.Multiplayer.ChatMode == InnerNet.QuickChatModes.QuickChatOnly) - DataManager.Settings.Multiplayer.ChatMode = InnerNet.QuickChatModes.FreeChatOrQuickChat; //コマンドを打つためにホストのみ常時フリーチャット開放 + DataManager.Settings.Multiplayer.ChatMode = InnerNet.QuickChatModes.FreeChatOrQuickChat; } public static void Postfix(ChatController __instance) { diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index fc896ec83c..3b247643fd 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -1,4 +1,3 @@ -using Hazel; using System; using System.Text; using TOHE.Modules; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9255754d47..afd5c1e525 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -41,7 +41,7 @@ public static bool Prefix(MeetingHud __instance) foreach (var pva in __instance.playerStates) { if (pva == null) continue; - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + PlayerControl pc = GetPlayerById(pva.TargetPlayerId); if (pc == null) continue; if (pva.DidVote && pc.PlayerId == pva.VotedFor && pva.VotedFor < 253 && !pc.Data.IsDead) @@ -51,14 +51,14 @@ public static bool Prefix(MeetingHud __instance) Main.MadmateNum++; pc.RpcSetCustomRole(CustomRoles.Madmate); ExtendedPlayerControl.RpcSetCustomRole(pc.PlayerId, CustomRoles.Madmate); - Utils.NotifyRoles(isForMeeting: true, SpecifySeer: pc, NoCache: true); + NotifyRoles(isForMeeting: true, SpecifySeer: pc, NoCache: true); Logger.Info($"Assign in meeting by self vote: {pc?.Data?.PlayerName} = {pc.GetCustomRole()} + {CustomRoles.Madmate}", "Madmate"); } } if (Dictator.CheckVotingForTarget(pc, pva)) { - var voteTarget = Utils.GetPlayerById(pva.VotedFor); + var voteTarget = GetPlayerById(pva.VotedFor); TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, pc.PlayerId); statesList.Add(new() @@ -112,11 +112,11 @@ public static bool Prefix(MeetingHud __instance) if (pva.DidVote && pva.VotedFor < 253 && pc.IsAlive()) { - var voteTarget = Utils.GetPlayerById(pva.VotedFor); + var voteTarget = GetPlayerById(pva.VotedFor); if (voteTarget == null || !voteTarget.IsAlive() || voteTarget.Data.Disconnected) { - Utils.SendMessage(GetString("VoteDead"), pc.PlayerId); + SendMessage(GetString("VoteDead"), pc.PlayerId); __instance.UpdateButtons(); __instance.RpcClearVote(pc.GetClientId()); Swapper.CheckSwapperTarget(pva.VotedFor); @@ -141,7 +141,7 @@ public static bool Prefix(MeetingHud __instance) foreach (var ps in __instance.playerStates) { //Players who are not dead have not voted - if (!ps.DidVote && Utils.GetPlayerById(ps.TargetPlayerId)?.IsAlive() == true) + if (!ps.DidVote && GetPlayerById(ps.TargetPlayerId)?.IsAlive() == true) { return false; } @@ -154,8 +154,8 @@ public static bool Prefix(MeetingHud __instance) foreach (var ps in __instance.playerStates) { if (ps == null) continue; - voteLog.Info(string.Format("{0,-2}{1}:{2,-3}{3}", ps.TargetPlayerId, Utils.PadRightV2($"({Utils.GetVoteName(ps.TargetPlayerId)})", 40), ps.VotedFor, $"({Utils.GetVoteName(ps.VotedFor)})")); - var voter = Utils.GetPlayerById(ps.TargetPlayerId); + voteLog.Info(string.Format("{0,-2}{1}:{2,-3}{3}", ps.TargetPlayerId, $"({GetVoteName(ps.TargetPlayerId)})".PadRightV2(40), ps.VotedFor, $"({GetVoteName(ps.VotedFor)})")); + var voter = GetPlayerById(ps.TargetPlayerId); if (voter == null || voter.Data == null || voter.Data.Disconnected) continue; if (Options.VoteMode.GetBool()) { @@ -201,7 +201,7 @@ public static bool Prefix(MeetingHud __instance) } } - var player = Utils.GetPlayerById(ps.TargetPlayerId); + var player = GetPlayerById(ps.TargetPlayerId); var playerRoleClass = player.GetRoleClass(); //Hides vote @@ -251,7 +251,7 @@ public static bool Prefix(MeetingHud __instance) for (int i = 0; i < statesList.Count; i++) { var voterstate = statesList[i]; - var voterpc = Utils.GetPlayerById(voterstate.VoterId); + var voterpc = GetPlayerById(voterstate.VoterId); if (voterpc == null || !voterpc.IsAlive()) continue; var voterpva = GetPlayerVoteArea(voterstate.VoterId); if (voterpva.VotedFor != voterstate.VotedForId) @@ -276,24 +276,24 @@ public static bool Prefix(MeetingHud __instance) voteLog.Info("=========Vote Result========="); foreach (var data in VotingData) { - voteLog.Info($"{Utils.GetVoteName(data.Key)}({data.Key}): {data.Value} votes"); + voteLog.Info($"{GetVoteName(data.Key)}({data.Key}): {data.Value} votes"); if (data.Value > max) { - voteLog.Info($"{Utils.GetVoteName(data.Key)}({data.Key}) have a higher number of votes ({data.Value})"); + voteLog.Info($"{GetVoteName(data.Key)}({data.Key}) have a higher number of votes ({data.Value})"); exileId = data.Key; max = data.Value; tie = false; } else if (data.Value == max) { - voteLog.Info($"{Utils.GetVoteName(data.Key)}({data.Key}) has the same number of votes as {Utils.GetVoteName(exileId)}({exileId}) - Count: {data.Value}"); + voteLog.Info($"{GetVoteName(data.Key)}({data.Key}) has the same number of votes as {GetVoteName(exileId)}({exileId}) - Count: {data.Value}"); exileId = byte.MaxValue; tie = true; } - voteLog.Info($"Exiled ID: {exileId} ({Utils.GetVoteName(exileId)}), max: {max} votes"); + voteLog.Info($"Exiled ID: {exileId} ({GetVoteName(exileId)}), max: {max} votes"); } - voteLog.Info($"Decision to exiled a player: {exileId} ({Utils.GetVoteName(exileId)})"); + voteLog.Info($"Decision to exiled a player: {exileId} ({GetVoteName(exileId)})"); bool braked = false; if (tie) @@ -314,12 +314,12 @@ public static bool Prefix(MeetingHud __instance) if (target != byte.MaxValue) { Logger.Info("Flat breakers cover expulsion of players", "Tiebreaker Vote"); - exiledPlayer = Utils.GetPlayerInfoById(target); + exiledPlayer = GetPlayerInfoById(target); tie = false; braked = true; } } - List CollectorCL = Utils.GetRoleBasesByType()?.ToList(); + List CollectorCL = GetRoleBasesByType()?.ToList(); if (Collector.HasEnabled) CollectorCL?.Do(x => { x.CollectAmount(VotingData, __instance); }); if (Options.VoteMode.GetBool() && Options.WhenTie.GetBool() && tie) @@ -332,7 +332,7 @@ public static bool Prefix(MeetingHud __instance) case TieMode.All: var exileIds = VotingData.Where(x => x.Key < 15 && x.Value == max).Select(kvp => kvp.Key).ToArray(); foreach (var playerId in exileIds) - Utils.GetPlayerById(playerId).SetRealKiller(null); + GetPlayerById(playerId).SetRealKiller(null); TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Vote, exileIds); exiledPlayer = null; break; @@ -348,7 +348,7 @@ public static bool Prefix(MeetingHud __instance) if (Keeper.IsTargetExiled(exileId)) { exileId = 0xff; - exiledPlayer = Utils.GetPlayerInfoById(exileId); + exiledPlayer = GetPlayerInfoById(exileId); } exiledPlayer?.Object.SetRealKiller(null); @@ -406,19 +406,19 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti var realName = exiledPlayer.Object.GetRealName(isMeeting: true); Main.LastVotedPlayer = realName; - var player = Utils.GetPlayerById(exiledPlayer.PlayerId); + var player = GetPlayerById(exiledPlayer.PlayerId); var role = GetString(exiledPlayer.GetCustomRole().ToString()); var crole = exiledPlayer.GetCustomRole(); - var coloredRole = Utils.GetDisplayRoleAndSubName(exileId, exileId, true); + var coloredRole = GetDisplayRoleAndSubName(exileId, exileId, true); if (Options.ConfirmEgoistOnEject.GetBool() && player.Is(CustomRoles.Egoist)) - coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Egoist), coloredRole.RemoveHtmlTags()); + coloredRole = ColorString(GetRoleColor(CustomRoles.Egoist), coloredRole.RemoveHtmlTags()); if (Options.ConfirmLoversOnEject.GetBool() && player.Is(CustomRoles.Lovers)) - coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), coloredRole.RemoveHtmlTags()); + coloredRole = ColorString(GetRoleColor(CustomRoles.Lovers), coloredRole.RemoveHtmlTags()); if (Rascal.AppearAsMadmate(player)) - coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetRoleString("Mad-") + coloredRole.RemoveHtmlTags()); + coloredRole = ColorString(GetRoleColor(CustomRoles.Madmate), GetRoleString("Mad-") + coloredRole.RemoveHtmlTags()); var name = ""; int impnum = 0; @@ -448,13 +448,13 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti break; case 1: if (player.GetCustomRole().IsImpostor() || player.Is(CustomRoles.Parasite) || player.Is(CustomRoles.Crewpostor) || player.Is(CustomRoles.Refugee)) - name = string.Format(GetString("BelongTo"), realName, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("TeamImpostor"))); + name = string.Format(GetString("BelongTo"), realName, ColorString(GetRoleColor(CustomRoles.Impostor), GetString("TeamImpostor"))); else if (player.GetCustomRole().IsCrewmate()) name = string.Format(GetString("IsGood"), realName); else if (player.GetCustomRole().IsNeutral() && !player.Is(CustomRoles.Parasite) && !player.Is(CustomRoles.Refugee) && !player.Is(CustomRoles.Crewpostor)) - name = string.Format(GetString("BelongTo"), realName, Utils.ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral"))); + name = string.Format(GetString("BelongTo"), realName, ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral"))); break; case 2: @@ -463,11 +463,11 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti { name += " ("; if (player.GetCustomRole().IsImpostor() || player.Is(CustomRoles.Madmate)) - name += Utils.ColorString(new Color32(255, 25, 25, byte.MaxValue), GetString("TeamImpostor")); + name += ColorString(new Color32(255, 25, 25, byte.MaxValue), GetString("TeamImpostor")); else if (player.GetCustomRole().IsNeutral() || player.Is(CustomRoles.Charmed)) - name += Utils.ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral")); + name += ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral")); else if (player.GetCustomRole().IsCrewmate()) - name += Utils.ColorString(new Color32(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")); + name += ColorString(new Color32(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")); name += ")"; } break; @@ -506,7 +506,7 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti Main.DoBlockNameChange = true; if (GameStates.IsInGame) { - exiledPlayer.UpdateName(name, Utils.GetClientById(exiledPlayer.ClientId)); + exiledPlayer.UpdateName(name, GetClientById(exiledPlayer.ClientId)); player?.RpcSetName(name); } } @@ -529,7 +529,7 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti if (GameStates.IsInGame && player.Data.Disconnected) { player.Data.PlayerName = realName; - exiledPlayer.UpdateName(realName, Utils.GetClientById(exiledPlayer.ClientId)); + exiledPlayer.UpdateName(realName, GetClientById(exiledPlayer.ClientId)); //Await Next Send Data or Next Meeting } } @@ -578,7 +578,7 @@ public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathR var AddedIdList = new List(); foreach (var playerId in playerIds) { - var pc = Utils.GetPlayerById(playerId); + var pc = GetPlayerById(playerId); if (pc == null) return; if (pc.Is(CustomRoles.Susceptible)) { @@ -611,7 +611,7 @@ private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, pa } private static void RevengeOnExile(byte playerId/*, PlayerState.DeathReason deathReason*/) { - var player = Utils.GetPlayerById(playerId); + var player = GetPlayerById(playerId); if (player == null) return; var target = PickRevengeTarget(player); if (target == null) return; @@ -641,7 +641,7 @@ class CastVotePatch public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPlayerId, [HarmonyArgument(1)] byte suspectPlayerId) { if (!AmongUsClient.Instance.AmHost) return true; - var voter = Utils.GetPlayerById(srcPlayerId); + var voter = GetPlayerById(srcPlayerId); if (voter == null || !voter.IsAlive()) return false; var target = GetPlayerById(suspectPlayerId); @@ -745,7 +745,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta int VoteNum = 1; // Judgment only when voting for a valid player - var target = Utils.GetPlayerById(ps.VotedFor); + var target = GetPlayerById(ps.VotedFor); if (target != null) { // Remove all votes for Zombie @@ -762,7 +762,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta } //Add votes for roles - var pc = Utils.GetPlayerById(ps.TargetPlayerId); + var pc = GetPlayerById(ps.TargetPlayerId); if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, pc.GetCustomRole()) && ps.TargetPlayerId != ps.VotedFor && ps != null) VoteNum += ps.TargetPlayerId.GetRoleClassById().AddRealVotesNum(ps); // returns + 0 or given role value (+/-) @@ -831,16 +831,16 @@ public static void NotifyRoleSkillOnMeetingStart() var title = $"" + role.GetRoleTitle() + "\n"; var Conf = new StringBuilder(); var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); + var rlHex = GetRoleColorCode(role); var SubTitle = $"" + GetString("YourAddon") + "\n"; if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); + ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); var cleared = Conf.ToString(); var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); foreach (var subRole in Main.PlayerStates[pc.PlayerId].SubRoles.ToArray()) - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + Sub.Append($"\n\n" + $"" + subRole.GetRoleTitle() + subRole.GetInfoLong() + ""); if (Sub.ToString() != string.Empty) { @@ -860,7 +860,7 @@ public static void NotifyRoleSkillOnMeetingStart() var msgTemp = msgToSend.ToList(); _ = new LateTask(() => { - msgTemp.Do(x => Utils.SendMessage(x.Item1, x.Item2, x.Item3)); + msgTemp.Do(x => SendMessage(x.Item1, x.Item2, x.Item3)); }, 3f, "Skill Description First Meeting"); } @@ -880,12 +880,12 @@ public static void NotifyRoleSkillOnMeetingStart() List baitAliveList = []; foreach (var whId in Bait.BaitAlive.ToArray()) { - PlayerControl whpc = Utils.GetPlayerById(whId); + PlayerControl whpc = GetPlayerById(whId); if (whpc == null) continue; baitAliveList.Add(whpc.GetRealName()); } string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; - AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); + AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, ColorString(GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); } // Apocalypse Notify, thanks tommy var transformRoles = new CustomRoles[] { CustomRoles.Pestilence, CustomRoles.War, CustomRoles.Famine, CustomRoles.Death }; @@ -905,7 +905,7 @@ public static void NotifyRoleSkillOnMeetingStart() }; if (roleMessage != "") - AddMsg(roleMessage, 255, Utils.ColorString(Utils.GetRoleColor(role), GetString("ApocalypseIsNigh"))); + AddMsg(roleMessage, 255, ColorString(GetRoleColor(role), GetString("ApocalypseIsNigh"))); }, 3f, $"{role} Apocalypse Notify"); } @@ -923,12 +923,12 @@ public static void NotifyRoleSkillOnMeetingStart() if (!Cyber.NeutralKnowCyberDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; if (!Cyber.CrewKnowCyberDead.GetBool() && pc.GetCustomRole().IsCrewmate()) continue; - AddMsg(string.Format(GetString("CyberDead"), Main.AllPlayerNames[csId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cyber), GetString("CyberNewsTitle"))); + AddMsg(string.Format(GetString("CyberDead"), Main.AllPlayerNames[csId]), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Cyber), GetString("CyberNewsTitle"))); } // Sleuth notify msg if (Sleuth.SleuthNotify.ContainsKey(pc.PlayerId)) - AddMsg(Sleuth.SleuthNotify[pc.PlayerId], pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sleuth), GetString("SleuthNoticeTitle"))); + AddMsg(Sleuth.SleuthNotify[pc.PlayerId], pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Sleuth), GetString("SleuthNoticeTitle"))); // Check Mimic kill if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive()) @@ -943,7 +943,7 @@ public static void NotifyRoleSkillOnMeetingStart() var isImpostorTeamList = Main.AllPlayerControls.Where(x => x.GetCustomRole().IsImpostorTeam()).ToArray(); foreach (var imp in isImpostorTeamList) { - AddMsg(MimicMsg, imp.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mimic), GetString("MimicMsgTitle"))); + AddMsg(MimicMsg, imp.PlayerId, ColorString(GetRoleColor(CustomRoles.Mimic), GetString("MimicMsgTitle"))); } } @@ -952,7 +952,7 @@ public static void NotifyRoleSkillOnMeetingStart() // Send message _ = new LateTask(() => { - msgToSend.Do(x => Utils.SendMessage(x.Item1, x.Item2, x.Item3)); + msgToSend.Do(x => SendMessage(x.Item1, x.Item2, x.Item3)); }, 3f, "Skill Notice On Meeting Start"); Main.PlayerStates.Do(x @@ -965,7 +965,7 @@ public static void Prefix(/*MeetingHud __instance*/) { Logger.Info("------------Opening of the session------------", "Phase"); ChatUpdatePatch.DoBlockChat = true; - GameStates.AlreadyDied |= !Utils.IsAllAlive; + GameStates.AlreadyDied |= !IsAllAlive; Main.AllPlayerControls.Do(x => ReportDeadBodyPatch.WaitReport[x.PlayerId].Clear()); MeetingStates.MeetingCalled = true; } @@ -980,15 +980,15 @@ public static void Postfix(MeetingHud __instance) foreach (var pva in __instance.playerStates) { - var pc = Utils.GetPlayerById(pva.TargetPlayerId); + var pc = GetPlayerById(pva.TargetPlayerId); if (pc == null) continue; - var RoleTextData = Utils.GetRoleAndSubText(PlayerControl.LocalPlayer.PlayerId, pc.PlayerId); + var RoleTextData = GetRoleAndSubText(PlayerControl.LocalPlayer.PlayerId, pc.PlayerId); var roleTextMeeting = UnityEngine.Object.Instantiate(pva.NameText); roleTextMeeting.transform.SetParent(pva.NameText.transform); roleTextMeeting.transform.localPosition = new Vector3(0f, -0.18f, 0f); roleTextMeeting.fontSize = 1.6f; roleTextMeeting.text = RoleTextData.Item1; - if (Main.VisibleTasksCount) roleTextMeeting.text += Utils.GetProgressText(pc); + if (Main.VisibleTasksCount) roleTextMeeting.text += GetProgressText(pc); roleTextMeeting.color = RoleTextData.Item2; roleTextMeeting.gameObject.name = "RoleTextMeeting"; roleTextMeeting.enableWordWrapping = false; @@ -1013,7 +1013,7 @@ public static void Postfix(MeetingHud __instance) // If Doppelganger.CurrentVictimCanSeeRolesAsDead is disabled and player is the most recent victim from the doppelganger hide role information for player. var player = PlayerControl.LocalPlayer; - var target = Utils.GetPlayerById(pva.TargetPlayerId); + var target = GetPlayerById(pva.TargetPlayerId); if (suffixBuilder.Length > 0) { @@ -1024,7 +1024,7 @@ public static void Postfix(MeetingHud __instance) if (Options.SyncButtonMode.GetBool()) { - Utils.SendMessage(string.Format(GetString("Message.SyncButtonLeft"), Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount)); + SendMessage(string.Format(GetString("Message.SyncButtonLeft"), Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount)); Logger.Info("紧急会议剩余 " + (Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) + " 次使用次数", "SyncButtonMode"); } @@ -1033,7 +1033,7 @@ public static void Postfix(MeetingHud __instance) { _ = new LateTask(() => { - Utils.SendMessage(GetString("Warning.AntiBlackoutProtectionMsg"), 255, Utils.ColorString(Color.blue, GetString("AntiBlackoutProtectionTitle")), noReplay: true); + SendMessage(GetString("Warning.AntiBlackoutProtectionMsg"), 255, ColorString(Color.blue, GetString("AntiBlackoutProtectionTitle")), noReplay: true); }, 5f, "Warning BlackOut Is Active"); } @@ -1046,7 +1046,7 @@ public static void Postfix(MeetingHud __instance) AntiBlackout.StoreExiledMessage = GetString("Warning.ShowAntiBlackExiledPlayer") + AntiBlackout.StoreExiledMessage; _ = new LateTask(() => { - Utils.SendMessage(AntiBlackout.StoreExiledMessage, 255, Utils.ColorString(Color.red, GetString("DefaultSystemMessageTitle")), noReplay: true); + SendMessage(AntiBlackout.StoreExiledMessage, 255, ColorString(Color.red, GetString("DefaultSystemMessageTitle")), noReplay: true); AntiBlackout.StoreExiledMessage = ""; }, 5.5f, "AntiBlackout.StoreExiledMessage"); } @@ -1056,7 +1056,7 @@ public static void Postfix(MeetingHud __instance) //{ // _ = new LateTask(() => // { - // Utils.SendMessage(GetString("Warning.BrokenVentsInDleksMessage"), title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceMini), GetString("WarningTitle")), replay: true); + // SendMessage(GetString("Warning.BrokenVentsInDleksMessage"), title: ColorString(GetRoleColor(CustomRoles.NiceMini), GetString("WarningTitle")), replay: true); // }, 6f, "Message: Warning Broken Vents In Dleks"); //} @@ -1084,7 +1084,7 @@ public static void Postfix(MeetingHud __instance) if (pva == null) continue; PlayerControl seer = PlayerControl.LocalPlayer; var seerRoleClass = seer.GetRoleClass(); - PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); + PlayerControl target = GetPlayerById(pva.TargetPlayerId); if (target == null) continue; @@ -1098,24 +1098,24 @@ public static void Postfix(MeetingHud __instance) { if (Options.CrewmatesCanGuess.GetBool() && seer.GetCustomRole().IsCrewmate() && !seer.Is(CustomRoles.Judge) && !seer.Is(CustomRoles.Lookout) && !seer.Is(CustomRoles.Swapper) && !seer.Is(CustomRoles.Inspector)) if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; if (Options.ImpostorsCanGuess.GetBool() && (seer.GetCustomRole().IsImpostor() || seer.GetCustomRole().IsMadmate()) && !seer.Is(CustomRoles.Councillor)) if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; if (Options.NeutralKillersCanGuess.GetBool() && seer.GetCustomRole().IsNK()) if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; if (Options.NeutralApocalypseCanGuess.GetBool() && seer.GetCustomRole().IsNA()) if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; } if (seer.KnowDeathReason(target)) - sb.Append($" ({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})"); + sb.Append($" ({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))})"); sb.Append(seerRoleClass?.GetMark(seer, target, true)); sb.Append(CustomRoleManager.GetMarkOthers(seer, target, true)); @@ -1123,7 +1123,7 @@ public static void Postfix(MeetingHud __instance) if (seer.GetCustomRole().IsImpostor() && target.GetPlayerTaskState().IsTaskFinished) { if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate)) - sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); + sb.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); } var tempNemeText = seer.GetRoleClass().PVANameText(pva, seer, target); @@ -1138,7 +1138,7 @@ public static void Postfix(MeetingHud __instance) { case CustomRoles.Guesser: if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Guesser), target.PlayerId.ToString()) + " " + pva.NameText.text; + pva.NameText.text = ColorString(GetRoleColor(CustomRoles.Guesser), target.PlayerId.ToString()) + " " + pva.NameText.text; break; } } @@ -1151,12 +1151,12 @@ public static void Postfix(MeetingHud __instance) case CustomRoles.Lovers: if (seer.Is(CustomRoles.Lovers) || seer.Data.IsDead) { - sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), "♥")); + sb.Append(ColorString(GetRoleColor(CustomRoles.Lovers), "♥")); //isLover = true; } break; case CustomRoles.Cyber when Cyber.CyberKnown.GetBool(): - sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cyber), "★")); + sb.Append(ColorString(GetRoleColor(CustomRoles.Cyber), "★")); break; } } @@ -1187,7 +1187,7 @@ public static void Postfix(MeetingHud __instance) { __instance.playerStates.DoIf(x => x.HighlightedFX.enabled, x => { - var player = Utils.GetPlayerById(x.TargetPlayerId); + var player = GetPlayerById(x.TargetPlayerId); if (player != null && !player.Data.IsDead) { player.SetDeathReason(PlayerState.DeathReason.Execution); @@ -1195,7 +1195,7 @@ public static void Postfix(MeetingHud __instance) player.RpcExileV2(); Main.PlayerStates[player.PlayerId].SetDead(); MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); - Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); Logger.Info($"{player.GetNameWithRole()} was executed", "Execution"); __instance.CheckForEndVoting(); } diff --git a/Roles/(Ghosts)/Impostor/Bloodmoon.cs b/Roles/(Ghosts)/Impostor/Bloodmoon.cs index fdc15f4074..12cb73fa78 100644 --- a/Roles/(Ghosts)/Impostor/Bloodmoon.cs +++ b/Roles/(Ghosts)/Impostor/Bloodmoon.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using Hazel; using TOHE.Roles.Core; using TOHE.Roles.Double; using UnityEngine; diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index 1626ca3c17..becde4d247 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -14,7 +14,7 @@ public static class Radar public static OptionItem CrewCanBeRadar; public static OptionItem NeutralCanBeRadar; - private static Dictionary ClosestPlayer = []; + private static readonly Dictionary ClosestPlayer = []; public static void SetupCustomOptions() { diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index b561435e87..56e4a00508 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -2,7 +2,6 @@ using UnityEngine; using TOHE.Modules; using TOHE.Roles.Crewmate; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Impostor; diff --git a/Roles/Impostor/Pitfall.cs b/Roles/Impostor/Pitfall.cs index ff926c8559..9a548a7b9e 100644 --- a/Roles/Impostor/Pitfall.cs +++ b/Roles/Impostor/Pitfall.cs @@ -4,7 +4,6 @@ using UnityEngine; using static TOHE.Options; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Impostor; diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index b3f034343f..c89c28d18a 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -1,14 +1,10 @@ using AmongUs.GameOptions; using Hazel; -using System.Linq; using System.Text; using TOHE.Roles.Core; -using TOHE.Roles.Impostor; using static TOHE.Options; -using static TOHE.PlayerState; using static TOHE.Translator; using static TOHE.Utils; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE.Roles.Neutral; @@ -29,9 +25,9 @@ internal class Baker : RoleBase private static OptionItem BTOS2Baker; private static byte BreadID = 0; - public static readonly Dictionary> BreadList = []; - public static readonly Dictionary> RevealList = []; - public static readonly Dictionary> BarrierList = []; + private static readonly Dictionary> BreadList = []; + private static readonly Dictionary> RevealList = []; + private static readonly Dictionary> BarrierList = []; public static readonly Dictionary> FamineList = []; private static bool CanUseAbility; public static bool StarvedNonBreaded; @@ -128,7 +124,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo } public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (HasBread(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + if (playerIdList.Any() && HasBread(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) { return ColorString(GetRoleColor(CustomRoles.Baker), "●"); } @@ -222,7 +218,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr else { BreadList[killer.PlayerId].Add(target.PlayerId); SendRPC(killer, target); - Utils.NotifyRoles(SpecifySeer: killer); + NotifyRoles(SpecifySeer: killer); killer.Notify(GetString("BakerBreaded")); Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); CanUseAbility = false; diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index f93e390a12..2c2554f751 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -201,7 +201,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen, bool isFo => IsPlagued(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.PlagueBearer), "⦿") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (IsPlagued(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + if (playerIdList.Any() && IsPlagued(playerIdList.First(), target.PlayerId) && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) { return ColorString(GetRoleColor(CustomRoles.PlagueBearer), "⦿"); } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index e65fd2fb8d..b4328536da 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -89,7 +89,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo => SoulCollectorTarget[seer.PlayerId] == seen.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (SoulCollectorTarget[playerIdList.First()] == target.PlayerId && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + if (playerIdList.Any() && SoulCollectorTarget[playerIdList.First()] == target.PlayerId && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) { return Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠"); } From dbb5c1015bb6142694af9489c57868767ccbfec4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 6 Aug 2024 23:46:31 +0800 Subject: [PATCH 168/778] Set 300 cd for Nemesis if they cannot use kill button --- Roles/Impostor/Nemesis.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Impostor/Nemesis.cs b/Roles/Impostor/Nemesis.cs index 5b3a2f3c14..0c912a9f75 100644 --- a/Roles/Impostor/Nemesis.cs +++ b/Roles/Impostor/Nemesis.cs @@ -190,6 +190,7 @@ public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) NemesisMsgCheck(pc, $"/rv {PlayerId}", true); } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = CheckCanUseKillButton() ? DefaultKillCooldown : 300f; public override bool CanUseKillButton(PlayerControl pc) => CheckCanUseKillButton(); public static bool CheckCanUseKillButton() From 2459d9c44878f393092732e787a8e88879840187 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 6 Aug 2024 23:47:28 +0800 Subject: [PATCH 169/778] Fix F1 show role settings --- Patches/ControlPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 3b247643fd..5a04aa17e5 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -63,8 +63,8 @@ public static void Postfix(/*ControllerManager __instance*/) var lp = PlayerControl.LocalPlayer; var sb = new StringBuilder(); sb.Append(GetString(role.ToString()) + Utils.GetRoleMode(role) + lp.GetRoleInfo(true)); - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref sb, command: true); + //if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + // Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref sb, command: true); HudManager.Instance.ShowPopUp(sb.ToString() + "tohe"); } catch (Exception ex) From 0284d8143fdb2813796aa6e1351b298b39de76fc Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 7 Aug 2024 00:26:04 +0800 Subject: [PATCH 170/778] Use delay clear vote --- Modules/ExtendedPlayerControl.cs | 19 +++++++++++++++++++ Patches/MeetingHudPatch.cs | 31 +++++++++++-------------------- Roles/Crewmate/FortuneTeller.cs | 2 +- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e380d46414..9ffea92a61 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -93,6 +93,25 @@ public static void RpcCastVote(this PlayerControl player, byte suspectIdx) writer.SendMessage(); } } + public static void RpcClearVoteDelay(this MeetingHud meeting, int clientId) + { + _ = new LateTask(() => + { + if (meeting == null) + { + Logger.Info($"Cannot be cleared because meetinghud is null", "RpcClearVoteDelay"); + return; + } + if (AmongUsClient.Instance.ClientId == clientId) + { + meeting.ClearVote(); + return; + } + var writer = CustomRpcSender.Create("Clear Vote", SendOption.Reliable); + writer.AutoStartRpc(meeting.NetId, (byte)RpcCalls.ClearVote, clientId).EndRpc(); + writer.SendMessage(); + }, 0.5f, "Clear Vote"); + } public static int GetClientId(this PlayerControl player) { if (player == null) return -1; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index afd5c1e525..0bad479c5b 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -118,7 +118,7 @@ public static bool Prefix(MeetingHud __instance) { SendMessage(GetString("VoteDead"), pc.PlayerId); __instance.UpdateButtons(); - __instance.RpcClearVote(pc.GetClientId()); + __instance.RpcClearVoteDelay(pc.GetClientId()); Swapper.CheckSwapperTarget(pva.VotedFor); continue; } @@ -638,7 +638,7 @@ private static PlayerControl PickRevengeTarget(PlayerControl exiledplayer)//道 [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CastVote))] class CastVotePatch { - public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPlayerId, [HarmonyArgument(1)] byte suspectPlayerId) + public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectPlayerId) { if (!AmongUsClient.Instance.AmHost) return true; var voter = GetPlayerById(srcPlayerId); @@ -648,7 +648,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (target == null && suspectPlayerId < 253) { SendMessage(GetString("VoteDead"), srcPlayerId); - __instance.RpcClearVote(voter.GetClientId()); + __instance.RpcClearVoteDelay(voter.GetClientId()); return false; } //Vote a disconnect player @@ -659,7 +659,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl { voter.GetRoleClass().HasVoted = true; SendMessage(GetString("VoteNotUseAbility"), voter.PlayerId); - __instance.RpcClearVote(voter.GetClientId()); + __instance.RpcClearVoteDelay(voter.GetClientId()); return false; } } @@ -669,7 +669,7 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (!target.IsAlive() || target.Data.Disconnected) { SendMessage(GetString("VoteDead"), srcPlayerId); - __instance.RpcClearVote(voter.GetClientId()); + __instance.RpcClearVoteDelay(voter.GetClientId()); Swapper.CheckSwapperTarget(suspectPlayerId); return false; } @@ -679,15 +679,12 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl { Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch.RoleBase.CheckVote"); voter.GetRoleClass().HasVoted = true; - __instance.RpcClearVote(voter.GetClientId()); + __instance.RpcClearVoteDelay(voter.GetClientId()); - if (target != null) - { - // Attempts to set thumbsdown color to the same as playerrole to signify player ability used on (only for modded client) - PlayerVoteArea pva = MeetingHud.Instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); - Color color = GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); - pva.ThumbsDown.set_color_Injected(ref color); - } + // Attempts to set thumbsdown color to the same as playerrole to signify player ability used on (only for modded client) + PlayerVoteArea pva = __instance.playerStates.FirstOrDefault(pva => pva.TargetPlayerId == target.PlayerId); + Color color = GetRoleColor(voter.GetCustomRole()).ShadeColor(0.5f); + pva.ThumbsDown.set_color_Injected(ref color); return false; } @@ -697,15 +694,9 @@ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPl if (target.Is(CustomRoles.Solsticer)) { SendMessage(GetString("VoteSolsticer"), srcPlayerId); - __instance.RpcClearVote(voter.GetClientId()); + __instance.RpcClearVoteDelay(voter.GetClientId()); return false; } - if (!target.IsAlive()) - { - SendMessage(GetString("VoteDead"), srcPlayerId); - __instance.RpcClearVote(voter.GetClientId()); - return false; - } //patch here so checkend is not triggered break; } } diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 1ad018f3f1..cca1645c1d 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -158,7 +158,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } else { - List completeRoleList = EnumHelper.Achunk(chunkSize: 6, shuffle: true, exclude: (x) => !x.IsGhostRole() && !x.IsAdditionRole() && x != CustomRoles.NotAssigned); + List completeRoleList = EnumHelper.Achunk(chunkSize: 6, shuffle: true, exclude: (x) => !x.IsGhostRole() && !x.IsAdditionRole() && x != CustomRoles.NotAssigned && x != CustomRoles.ChiefOfPolice); var targetRole = target.GetCustomRole(); string text = string.Empty; From c64f3798493a9ff3b442a63c5de92c4eb0af1357 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 7 Aug 2024 00:26:40 +0800 Subject: [PATCH 171/778] Dev 2 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 4318163166..466ff17a8f 100644 --- a/main.cs +++ b/main.cs @@ -39,12 +39,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0804.210.00010"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Dev 1"; + public const string PluginVersion = "2024.0807.210.00010"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Dev 2"; public const string SupportedVersionAU = "2024.6.18"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Dev 1 + public static readonly bool devRelease = true; // Latest: V2.1.0 Dev 2 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.2 From 814062e3eb26b9a82052d71990627100c1c94792 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 7 Aug 2024 00:41:00 +0800 Subject: [PATCH 172/778] Plugin Version --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index 466ff17a8f..a43c4791b7 100644 --- a/main.cs +++ b/main.cs @@ -39,7 +39,7 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0807.210.00010"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginVersion = "2024.0807.210.00020"; // YEAR.MMDD.VERSION.CANARYDEV public const string PluginDisplayVersion = "2.1.0 Dev 2"; public const string SupportedVersionAU = "2024.6.18"; From 6e12151b808064f0344e41e81646c16dec0c56de Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:00:41 +0200 Subject: [PATCH 173/778] Addonbase + Spurt --- Modules/CustomRolesHelper.cs | 86 +-------- Modules/ExtendedPlayerControl.cs | 1 + Modules/OptionHolder.cs | 249 +++++++------------------- Modules/Utils.cs | 3 + Patches/HudPatch.cs | 4 +- Patches/PlayerControlPatch.cs | 3 + Patches/onGameStartedPatch.cs | 2 + Resources/Lang/en_US.json | 2 + Roles/AddOns/Common/Antidote.cs | 10 +- Roles/AddOns/Common/Autopsy.cs | 15 +- Roles/AddOns/Common/Avanger.cs | 14 +- Roles/AddOns/Common/Aware.cs | 10 +- Roles/AddOns/Common/Bait.cs | 13 +- Roles/AddOns/Common/Beartrap.cs | 21 +-- Roles/AddOns/Common/Bewilder.cs | 13 +- Roles/AddOns/Common/Burst.cs | 13 +- Roles/AddOns/Common/Cyber.cs | 13 +- Roles/AddOns/Common/Diseased.cs | 13 +- Roles/AddOns/Common/DoubleShot.cs | 14 +- Roles/AddOns/Common/Egoist.cs | 5 +- Roles/AddOns/Common/Flash.cs | 7 +- Roles/AddOns/Common/Fool.cs | 15 +- Roles/AddOns/Common/Fragile.cs | 13 +- Roles/AddOns/Common/Glow.cs | 16 +- Roles/AddOns/Common/Gravestone.cs | 14 +- Roles/AddOns/Common/Guesser.cs | 5 +- Roles/AddOns/Common/Influenced.cs | 13 +- Roles/AddOns/Common/Loyal.cs | 5 +- Roles/AddOns/Common/Lucky.cs | 13 +- Roles/AddOns/Common/Mundane.cs | 5 +- Roles/AddOns/Common/Necroview.cs | 14 +- Roles/AddOns/Common/Oblivious.cs | 13 +- Roles/AddOns/Common/Oiiai.cs | 14 +- Roles/AddOns/Common/Onbound.cs | 14 +- Roles/AddOns/Common/Overlocked.cs | 5 +- Roles/AddOns/Common/Paranoia.cs | 5 +- Roles/AddOns/Common/Radar.cs | 17 +- Roles/AddOns/Common/Rainbow.cs | 17 +- Roles/AddOns/Common/Reach.cs | 8 +- Roles/AddOns/Common/Rebound.cs | 14 +- Roles/AddOns/Common/Seer.cs | 14 +- Roles/AddOns/Common/Silent.cs | 15 +- Roles/AddOns/Common/Sleuth.cs | 11 +- Roles/AddOns/Common/Spurt.cs | 116 ++++++++++++ Roles/AddOns/Common/Statue.cs | 13 +- Roles/AddOns/Common/Stubborn.cs | 15 +- Roles/AddOns/Common/Susceptible.cs | 13 +- Roles/AddOns/Common/Tiebreaker.cs | 14 +- Roles/AddOns/Common/Tired.cs | 13 +- Roles/AddOns/Common/Unlucky.cs | 13 +- Roles/AddOns/Common/Unreportable.cs | 14 +- Roles/AddOns/Common/Voidballot.cs | 14 +- Roles/AddOns/Common/Watcher.cs | 14 +- Roles/AddOns/Common/Youtuber.cs | 5 +- Roles/AddOns/Crewmate/Bloodthirst.cs | 9 +- Roles/AddOns/Crewmate/Ghoul.cs | 5 +- Roles/AddOns/Crewmate/Hurried.cs | 5 +- Roles/AddOns/Crewmate/Lazy.cs | 5 +- Roles/AddOns/Crewmate/Nimble.cs | 5 +- Roles/AddOns/Crewmate/Rascal.cs | 5 +- Roles/AddOns/Crewmate/Torch.cs | 5 +- Roles/AddOns/Crewmate/Workhorse.cs | 5 +- Roles/AddOns/IAddon.cs | 26 +++ Roles/AddOns/Impostor/Circumvent.cs | 5 +- Roles/AddOns/Impostor/Clumsy.cs | 5 +- Roles/AddOns/Impostor/LastImpostor.cs | 5 +- Roles/AddOns/Impostor/Mare.cs | 5 +- Roles/AddOns/Impostor/Mimic.cs | 5 +- Roles/AddOns/Impostor/Stealer.cs | 7 +- Roles/AddOns/Impostor/Swift.cs | 7 +- Roles/AddOns/Impostor/Tricky.cs | 9 +- Roles/Core/CustomRoleManager.cs | 4 + TOHE.csproj | 2 +- main.cs | 1 + 74 files changed, 465 insertions(+), 675 deletions(-) create mode 100644 Roles/AddOns/Common/Spurt.cs create mode 100644 Roles/AddOns/IAddon.cs diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 70ffe5126d..c5f40bdb62 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -373,6 +373,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Only add-ons if (!role.IsAdditionRole()) return false; + if(Options.AddonCanBeSettings.TryGetValue(role, out var o) && (o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.GetCustomRole().IsCrewmate())) + return false; + // if player already has this addon else if (pc.Is(role)) return false; @@ -389,18 +392,12 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Checking for conflicts with roles and other add-ons switch (role) { - case CustomRoles.Stubborn: - if ((pc.GetCustomRole().IsCrewmate() && !Stubborn.CrewCanBeStubborn.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Stubborn.NeutralCanBeStubborn.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Stubborn.ImpCanBeStubborn.GetBool())) - return false; - break; case CustomRoles.Autopsy: if (pc.Is(CustomRoles.Doctor) || pc.Is(CustomRoles.Tracefinder) || pc.Is(CustomRoles.ScientistTOHE) || pc.Is(CustomRoles.Sunnyboy)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Autopsy.CrewCanBeAutopsy.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Autopsy.NeutralCanBeAutopsy.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Autopsy.ImpCanBeAutopsy.GetBool())) - return false; break; case CustomRoles.Bait: @@ -416,8 +413,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || (pc.Is(CustomRoles.Rebound) && Bait.BaitNotification.GetBool()) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Bait.CrewCanBeBait.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Bait.NeutralCanBeBait.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Bait.ImpCanBeBait.GetBool())) - return false; break; case CustomRoles.Trapper: @@ -428,8 +423,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.GuardianAngelTOHE) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Trapper.CrewCanBeTrapper.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Trapper.NeutralCanBeTrapper.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Trapper.ImpCanBeTrapper.GetBool())) - return false; break; case CustomRoles.Guesser: @@ -496,8 +489,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Workaholic) && !Workaholic.WorkaholicVisibleToEveryone.GetBool() || pc.Is(CustomRoles.PunchingBag)) return false; //Based on guess manager - if ((pc.GetCustomRole().IsCrewmate() && !Onbound.CrewCanBeOnbound.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Onbound.NeutralCanBeOnbound.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Onbound.ImpCanBeOnbound.GetBool())) - return false; break; case CustomRoles.Rebound: @@ -514,8 +505,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c { return false; } //Based on guess manager - if ((pc.GetCustomRole().IsCrewmate() && !Rebound.CrewCanBeRebound.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Rebound.NeutralCanBeRebound.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Rebound.ImpCanBeRebound.GetBool())) - return false; break; case CustomRoles.DoubleShot: @@ -552,10 +541,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Cyber: if (pc.Is(CustomRoles.Doppelganger) || pc.Is(CustomRoles.Celebrity) || pc.Is(CustomRoles.SuperStar)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Cyber.CrewCanBeCyber.GetBool()) - || (pc.GetCustomRole().IsNeutral() && !Cyber.NeutralCanBeCyber.GetBool()) - || (pc.GetCustomRole().IsImpostor() && !Cyber.ImpCanBeCyber.GetBool())) - return false; break; case CustomRoles.Reach: @@ -604,16 +589,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; - case CustomRoles.Watcher: - if ((pc.GetCustomRole().IsCrewmate() && !Watcher.CrewCanBeWatcher.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Watcher.NeutralCanBeWatcher.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Watcher.ImpCanBeWatcher.GetBool())) - return false; - break; - - case CustomRoles.Aware: - if ((pc.GetCustomRole().IsCrewmate() && !Aware.CrewCanBeAware.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Aware.NeutralCanBeAware.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Aware.ImpCanBeAware.GetBool())) - return false; - break; - case CustomRoles.Fragile: if (pc.Is(CustomRoles.Lucky) || pc.Is(CustomRoles.Veteran) @@ -631,8 +606,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Demon) || pc.Is(CustomRoles.Shaman)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Fragile.CrewCanBeFragile.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Fragile.NeutralCanBeFragile.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Fragile.ImpCanBeFragile.GetBool())) - return false; break; case CustomRoles.VoidBallot: @@ -646,20 +619,13 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Tiebreaker) || pc.Is(CustomRoles.Paranoia)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !VoidBallot.CrewCanBeVoidBallot.GetBool()) || (pc.GetCustomRole().IsNeutral() && !VoidBallot.NeutralCanBeVoidBallot.GetBool()) || (pc.GetCustomRole().IsImpostor() && !VoidBallot.ImpCanBeVoidBallot.GetBool())) - return false; break; case CustomRoles.Glow: if (pc.Is(CustomRoles.KillingMachine)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Glow.CrewCanBeGlow.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Glow.NeutralCanBeGlow.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Glow.ImpCanBeGlow.GetBool())) - return false; - break; - case CustomRoles.Radar: - if ((pc.GetCustomRole().IsCrewmate() && !Radar.CrewCanBeRadar.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Radar.NeutralCanBeRadar.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Radar.ImpCanBeRadar.GetBool())) - return false; break; + case CustomRoles.Antidote: if (pc.Is(CustomRoles.Diseased) || pc.Is(CustomRoles.Solsticer)) return false; @@ -670,8 +636,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Diseased: if (pc.Is(CustomRoles.Antidote) || pc.Is(CustomRoles.Solsticer)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Diseased.CrewCanBeDiseased.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Diseased.NeutralCanBeDiseased.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Diseased.ImpCanBeDiseased.GetBool())) - return false; break; case CustomRoles.Seer: @@ -679,8 +643,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.EvilTracker) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Seer.CrewCanBeSeer.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Seer.NeutralCanBeSeer.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Seer.ImpCanBeSeer.GetBool())) - return false; break; case CustomRoles.Sleuth: @@ -692,8 +654,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Vulture) || pc.Is(CustomRoles.Coroner)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Sleuth.CrewCanBeSleuth.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Sleuth.NeutralCanBeSleuth.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Sleuth.ImpCanBeSleuth.GetBool())) - return false; break; case CustomRoles.Necroview: @@ -702,8 +662,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Visionary) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Necroview.CrewCanBeNecroview.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Necroview.NeutralCanBeNecroview.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Necroview.ImpCanBeNecroview.GetBool())) - return false; break; case CustomRoles.Bewilder: @@ -716,8 +674,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.GuardianAngelTOHE) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Bewilder.CrewCanBeBewilder.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Bewilder.NeutralCanBeBewilder.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Bewilder.ImpCanBeBewilder.GetBool())) - return false; break; case CustomRoles.Lucky: @@ -727,8 +683,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Fragile) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Lucky.CrewCanBeLucky.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Lucky.NeutralCanBeLucky.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Lucky.ImpCanBeLucky.GetBool())) - return false; break; case CustomRoles.Unlucky: @@ -740,8 +694,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Unlucky.CrewCanBeUnlucky.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Unlucky.NeutralCanBeUnlucky.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Unlucky.ImpCanBeUnlucky.GetBool())) - return false; break; case CustomRoles.Madmate: @@ -768,8 +720,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.KillingMachine) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Oblivious.CrewCanBeOblivious.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Oblivious.NeutralCanBeOblivious.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Oblivious.ImpCanBeOblivious.GetBool())) - return false; break; case CustomRoles.Tiebreaker: @@ -777,7 +727,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.VoidBallot) || pc.Is(CustomRoles.Influenced) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Tiebreaker.CrewCanBeTiebreaker.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Tiebreaker.NeutralCanBeTiebreaker.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Tiebreaker.ImpCanBeTiebreaker.GetBool())) return false; break; case CustomRoles.Youtuber: @@ -931,8 +880,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Bait) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Burst.CrewCanBeBurst.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Burst.NeutralCanBeBurst.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Burst.ImpCanBeBurst.GetBool())) - return false; break; case CustomRoles.Avanger: @@ -942,8 +889,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Avanger.CrewCanBeAvanger.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Avanger.NeutralCanBeAvanger.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Avanger.ImpCanBeAvanger.GetBool())) - return false; break; case CustomRoles.Paranoia: @@ -981,8 +926,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.NiceMini)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Gravestone.CrewCanBeGravestone.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Gravestone.NeutralCanBeGravestone.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Gravestone.ImpCanBeGravestone.GetBool())) - return false; break; case CustomRoles.Unreportable: @@ -990,8 +933,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.Bait)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Unreportable.CrewCanBeUnreportable.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Unreportable.NeutralCanBeUnreportable.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Unreportable.ImpCanBeUnreportable.GetBool())) - return false; break; case CustomRoles.Flash: @@ -1010,8 +951,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.GuardianAngelTOHE) || pc.Is(CustomRoles.Alchemist)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Fool.CrewCanBeFool.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Fool.NeutralCanBeFool.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Fool.ImpCanBeFool.GetBool())) - return false; break; case CustomRoles.Influenced: @@ -1022,8 +961,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Collector) || pc.Is(CustomRoles.Keeper)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Influenced.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Influenced.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Influenced.CanBeOnImp.GetBool())) - return false; break; case CustomRoles.Oiiai: @@ -1032,8 +969,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Innocent) || pc.Is(CustomRoles.PunchingBag)) return false; - if ((pc.GetCustomRole().IsNeutral() && !Oiiai.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsCrewmate() && !Oiiai.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Oiiai.CanBeOnImp.GetBool())) - return false; break; case CustomRoles.Hurried: @@ -1050,21 +985,12 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Silent: if (pc.Is(CustomRoles.Dictator) || pc.Is(CustomRoles.VoidBallot)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Silent.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Silent.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Silent.CanBeOnImp.GetBool())) - return false; break; case CustomRoles.Rainbow: if (pc.Is(CustomRoles.Doppelganger) || pc.Is(CustomRoles.DollMaster)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Rainbow.CrewCanBeRainbow.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Rainbow.NeutralCanBeRainbow.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Rainbow.ImpCanBeRainbow.GetBool())) - return false; - break; - - case CustomRoles.Susceptible: - if ((pc.GetCustomRole().IsCrewmate() && !Susceptible.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Susceptible.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Susceptible.CanBeOnImp.GetBool())) - return false; break; case CustomRoles.Tired: @@ -1076,8 +1002,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Flash) || pc.Is(CustomRoles.Mare)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Tired.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Tired.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Tired.CanBeOnImp.GetBool())) - return false; break; case CustomRoles.Statue: @@ -1085,8 +1009,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Flash) || pc.Is(CustomRoles.Tired)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Statue.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Statue.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Statue.CanBeOnImp.GetBool())) - return false; break; } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 9ffea92a61..95a1380dc5 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -349,6 +349,7 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, } player.ResetKillCooldown(); } + public static Vector2 Pos(this PlayerControl pc) => new(pc.transform.position.x, pc.transform.position.y); public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, bool force = false) { Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index ed4de32573..edde9fa1f9 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1,10 +1,13 @@ using System; +using System.Reflection; using TOHE.Modules; using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; using UnityEngine; using TOHE.Roles.Core; +using static Il2CppMono.Security.X509.X520; +using TOHE.Roles.AddOns; namespace TOHE; @@ -70,6 +73,8 @@ public static CustomGameMode CurrentGameMode public static Dictionary CustomGhostRoleCounts; public static Dictionary CustomRoleSpawnChances; public static Dictionary CustomAdtRoleSpawnRate; + + public static readonly Dictionary AddonCanBeSettings = []; public enum SpawnChance { Chance0, @@ -903,186 +908,52 @@ private static System.Collections.IEnumerator CoLoadOptions() yield return null; #region Add-Ons Settings - // Add-Ons - TextOptionItem.Create(10000015, "RoleType.Helpful", TabGroup.Addons) // HELPFUL - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 154, 206, byte.MaxValue)); - - - Autopsy.SetupCustomOptions(); - - Bait.SetupCustomOptions(); - - /* - * Beartrap - */ - Trapper.SetupCustomOptions(); - - Bewilder.SetupCustomOptions(); - - Burst.SetupCustomOptions(); - - Cyber.SetupCustomOptions(); - - Flash.SetupCustomOption(); - - Lazy.SetupCustomOptions(); - - Loyal.SetupCustomOptions(); - - Lucky.SetupCustomOptions(); - - Necroview.SetupCustomOptions(); - - Nimble.SetupCustomOptions(); - - Overclocked.SetupCustomOptions(); - - Radar.SetupCustomOptions(); - - Seer.SetupCustomOptions(); - - Silent.SetupCustomOptions(); - - Sleuth.SetupCustomOptions(); - - Tiebreaker.SetupCustomOptions(); - - Torch.SetupCustomOptions(); - - Watcher.SetupCustomOptions(); - - TextOptionItem.Create(10000016, "RoleType.Harmful", TabGroup.Addons) // HARMFUL - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 154, 206, byte.MaxValue)); - - Unreportable.SetupCustomOptions(); - - Fool.SetupCustomOptions(); - - Fragile.SetupCustomOptions(); - - Statue.SetupCustomOptions(); - - Hurried.SetupCustomOption(); - - Influenced.SetupCustomOption(); - - Mundane.SetupCustomOption(); - - Oblivious.SetupCustomOptions(); - - Rascal.SetupCustomOptions(); - - Unlucky.SetupCustomOptions(); - - Tired.SetupCustomOptions(); - - VoidBallot.SetupCustomOptions(); - - TextOptionItem.Create(10000017, "RoleType.Mixed", TabGroup.Addons) // MIXED - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 154, 206, byte.MaxValue)); - - Antidote.SetupCustomOptions(); - - Avanger.SetupCustomOptions(); - - Aware.SetupCustomOptions(); - - Bloodthirst.SetupCustomOptions(); - - Diseased.SetupCustomOptions(); - - Ghoul.SetupCustomOptions(); - - //BYE GLOW, SEE YOU NEVER - LOOOOOL - // SetupAdtRoleOptions(22000, CustomRoles.Glow, canSetNum: true); - //ImpCanBeGlow = BooleanOptionItem.Create(22003, "ImpCanBeGlow", true, TabGroup.Addons, false) - //.SetParent(CustomRoleSpawnChances[CustomRoles.Glow]); - //CrewCanBeGlow = BooleanOptionItem.Create(22004, "CrewCanBeGlow", true, TabGroup.Addons, false) - //.SetParent(CustomRoleSpawnChances[CustomRoles.Glow]); - //NeutralCanBeGlow = BooleanOptionItem.Create(22005, "NeutralCanBeGlow", true, TabGroup.Addons, false) - //.SetParent(CustomRoleSpawnChances[CustomRoles.Glow]); - - Gravestone.SetupCustomOptions(); - - Guesser.SetupCustomOptions(); - - Oiiai.SetupCustomOptions(); - - Paranoia.SetupCustomOptions(); - Stubborn.SetupCustomOptions(); + int titleId = 100100; - Susceptible.SetupCustomOptions(); + var IAddonType = typeof(IAddon); + Dictionary addonTypes = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface) + .OrderBy(t => Translator.GetString(t.Name)) + .Select(type => (IAddon)Activator.CreateInstance(type)) + .Where(x => x != null) + .GroupBy(x => x.Type) + .ToDictionary(x => x.Key, x => x.ToArray()); - TextOptionItem.Create(10000018, "RoleType.Impostor", TabGroup.Addons) // IMPOSTOR - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 25, 25, byte.MaxValue)); - - /* - * Circumvent - */ - Circumvent.SetupCustomOption(); - - /* - * Clumsy - */ - Clumsy.SetupCustomOption(); - - /* - * Last Impostor - */ - LastImpostor.SetupCustomOption(); - - /* - * Madmate - */ - Madmate.SetupCustomMenuOptions(); - - /* - * Mare - */ - Mare.SetupCustomOption(); - - /* - * Mimic - */ - Mimic.SetupCustomOption(); - - Stealer.SetupCustomOption(); - - /* - * Tricky - */ - Tricky.SetupCustomOption(); - - TextOptionItem.Create(10000019, "RoleType.Misc", TabGroup.Addons) // NEUTRAL - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(127, 140, 141, byte.MaxValue)); - - - Youtuber.SetupCustomOptions(); - - Egoist.SetupCustomOption(); - - SetupLoversRoleOptionsToggle(23600); - - Reach.SetupCustomOptions(); + foreach (var addonType in addonTypes) + { + int index = 0; - Rainbow.SetupCustomOptions(); + TextOptionItem.Create(titleId, $"RoleType.{addonType.Key}", TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .SetColor(GetAddonTypeColor(addonType.Key)) + .SetHeader(true); + titleId += 10; - Workhorse.SetupCustomOption(); + foreach (var addon in addonType.Value) + { + index++; - TextOptionItem.Create(10000023, "Experimental.Roles", TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(141, 140, 141, byte.MaxValue)); + addon.SetupCustomOption(); + } + yield return null; + } + static Color32 GetAddonTypeColor(AddonTypes type) => type switch + { + AddonTypes.Impostor => new Color32(255, 25, 25, byte.MaxValue), + AddonTypes.Helpful => new Color32(255, 154, 206, byte.MaxValue), + AddonTypes.Harmful => new Color32(255, 154, 206, byte.MaxValue), + AddonTypes.Misc => new Color32(127, 140, 141, byte.MaxValue), + AddonTypes.Mixed => new Color32(255, 154, 206, byte.MaxValue), + AddonTypes.Guesser => new Color32(214, 177, 73, byte.MaxValue), + AddonTypes.Experimental => new Color32(141, 140, 141, byte.MaxValue), + _ => Palette.CrewmateBlue + }; - Glow.SetupCustomOptions(); - Swift.SetupCustomOption(); #endregion @@ -1799,19 +1670,6 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetGameMode(CustomGameMode.Standard) .SetColor(Color.cyan); - - TextOptionItem.Create(10000029, "MenuTitle.GuesserModeRoles", TabGroup.ModifierSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.yellow) - .SetHeader(true); - - DoubleShot.SetupCustomOption(); - - Onbound.SetupCustomOptions(); - - Rebound.SetupCustomOptions(); - - // Meeting Settings TextOptionItem.Create(10000030, "MenuTitle.Meeting", TabGroup.ModSettings) .SetGameMode(CustomGameMode.Standard) @@ -2020,7 +1878,7 @@ private static void SetupLoversRoleOptionsToggle(int id, CustomGameMode customGa CustomRoleCounts.Add(role, countOption); } - public static void SetupAdtRoleOptions(int id, CustomRoles role, CustomGameMode customGameMode = CustomGameMode.Standard, bool canSetNum = false, TabGroup tab = TabGroup.Addons, bool canSetChance = true) + public static void SetupAdtRoleOptions(int id, CustomRoles role, CustomGameMode customGameMode = CustomGameMode.Standard, bool canSetNum = false, TabGroup tab = TabGroup.Addons, bool canSetChance = true, bool teamSpawnOptions = false) { var spawnOption = StringOptionItem.Create(id, role.ToString(), EnumHelper.GetAllNames(), 0, tab, false).SetColor(Utils.GetRoleColor(role)) .SetHeader(true) @@ -2038,6 +1896,27 @@ public static void SetupAdtRoleOptions(int id, CustomRoles role, CustomGameMode .SetHidden(!canSetChance) .SetGameMode(customGameMode) as IntegerOptionItem; + if (teamSpawnOptions) + { + var impOption = BooleanOptionItem.Create(id + 3, "ImpCanBeRole", true, tab, false) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + var neutralOption = BooleanOptionItem.Create(id + 4, "NeutralCanBeRole", true, tab, false) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + var crewOption = BooleanOptionItem.Create(id + 5, "CrewCanBeRole", true, tab, false) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + AddonCanBeSettings.Add(role, (impOption, neutralOption, crewOption)); + } + + CustomAdtRoleSpawnRate.Add(role, spawnRateOption); CustomRoleSpawnChances.Add(role, spawnOption); CustomRoleCounts.Add(role, countOption); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 206fe955bc..6aef2d2d1b 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1865,6 +1865,8 @@ static int GetInfoSize(string RoleInfo) SelfSuffix.Append(seerRoleClass?.GetSuffix(seer, seer, isForMeeting: isForMeeting)); SelfSuffix.Append(CustomRoleManager.GetSuffixOthers(seer, seer, isForMeeting: isForMeeting)); + SelfSuffix.Append(Spurt.GetSuffix(seer)); + switch (Options.CurrentGameMode) { @@ -2254,6 +2256,7 @@ public static void AfterMeetingTasks() ventilationSystem.IsDirty = true; } } + public static string ToColoredString(this CustomRoles role) => Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString($"{role}")); public static void ChangeInt(ref int ChangeTo, int input, int max) { var tmp = ChangeTo * 10; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 178e33ee15..94929495bf 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -4,6 +4,7 @@ using TOHE.Roles.Core; using UnityEngine; using static TOHE.Translator; +using TOHE.Roles.AddOns.Common; namespace TOHE; @@ -97,7 +98,8 @@ public static void Postfix(HudManager __instance) case CustomGameMode.Standard: var roleClass = player.GetRoleClass(); LowerInfoText.text = roleClass?.GetLowerText(player, player, isForMeeting: Main.MeetingIsStarted, isForHud: true) ?? string.Empty; - + + LowerInfoText.text += "\n" + Spurt.GetSuffix(player, true); if (roleClass != null) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 2cf31c8a8a..827b1ad504 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1208,6 +1208,9 @@ public static Task DoPostfix(PlayerControl __instance) if (player.Is(CustomRoles.Statue) && player.IsAlive()) Statue.OnFixedUpdate(player); + if (player.Is(CustomRoles.Spurt) && player.IsAlive()) + Spurt.OnFixedUpdate(player); + if (!lowLoad) { CustomRoleManager.OnFixedUpdateLowLoad(player); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 90e51c5870..d65f175542 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -547,6 +547,8 @@ private static void SetRolesAfterSelect() } } + Spurt.Add(); + EndOfSelectRolePatch: try diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d211ccdeab..084223a4fc 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3473,7 +3473,9 @@ "RoleType.Mixed": "★ Mixed Add-ons", "RoleType.Misc": "★ Miscellaneous Add-ons", "RoleType.Impostor": "★ Impostor Add-ons", + "RoleType.Guesser": "★ Guesser Add-ons", "RoleType.Neut": "★ Neutral Add-ons", + "RoleType.Experimental": "★ Experimental Addons (NOTICE: Use with caution, as these require testing)", "SubType.Impostor": "★ Impostors", "SubType.Shapeshifter": "★ Shapeshifters", "SubType.SemiShapeshifter": "★ Semi-Shapeshifters", diff --git a/Roles/AddOns/Common/Antidote.cs b/Roles/AddOns/Common/Antidote.cs index 78b4aea967..731dd38844 100644 --- a/Roles/AddOns/Common/Antidote.cs +++ b/Roles/AddOns/Common/Antidote.cs @@ -2,10 +2,11 @@ namespace TOHE.Roles.AddOns.Common; -public static class Antidote +public class Antidote : IAddon { private const int Id = 21400; public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Mixed; public static OptionItem ImpCanBeAntidote; public static OptionItem CrewCanBeAntidote; @@ -15,12 +16,9 @@ public static class Antidote private static Dictionary KilledAntidote = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Antidote, canSetNum: true); - ImpCanBeAntidote = BooleanOptionItem.Create(Id + 10, "ImpCanBeAntidote", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); - CrewCanBeAntidote = BooleanOptionItem.Create(Id + 11, "CrewCanBeAntidote", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); - NeutralCanBeAntidote = BooleanOptionItem.Create(Id + 12, "NeutralCanBeAntidote", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); + SetupAdtRoleOptions(Id, CustomRoles.Antidote, canSetNum: true, teamSpawnOptions: true); AntidoteCDOpt = FloatOptionItem.Create(Id + 13, "AntidoteCDOpt", new(0f, 180f, 1f), 5f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]) .SetValueFormat(OptionFormat.Seconds); AntidoteCDReset = BooleanOptionItem.Create(Id + 14, "AntidoteCDReset", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); diff --git a/Roles/AddOns/Common/Autopsy.cs b/Roles/AddOns/Common/Autopsy.cs index 8d2344519a..2aada77a1b 100644 --- a/Roles/AddOns/Common/Autopsy.cs +++ b/Roles/AddOns/Common/Autopsy.cs @@ -2,19 +2,12 @@ namespace TOHE.Roles.AddOns.Common; -public static class Autopsy +public class Autopsy : IAddon { private const int Id = 18600; - - public static OptionItem ImpCanBeAutopsy; - public static OptionItem CrewCanBeAutopsy; - public static OptionItem NeutralCanBeAutopsy; - - public static void SetupCustomOptions() + public AddonTypes Type => AddonTypes.Helpful; + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Autopsy, canSetNum: true); - ImpCanBeAutopsy = BooleanOptionItem.Create(Id + 10, "ImpCanBeAutopsy", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Autopsy]); - CrewCanBeAutopsy = BooleanOptionItem.Create(Id + 11, "CrewCanBeAutopsy", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Autopsy]); - NeutralCanBeAutopsy = BooleanOptionItem.Create(Id + 12, "NeutralCanBeAutopsy", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Autopsy]); + SetupAdtRoleOptions(Id, CustomRoles.Autopsy, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Avanger.cs b/Roles/AddOns/Common/Avanger.cs index ac1631f2d2..c4044ecc9c 100644 --- a/Roles/AddOns/Common/Avanger.cs +++ b/Roles/AddOns/Common/Avanger.cs @@ -6,20 +6,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Avanger +public class Avanger : IAddon { private const int Id = 21500; - - public static OptionItem ImpCanBeAvanger; - public static OptionItem CrewCanBeAvanger; - public static OptionItem NeutralCanBeAvanger; + public AddonTypes Type => AddonTypes.Mixed; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Avanger, canSetNum: true); - ImpCanBeAvanger = BooleanOptionItem.Create(Id + 10, "ImpCanBeAvanger", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Avanger]); - CrewCanBeAvanger = BooleanOptionItem.Create(Id + 12, "CrewCanBeAvanger", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Avanger]); - NeutralCanBeAvanger = BooleanOptionItem.Create(Id + 13, "NeutralCanBeAvanger", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Avanger]); + SetupAdtRoleOptions(Id, CustomRoles.Avanger, canSetNum: true, teamSpawnOptions: true); } public static void OnMurderPlayer(PlayerControl target) diff --git a/Roles/AddOns/Common/Aware.cs b/Roles/AddOns/Common/Aware.cs index 12151e7886..bd22bfca4b 100644 --- a/Roles/AddOns/Common/Aware.cs +++ b/Roles/AddOns/Common/Aware.cs @@ -3,10 +3,11 @@ namespace TOHE.Roles.AddOns.Common; -public static class Aware +public class Aware : IAddon { private const int Id = 21600; public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Mixed; public static OptionItem ImpCanBeAware; public static OptionItem CrewCanBeAware; @@ -15,12 +16,9 @@ public static class Aware public static Dictionary> AwareInteracted = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(21600, CustomRoles.Aware, canSetNum: true); - ImpCanBeAware = BooleanOptionItem.Create(Id + 10, "ImpCanBeAware", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Aware]); - CrewCanBeAware = BooleanOptionItem.Create(Id + 11, "CrewCanBeAware", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Aware]); - NeutralCanBeAware = BooleanOptionItem.Create(Id + 12, "NeutralCanBeAware", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Aware]); + SetupAdtRoleOptions(21600, CustomRoles.Aware, canSetNum: true, teamSpawnOptions: true); AwareknowRole = BooleanOptionItem.Create(Id + 13, "AwareKnowRole", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Aware]); } diff --git a/Roles/AddOns/Common/Bait.cs b/Roles/AddOns/Common/Bait.cs index 110b826fef..a63b425bde 100644 --- a/Roles/AddOns/Common/Bait.cs +++ b/Roles/AddOns/Common/Bait.cs @@ -5,13 +5,11 @@ namespace TOHE.Roles.AddOns.Common; -public static class Bait +public class Bait : IAddon { private const int Id = 18700; + public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeBait; - public static OptionItem CrewCanBeBait; - public static OptionItem NeutralCanBeBait; public static OptionItem BaitDelayMin; public static OptionItem BaitDelayMax; public static OptionItem BaitDelayNotify; @@ -20,12 +18,9 @@ public static class Bait public static List BaitAlive = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Bait, canSetNum: true); - ImpCanBeBait = BooleanOptionItem.Create(Id + 10, "ImpCanBeBait", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); - CrewCanBeBait = BooleanOptionItem.Create(Id + 11, "CrewCanBeBait", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); - NeutralCanBeBait = BooleanOptionItem.Create(Id + 12, "NeutralCanBeBait", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); + SetupAdtRoleOptions(Id, CustomRoles.Bait, canSetNum: true, teamSpawnOptions: true); BaitDelayMin = FloatOptionItem.Create(Id + 13, "BaitDelayMin", new(0f, 5f, 1f), 0f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bait]) .SetValueFormat(OptionFormat.Seconds); BaitDelayMax = FloatOptionItem.Create(Id + 14, "BaitDelayMax", new(0f, 10f, 1f), 0f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bait]) diff --git a/Roles/AddOns/Common/Beartrap.cs b/Roles/AddOns/Common/Beartrap.cs index 94bea692ad..c9536a8a50 100644 --- a/Roles/AddOns/Common/Beartrap.cs +++ b/Roles/AddOns/Common/Beartrap.cs @@ -2,24 +2,22 @@ namespace TOHE.Roles.AddOns.Common; -public static class Trapper +public class Trapper : IAddon { private const int Id = 18800; + public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeTrapper; - public static OptionItem CrewCanBeTrapper; - public static OptionItem NeutralCanBeTrapper; - private static OptionItem TrapperBlockMoveTime; + public static OptionItem TrapperBlockMoveTime; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Trapper, canSetNum: true); - ImpCanBeTrapper = BooleanOptionItem.Create(Id + 10, "ImpCanBeTrapper", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]); - CrewCanBeTrapper = BooleanOptionItem.Create(Id + 11, "CrewCanBeTrapper", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]); - NeutralCanBeTrapper = BooleanOptionItem.Create(Id + 12, "NeutralCanBeTrapper", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]); + SetupAdtRoleOptions(Id, CustomRoles.Trapper, canSetNum: true, teamSpawnOptions: true); TrapperBlockMoveTime = FloatOptionItem.Create(Id + 13, "TrapperBlockMoveTime", new(1f, 180f, 1f), 5f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]) .SetValueFormat(OptionFormat.Seconds); } +} +public static class TrapperExtension +{ public static void TrapperKilled(this PlayerControl killer, PlayerControl target) { Logger.Info($"{target?.Data?.PlayerName} was Trapper", "Trapper"); @@ -33,7 +31,6 @@ public static void TrapperKilled(this PlayerControl killer, PlayerControl target ReportDeadBodyPatch.CanReport[killer.PlayerId] = true; killer.MarkDirtySettings(); RPC.PlaySoundRPC(killer.PlayerId, Sounds.TaskComplete); - }, TrapperBlockMoveTime.GetFloat(), "Trapper BlockMove"); + }, Trapper.TrapperBlockMoveTime.GetFloat(), "Trapper BlockMove"); } - } diff --git a/Roles/AddOns/Common/Bewilder.cs b/Roles/AddOns/Common/Bewilder.cs index f358e95fb7..255fe00802 100644 --- a/Roles/AddOns/Common/Bewilder.cs +++ b/Roles/AddOns/Common/Bewilder.cs @@ -3,26 +3,21 @@ namespace TOHE.Roles.AddOns.Common; -public static class Bewilder +public class Bewilder : IAddon { private const int Id = 18900; + public AddonTypes Type => AddonTypes.Helpful; private static OptionItem BewilderVision; - public static OptionItem ImpCanBeBewilder; - public static OptionItem CrewCanBeBewilder; - public static OptionItem NeutralCanBeBewilder; private static OptionItem KillerGetBewilderVision; public static bool IsEnable; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Bewilder, canSetNum: true); + SetupAdtRoleOptions(Id, CustomRoles.Bewilder, canSetNum: true, teamSpawnOptions: true); BewilderVision = FloatOptionItem.Create(Id + 10, "BewilderVision", new(0f, 5f, 0.05f), 0.6f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]) .SetValueFormat(OptionFormat.Multiplier); - ImpCanBeBewilder = BooleanOptionItem.Create(Id + 11, "ImpCanBeBewilder", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]); - CrewCanBeBewilder = BooleanOptionItem.Create(Id + 12, "CrewCanBeBewilder", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]); - NeutralCanBeBewilder = BooleanOptionItem.Create(Id + 13, "NeutralCanBeBewilder", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]); KillerGetBewilderVision = BooleanOptionItem.Create(Id + 14, "KillerGetBewilderVision", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]); } diff --git a/Roles/AddOns/Common/Burst.cs b/Roles/AddOns/Common/Burst.cs index 16e2501a4c..45668a2e61 100644 --- a/Roles/AddOns/Common/Burst.cs +++ b/Roles/AddOns/Common/Burst.cs @@ -3,23 +3,18 @@ namespace TOHE.Roles.AddOns.Common; -public static class Burst +public class Burst : IAddon { private const int Id = 19000; public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeBurst; - public static OptionItem CrewCanBeBurst; - public static OptionItem NeutralCanBeBurst; private static OptionItem BurstKillDelay; private static readonly List BurstBodies = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Burst, canSetNum: true); - ImpCanBeBurst = BooleanOptionItem.Create(Id + 10, "ImpCanBeBurst", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Burst]); - CrewCanBeBurst = BooleanOptionItem.Create(Id + 11, "CrewCanBeBurst", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Burst]); - NeutralCanBeBurst = BooleanOptionItem.Create(Id + 12, "NeutralCanBeBurst", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Burst]); + SetupAdtRoleOptions(Id, CustomRoles.Burst, canSetNum: true, teamSpawnOptions: true); BurstKillDelay = FloatOptionItem.Create(Id + 13, "BurstKillDelay", new(1f, 180f, 1f), 5f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Burst]) .SetValueFormat(OptionFormat.Seconds); } diff --git a/Roles/AddOns/Common/Cyber.cs b/Roles/AddOns/Common/Cyber.cs index c724a191cc..7ecb3378c0 100644 --- a/Roles/AddOns/Common/Cyber.cs +++ b/Roles/AddOns/Common/Cyber.cs @@ -2,26 +2,21 @@ namespace TOHE.Roles.AddOns.Common; -public class Cyber +public class Cyber : IAddon { private const int Id = 19100; + public AddonTypes Type => AddonTypes.Helpful; public static List CyberDead = []; - public static OptionItem ImpCanBeCyber; - public static OptionItem CrewCanBeCyber; - public static OptionItem NeutralCanBeCyber; public static OptionItem ImpKnowCyberDead; public static OptionItem CrewKnowCyberDead; public static OptionItem NeutralKnowCyberDead; public static OptionItem CyberKnown; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Cyber, canSetNum: true); - ImpCanBeCyber = BooleanOptionItem.Create(Id + 10, "ImpCanBeCyber", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); - CrewCanBeCyber = BooleanOptionItem.Create(Id + 11, "CrewCanBeCyber", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); - NeutralCanBeCyber = BooleanOptionItem.Create(Id + 12, "NeutralCanBeCyber", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); + SetupAdtRoleOptions(Id, CustomRoles.Cyber, canSetNum: true, teamSpawnOptions: true); ImpKnowCyberDead = BooleanOptionItem.Create(Id + 13, "ImpKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); CrewKnowCyberDead = BooleanOptionItem.Create(Id + 14, "CrewKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); NeutralKnowCyberDead = BooleanOptionItem.Create(Id + 15, "NeutralKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); diff --git a/Roles/AddOns/Common/Diseased.cs b/Roles/AddOns/Common/Diseased.cs index d19a110fe5..ac8b77c984 100644 --- a/Roles/AddOns/Common/Diseased.cs +++ b/Roles/AddOns/Common/Diseased.cs @@ -2,25 +2,20 @@ namespace TOHE.Roles.AddOns.Common; -public static class Diseased +public class Diseased : IAddon { private const int Id = 21800; public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Mixed; - public static OptionItem ImpCanBeDiseased; - public static OptionItem CrewCanBeDiseased; - public static OptionItem NeutralCanBeDiseased; private static OptionItem DiseasedCDOpt; private static OptionItem DiseasedCDReset; private static Dictionary KilledDiseased; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Diseased, canSetNum: true); - ImpCanBeDiseased = BooleanOptionItem.Create(Id + 10, "ImpCanBeDiseased", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); - CrewCanBeDiseased = BooleanOptionItem.Create(Id + 11, "CrewCanBeDiseased", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); - NeutralCanBeDiseased = BooleanOptionItem.Create(Id + 12, "NeutralCanBeDiseased", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); + SetupAdtRoleOptions(Id, CustomRoles.Diseased, canSetNum: true, teamSpawnOptions: true); DiseasedCDOpt = FloatOptionItem.Create(Id + 13, "DiseasedCDOpt", new(0f, 180f, 1f), 25f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]) .SetValueFormat(OptionFormat.Seconds); DiseasedCDReset = BooleanOptionItem.Create(Id + 14, "DiseasedCDReset", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); diff --git a/Roles/AddOns/Common/DoubleShot.cs b/Roles/AddOns/Common/DoubleShot.cs index de3d56b007..573c718023 100644 --- a/Roles/AddOns/Common/DoubleShot.cs +++ b/Roles/AddOns/Common/DoubleShot.cs @@ -2,22 +2,24 @@ namespace TOHE.Roles.AddOns.Common; -public static class DoubleShot +public class DoubleShot : IAddon { public static HashSet IsActive = []; + public AddonTypes Type => AddonTypes.Guesser; + public static OptionItem ImpCanBeDoubleShot; public static OptionItem CrewCanBeDoubleShot; public static OptionItem NeutralCanBeDoubleShot; - public static void SetupCustomOption() + public void SetupCustomOption() { - SetupAdtRoleOptions(19200, CustomRoles.DoubleShot, canSetNum: true, tab: TabGroup.ModifierSettings); - ImpCanBeDoubleShot = BooleanOptionItem.Create(19203, "ImpCanBeDoubleShot", true, TabGroup.ModifierSettings, false) + SetupAdtRoleOptions(19200, CustomRoles.DoubleShot, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); + ImpCanBeDoubleShot = BooleanOptionItem.Create(19210, "ImpCanBeDoubleShot", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); - CrewCanBeDoubleShot = BooleanOptionItem.Create(19204, "CrewCanBeDoubleShot", true, TabGroup.ModifierSettings, false) + CrewCanBeDoubleShot = BooleanOptionItem.Create(19211, "CrewCanBeDoubleShot", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); - NeutralCanBeDoubleShot = BooleanOptionItem.Create(19205, "NeutralCanBeDoubleShot", true, TabGroup.ModifierSettings, false) + NeutralCanBeDoubleShot = BooleanOptionItem.Create(19212, "NeutralCanBeDoubleShot", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); } public static void Init() diff --git a/Roles/AddOns/Common/Egoist.cs b/Roles/AddOns/Common/Egoist.cs index 6ea2e65317..ba90e9182c 100644 --- a/Roles/AddOns/Common/Egoist.cs +++ b/Roles/AddOns/Common/Egoist.cs @@ -2,16 +2,17 @@ namespace TOHE.Roles.AddOns.Common; -public static class Egoist +public class Egoist : IAddon { private const int Id = 23500; + public AddonTypes Type => AddonTypes.Misc; public static OptionItem CrewCanBeEgoist; public static OptionItem ImpCanBeEgoist; public static OptionItem ImpEgoistVisibalToAllies; public static OptionItem EgoistCountAsConverted; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Egoist, canSetNum: true, tab: TabGroup.Addons); CrewCanBeEgoist = BooleanOptionItem.Create(Id + 10, "CrewCanBeEgoist", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Egoist]); diff --git a/Roles/AddOns/Common/Flash.cs b/Roles/AddOns/Common/Flash.cs index ecec880ca4..7b38ffdfcc 100644 --- a/Roles/AddOns/Common/Flash.cs +++ b/Roles/AddOns/Common/Flash.cs @@ -3,15 +3,16 @@ namespace TOHE.Roles.AddOns.Common; -public static class Flash +public class Flash : IAddon { private const int Id = 26100; + public AddonTypes Type => AddonTypes.Helpful; private static OptionItem OptionSpeed; - public static void SetupCustomOption() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Flash, canSetNum: true, tab: TabGroup.Addons); + SetupAdtRoleOptions(Id, CustomRoles.Flash, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); OptionSpeed = FloatOptionItem.Create(Id + 10, "FlashSpeed", new(0.25f, 5f, 0.25f), 2.5f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Flash]) .SetValueFormat(OptionFormat.Multiplier); diff --git a/Roles/AddOns/Common/Fool.cs b/Roles/AddOns/Common/Fool.cs index eb824ff1a8..33864f8513 100644 --- a/Roles/AddOns/Common/Fool.cs +++ b/Roles/AddOns/Common/Fool.cs @@ -2,21 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Fool +public class Fool : IAddon { private const int Id = 25600; public static bool IsEnable = false; - - public static OptionItem ImpCanBeFool; - public static OptionItem CrewCanBeFool; - public static OptionItem NeutralCanBeFool; - - public static void SetupCustomOptions() + public AddonTypes Type => AddonTypes.Harmful; + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Fool, canSetNum: true, tab: TabGroup.Addons); - ImpCanBeFool = BooleanOptionItem.Create(Id + 10, "ImpCanBeFool", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fool]); - CrewCanBeFool = BooleanOptionItem.Create(Id + 11, "CrewCanBeFool", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fool]); - NeutralCanBeFool = BooleanOptionItem.Create(Id + 12, "NeutralCanBeFool", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fool]); + SetupAdtRoleOptions(Id, CustomRoles.Fool, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } public static void Init() diff --git a/Roles/AddOns/Common/Fragile.cs b/Roles/AddOns/Common/Fragile.cs index 55946e26f1..693f7be371 100644 --- a/Roles/AddOns/Common/Fragile.cs +++ b/Roles/AddOns/Common/Fragile.cs @@ -2,24 +2,19 @@ namespace TOHE.Roles.AddOns.Common; -public static class Fragile +public class Fragile : IAddon { private const int Id = 20600; + public AddonTypes Type => AddonTypes.Harmful; - public static OptionItem ImpCanBeFragile; - public static OptionItem CrewCanBeFragile; - public static OptionItem NeutralCanBeFragile; public static OptionItem ImpCanKillFragile; private static OptionItem CrewCanKillFragile; private static OptionItem NeutralCanKillFragile; private static OptionItem FragileKillerLunge; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Fragile, canSetNum: true); - ImpCanBeFragile = BooleanOptionItem.Create(Id + 10, "ImpCanBeFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); - CrewCanBeFragile = BooleanOptionItem.Create(Id + 11, "CrewCanBeFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); - NeutralCanBeFragile = BooleanOptionItem.Create(Id + 12, "NeutralCanBeFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); + SetupAdtRoleOptions(Id, CustomRoles.Fragile, canSetNum: true, teamSpawnOptions: true); ImpCanKillFragile = BooleanOptionItem.Create(Id + 13, "ImpCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); CrewCanKillFragile = BooleanOptionItem.Create(Id + 14, "CrewCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); NeutralCanKillFragile = BooleanOptionItem.Create(Id + 15, "NeutralCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index 08364481cd..459ba11532 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -4,14 +4,12 @@ namespace TOHE.Roles.AddOns.Common; -public static class Glow +public class Glow : IAddon { private const int Id = 22000; public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Experimental; - public static OptionItem ImpCanBeGlow; - public static OptionItem CrewCanBeGlow; - public static OptionItem NeutralCanBeGlow; private static OptionItem GlowRadius; private static OptionItem GlowVisionOthers; private static OptionItem GlowVisionSelf; @@ -19,15 +17,9 @@ public static class Glow private static readonly Dictionary> InRadius = []; private static readonly Dictionary MarkedOnce = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Glow, canSetNum: true, tab: TabGroup.Addons); - ImpCanBeGlow = BooleanOptionItem.Create(Id + 10, "ImpCanBeGlow", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Glow]); - CrewCanBeGlow = BooleanOptionItem.Create(Id + 11, "CrewCanBeGlow", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Glow]); - NeutralCanBeGlow = BooleanOptionItem.Create(Id + 12, "NeutralCanBeGlow", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Glow]); + SetupAdtRoleOptions(Id, CustomRoles.Glow, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); GlowRadius = FloatOptionItem.Create(Id + 13, "GlowRadius", new(0.1f, 5f, 0.05f), 0.5f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Glow]) .SetValueFormat(OptionFormat.Multiplier); diff --git a/Roles/AddOns/Common/Gravestone.cs b/Roles/AddOns/Common/Gravestone.cs index 416cb75bb1..9aac266c92 100644 --- a/Roles/AddOns/Common/Gravestone.cs +++ b/Roles/AddOns/Common/Gravestone.cs @@ -2,20 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Gravestone +public class Gravestone : IAddon { private const int Id = 22100; - - public static OptionItem ImpCanBeGravestone; - public static OptionItem CrewCanBeGravestone; - public static OptionItem NeutralCanBeGravestone; + public AddonTypes Type => AddonTypes.Mixed; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Gravestone, canSetNum: true); - ImpCanBeGravestone = BooleanOptionItem.Create(Id + 10, "ImpCanBeGravestone", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Gravestone]); - CrewCanBeGravestone = BooleanOptionItem.Create(Id + 11, "CrewCanBeGravestone", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Gravestone]); - NeutralCanBeGravestone = BooleanOptionItem.Create(Id + 12, "NeutralCanBeGravestone", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Gravestone]); + SetupAdtRoleOptions(Id, CustomRoles.Gravestone, canSetNum: true, teamSpawnOptions: true); } public static bool EveryoneKnowRole(PlayerControl player) => player.Is(CustomRoles.Gravestone) && !player.IsAlive(); } diff --git a/Roles/AddOns/Common/Guesser.cs b/Roles/AddOns/Common/Guesser.cs index 487bd89780..c44a9a16f5 100644 --- a/Roles/AddOns/Common/Guesser.cs +++ b/Roles/AddOns/Common/Guesser.cs @@ -3,9 +3,10 @@ namespace TOHE.Roles.AddOns.Common; -public static class Guesser +public class Guesser : IAddon { private const int Id = 22200; + public AddonTypes Type => AddonTypes.Guesser; public static OptionItem ImpCanBeGuesser; public static OptionItem CrewCanBeGuesser; @@ -14,7 +15,7 @@ public static class Guesser public static OptionItem GCanGuessTaskDoneSnitch; public static OptionItem GTryHideMsg; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Guesser, canSetNum: true, tab: TabGroup.Addons); ImpCanBeGuesser = BooleanOptionItem.Create(Id + 10, "ImpCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); diff --git a/Roles/AddOns/Common/Influenced.cs b/Roles/AddOns/Common/Influenced.cs index 387e18a00d..82446aefb4 100644 --- a/Roles/AddOns/Common/Influenced.cs +++ b/Roles/AddOns/Common/Influenced.cs @@ -1,18 +1,13 @@ namespace TOHE.Roles.AddOns.Common; -public static class Influenced +public class Influenced : IAddon { private const int Id = 21200; - public static OptionItem CanBeOnCrew; - public static OptionItem CanBeOnImp; - public static OptionItem CanBeOnNeutral; + public AddonTypes Type => AddonTypes.Harmful; - public static void SetupCustomOption() + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Influenced, canSetNum: true); - CanBeOnImp = BooleanOptionItem.Create(Id + 10, "ImpCanBeInfluenced", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Influenced]); - CanBeOnCrew = BooleanOptionItem.Create(Id + 11, "CrewCanBeInfluenced", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Influenced]); - CanBeOnNeutral = BooleanOptionItem.Create(Id + 12, "NeutralCanBeInfluenced", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Influenced]); + Options.SetupAdtRoleOptions(Id, CustomRoles.Influenced, canSetNum: true, teamSpawnOptions: true); } public static void ChangeVotingData(Dictionary VotingData) { diff --git a/Roles/AddOns/Common/Loyal.cs b/Roles/AddOns/Common/Loyal.cs index 931010615a..798e3c4fa3 100644 --- a/Roles/AddOns/Common/Loyal.cs +++ b/Roles/AddOns/Common/Loyal.cs @@ -2,13 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Loyal +public class Loyal : IAddon { private const int Id = 19400; + public AddonTypes Type => AddonTypes.Helpful; public static OptionItem ImpCanBeLoyal; public static OptionItem CrewCanBeLoyal; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Loyal, canSetNum: true); ImpCanBeLoyal = BooleanOptionItem.Create(Id + 10, "ImpCanBeLoyal", true, TabGroup.Addons, false) diff --git a/Roles/AddOns/Common/Lucky.cs b/Roles/AddOns/Common/Lucky.cs index fc3a586652..90d2102d3d 100644 --- a/Roles/AddOns/Common/Lucky.cs +++ b/Roles/AddOns/Common/Lucky.cs @@ -2,25 +2,20 @@ namespace TOHE.Roles.AddOns.Common; -public static class Lucky +public class Lucky : IAddon { private const int Id = 19500; + public AddonTypes Type => AddonTypes.Helpful; private static OptionItem LuckyProbability; - public static OptionItem ImpCanBeLucky; - public static OptionItem CrewCanBeLucky; - public static OptionItem NeutralCanBeLucky; private static Dictionary LuckyAvoid; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Lucky, canSetNum: true); + SetupAdtRoleOptions(Id, CustomRoles.Lucky, canSetNum: true, teamSpawnOptions: true); LuckyProbability = IntegerOptionItem.Create(Id + 10, "LuckyProbability", new(0, 100, 5), 50, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lucky]) .SetValueFormat(OptionFormat.Percent); - ImpCanBeLucky = BooleanOptionItem.Create(Id + 11, "ImpCanBeLucky", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lucky]); - CrewCanBeLucky = BooleanOptionItem.Create(Id + 12, "CrewCanBeLucky", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lucky]); - NeutralCanBeLucky = BooleanOptionItem.Create(Id + 13, "NeutralCanBeLucky", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lucky]); } public static void Init() diff --git a/Roles/AddOns/Common/Mundane.cs b/Roles/AddOns/Common/Mundane.cs index 4c5c7b9c5d..dc23ed0f21 100644 --- a/Roles/AddOns/Common/Mundane.cs +++ b/Roles/AddOns/Common/Mundane.cs @@ -2,14 +2,15 @@ namespace TOHE.Roles.AddOns.Common; -public static class Mundane +public class Mundane : IAddon { private const int Id = 26700; + public AddonTypes Type => AddonTypes.Harmful; public static OptionItem CanBeOnCrew; public static OptionItem CanBeOnNeutral; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Mundane, canSetNum: true, tab: TabGroup.Addons); CanBeOnCrew = BooleanOptionItem.Create(Id + 11, "CrewCanBeMundane", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mundane]); diff --git a/Roles/AddOns/Common/Necroview.cs b/Roles/AddOns/Common/Necroview.cs index 179d8ac386..f64e8460fd 100644 --- a/Roles/AddOns/Common/Necroview.cs +++ b/Roles/AddOns/Common/Necroview.cs @@ -2,20 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Necroview +public class Necroview : IAddon { private const int Id = 19600; + public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeNecroview; - public static OptionItem CrewCanBeNecroview; - public static OptionItem NeutralCanBeNecroview; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Necroview, canSetNum: true, tab: TabGroup.Addons); - ImpCanBeNecroview = BooleanOptionItem.Create(Id + 10, "ImpCanBeNecroview", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necroview]); - CrewCanBeNecroview = BooleanOptionItem.Create(Id + 11, "CrewCanBeNecroview", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necroview]); - NeutralCanBeNecroview = BooleanOptionItem.Create(Id + 12, "NeutralCanBeNecroview", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Necroview]); + SetupAdtRoleOptions(Id, CustomRoles.Necroview, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } public static string NameColorOptions(PlayerControl target) diff --git a/Roles/AddOns/Common/Oblivious.cs b/Roles/AddOns/Common/Oblivious.cs index 36c224f4cd..a1a36c2e6e 100644 --- a/Roles/AddOns/Common/Oblivious.cs +++ b/Roles/AddOns/Common/Oblivious.cs @@ -2,21 +2,16 @@ namespace TOHE.Roles.AddOns.Common; -public static class Oblivious +public class Oblivious : IAddon { private const int Id = 20700; + public AddonTypes Type => AddonTypes.Harmful; - public static OptionItem ImpCanBeOblivious; - public static OptionItem CrewCanBeOblivious; - public static OptionItem NeutralCanBeOblivious; public static OptionItem ObliviousBaitImmune; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Oblivious, canSetNum: true); - ImpCanBeOblivious = BooleanOptionItem.Create(Id + 10, "ImpCanBeOblivious", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); - CrewCanBeOblivious = BooleanOptionItem.Create(Id + 11, "CrewCanBeOblivious", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); - NeutralCanBeOblivious = BooleanOptionItem.Create(Id + 12, "NeutralCanBeOblivious", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); + SetupAdtRoleOptions(Id, CustomRoles.Oblivious, canSetNum: true, teamSpawnOptions: true); ObliviousBaitImmune = BooleanOptionItem.Create(Id + 13, "ObliviousBaitImmune", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); } diff --git a/Roles/AddOns/Common/Oiiai.cs b/Roles/AddOns/Common/Oiiai.cs index 50b5543df2..4fc07e94f6 100644 --- a/Roles/AddOns/Common/Oiiai.cs +++ b/Roles/AddOns/Common/Oiiai.cs @@ -6,15 +6,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Oiiai +public class Oiiai : IAddon { private const int Id = 25700; private readonly static List playerIdList = []; public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Mixed; + - public static OptionItem CanBeOnCrew; - public static OptionItem CanBeOnImp; - public static OptionItem CanBeOnNeutral; private static OptionItem CanPassOn; private static OptionItem ChangeNeutralRole; @@ -31,12 +30,9 @@ private enum ChangeRolesSelectList CustomRoles.Imitator, ]; //Just -1 to use this LOL - public static void SetupCustomOptions() + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Oiiai, canSetNum: true, tab: TabGroup.Addons); - CanBeOnImp = BooleanOptionItem.Create(Id + 11, "ImpCanBeOiiai", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); - CanBeOnCrew = BooleanOptionItem.Create(Id + 12, "CrewCanBeOiiai", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); - CanBeOnNeutral = BooleanOptionItem.Create(Id + 13, "NeutralCanBeOiiai", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); + Options.SetupAdtRoleOptions(Id, CustomRoles.Oiiai, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); CanPassOn = BooleanOptionItem.Create(Id + 14, "OiiaiCanPassOn", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); ChangeNeutralRole = StringOptionItem.Create(Id + 15, "NeutralChangeRolesForOiiai", EnumHelper.GetAllNames(), 1, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); } diff --git a/Roles/AddOns/Common/Onbound.cs b/Roles/AddOns/Common/Onbound.cs index a534f49abc..1a46143f64 100644 --- a/Roles/AddOns/Common/Onbound.cs +++ b/Roles/AddOns/Common/Onbound.cs @@ -2,20 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Onbound +public class Onbound : IAddon { private const int Id = 25800; + public AddonTypes Type => AddonTypes.Guesser; - public static OptionItem ImpCanBeOnbound; - public static OptionItem CrewCanBeOnbound; - public static OptionItem NeutralCanBeOnbound; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Onbound, canSetNum: true, tab: TabGroup.ModifierSettings); - ImpCanBeOnbound = BooleanOptionItem.Create(Id + 10, "ImpCanBeOnbound", true, TabGroup.ModifierSettings, false).SetParent(CustomRoleSpawnChances[CustomRoles.Onbound]); - CrewCanBeOnbound = BooleanOptionItem.Create(Id + 11, "CrewCanBeOnbound", true, TabGroup.ModifierSettings, false).SetParent(CustomRoleSpawnChances[CustomRoles.Onbound]); - NeutralCanBeOnbound = BooleanOptionItem.Create(Id + 12, "NeutralCanBeOnbound", true, TabGroup.ModifierSettings, false).SetParent(CustomRoleSpawnChances[CustomRoles.Onbound]); + SetupAdtRoleOptions(Id, CustomRoles.Onbound, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } } diff --git a/Roles/AddOns/Common/Overlocked.cs b/Roles/AddOns/Common/Overlocked.cs index 14c371fc7e..09b098045c 100644 --- a/Roles/AddOns/Common/Overlocked.cs +++ b/Roles/AddOns/Common/Overlocked.cs @@ -2,13 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Overclocked +public class Overclocked : IAddon { private const int Id = 19800; + public AddonTypes Type => AddonTypes.Helpful; public static OptionItem OverclockedReduction; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Overclocked, canSetNum: true); OverclockedReduction = FloatOptionItem.Create(Id + 10, "OverclockedReduction", new(0f, 90f, 5f), 40f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Overclocked]) diff --git a/Roles/AddOns/Common/Paranoia.cs b/Roles/AddOns/Common/Paranoia.cs index d9d8a3c5b6..a1851beb2e 100644 --- a/Roles/AddOns/Common/Paranoia.cs +++ b/Roles/AddOns/Common/Paranoia.cs @@ -2,7 +2,7 @@ namespace TOHE.Roles.AddOns.Common; -public static class Paranoia +public class Paranoia : IAddon { private const int Id = 22400; @@ -10,8 +10,9 @@ public static class Paranoia public static OptionItem CanBeCrew; public static OptionItem DualVotes; private static OptionItem HideAdditionalVotes; + public AddonTypes Type => AddonTypes.Mixed; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Paranoia, canSetNum: true); CanBeImp = BooleanOptionItem.Create(Id + 10, "ImpCanBeParanoia", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]); diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index becde4d247..7e1d3dcd8c 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -5,26 +5,17 @@ namespace TOHE.Roles.AddOns.Common; -public static class Radar +public class Radar : IAddon { private const int Id = 28200; public static bool IsEnable = false; - - public static OptionItem ImpCanBeRadar; - public static OptionItem CrewCanBeRadar; - public static OptionItem NeutralCanBeRadar; + public AddonTypes Type => AddonTypes.Helpful; private static readonly Dictionary ClosestPlayer = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Radar, canSetNum: true, tab: TabGroup.Addons); - ImpCanBeRadar = BooleanOptionItem.Create(Id + 10, "ImpCanBeRadar", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Radar]); - CrewCanBeRadar = BooleanOptionItem.Create(Id + 11, "CrewCanBeRadar", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Radar]); - NeutralCanBeRadar = BooleanOptionItem.Create(Id + 12, "NeutralCanBeRadar", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Radar]); + SetupAdtRoleOptions(Id, CustomRoles.Radar, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } public static void Init() diff --git a/Roles/AddOns/Common/Rainbow.cs b/Roles/AddOns/Common/Rainbow.cs index 2ef3519b2a..1c000efb86 100644 --- a/Roles/AddOns/Common/Rainbow.cs +++ b/Roles/AddOns/Common/Rainbow.cs @@ -3,26 +3,19 @@ namespace TOHE.Roles.AddOns.Common; // https://github.com/Yumenopai/TownOfHost_Y/blob/main/Roles/Crewmate/Y/Rainbow.cs -public static class Rainbow +public class Rainbow : IAddon { private const int Id = 27700; - public static OptionItem CrewCanBeRainbow; - public static OptionItem ImpCanBeRainbow; - public static OptionItem NeutralCanBeRainbow; + public AddonTypes Type => AddonTypes.Misc; + private static OptionItem RainbowColorChangeCoolDown; private static OptionItem ChangeInCamouflage; public static bool isEnabled = false; public static long LastColorChange; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Rainbow, canSetNum: true, tab: TabGroup.Addons); - CrewCanBeRainbow = BooleanOptionItem.Create(Id + 10, "CrewCanBeRainbow", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Rainbow]); - ImpCanBeRainbow = BooleanOptionItem.Create(Id + 11, "ImpCanBeRainbow", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Rainbow]); - NeutralCanBeRainbow = BooleanOptionItem.Create(Id + 12, "NeutralCanBeRainbow", true, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Rainbow]); + SetupAdtRoleOptions(Id, CustomRoles.Rainbow, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); RainbowColorChangeCoolDown = IntegerOptionItem.Create(Id + 13, "RainbowColorChangeCoolDown", new(1, 100, 1), 3, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Rainbow]); ChangeInCamouflage = BooleanOptionItem.Create(Id + 14, "RainbowInCamouflage", true, TabGroup.Addons, false) diff --git a/Roles/AddOns/Common/Reach.cs b/Roles/AddOns/Common/Reach.cs index dd712de83d..fc92c6daf6 100644 --- a/Roles/AddOns/Common/Reach.cs +++ b/Roles/AddOns/Common/Reach.cs @@ -3,13 +3,13 @@ namespace TOHE.Roles.AddOns.Common; -public class Reach +public class Reach : IAddon { private const int Id = 23700; - - public static CustomRoles IsReach = CustomRoles.Reach; // Used to find "references" of this addon. + public AddonTypes Type => AddonTypes.Helpful; + public static CustomRoles IsReach => CustomRoles.Reach; // Used to find "references" of this addon. - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Reach, canSetNum: true); } diff --git a/Roles/AddOns/Common/Rebound.cs b/Roles/AddOns/Common/Rebound.cs index 633eebe53f..f26edb62e2 100644 --- a/Roles/AddOns/Common/Rebound.cs +++ b/Roles/AddOns/Common/Rebound.cs @@ -3,19 +3,13 @@ namespace TOHE.Roles.AddOns.Common; -public static class Rebound +public class Rebound : IAddon { private const int Id = 22300; + public AddonTypes Type => AddonTypes.Guesser; - public static OptionItem ImpCanBeRebound; - public static OptionItem CrewCanBeRebound; - public static OptionItem NeutralCanBeRebound; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Rebound, canSetNum: true, tab: TabGroup.ModifierSettings); - ImpCanBeRebound = BooleanOptionItem.Create(Id + 10, "ImpCanBeRebound", true, TabGroup.ModifierSettings, false).SetParent(CustomRoleSpawnChances[CustomRoles.Rebound]); - CrewCanBeRebound = BooleanOptionItem.Create(Id + 11, "CrewCanBeRebound", true, TabGroup.ModifierSettings, false).SetParent(CustomRoleSpawnChances[CustomRoles.Rebound]); - NeutralCanBeRebound = BooleanOptionItem.Create(Id + 12, "NeutralCanBeRebound", true, TabGroup.ModifierSettings, false).SetParent(CustomRoleSpawnChances[CustomRoles.Rebound]); + SetupAdtRoleOptions(Id, CustomRoles.Rebound, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Seer.cs b/Roles/AddOns/Common/Seer.cs index 4d46b2b927..f804ea26e3 100644 --- a/Roles/AddOns/Common/Seer.cs +++ b/Roles/AddOns/Common/Seer.cs @@ -1,19 +1,13 @@  namespace TOHE.Roles.AddOns.Common; -public static class Seer +public class Seer : IAddon { private const int Id = 20000; + public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeSeer; - public static OptionItem CrewCanBeSeer; - public static OptionItem NeutralCanBeSeer; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Seer, canSetNum: true); - ImpCanBeSeer = BooleanOptionItem.Create(Id + 10, "ImpCanBeSeer", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Seer]); - CrewCanBeSeer = BooleanOptionItem.Create(Id + 11, "CrewCanBeSeer", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Seer]); - NeutralCanBeSeer = BooleanOptionItem.Create(Id + 12, "NeutralCanBeSeer", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Seer]); + Options.SetupAdtRoleOptions(Id, CustomRoles.Seer, canSetNum: true, teamSpawnOptions: true); } } diff --git a/Roles/AddOns/Common/Silent.cs b/Roles/AddOns/Common/Silent.cs index 5f9d0826a2..c9f0b852a2 100644 --- a/Roles/AddOns/Common/Silent.cs +++ b/Roles/AddOns/Common/Silent.cs @@ -1,19 +1,12 @@  namespace TOHE.Roles.AddOns.Common; -public static class Silent +public class Silent : IAddon { private const int Id = 26600; - - public static OptionItem CanBeOnCrew; - public static OptionItem CanBeOnImp; - public static OptionItem CanBeOnNeutral; - - public static void SetupCustomOptions() + public AddonTypes Type => AddonTypes.Helpful; + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Silent, canSetNum: true, tab: TabGroup.Addons); - CanBeOnImp = BooleanOptionItem.Create(Id + 11, "ImpCanBeSilent", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Silent]); - CanBeOnCrew = BooleanOptionItem.Create(Id + 12, "CrewCanBeSilent", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Silent]); - CanBeOnNeutral = BooleanOptionItem.Create(Id + 13, "NeutralCanBeSilent", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Silent]); + Options.SetupAdtRoleOptions(Id, CustomRoles.Silent, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } } diff --git a/Roles/AddOns/Common/Sleuth.cs b/Roles/AddOns/Common/Sleuth.cs index d732d51fc1..3a1caebd4b 100644 --- a/Roles/AddOns/Common/Sleuth.cs +++ b/Roles/AddOns/Common/Sleuth.cs @@ -1,22 +1,17 @@ namespace TOHE.Roles.AddOns.Common; -public static class Sleuth +public class Sleuth : IAddon { private const int Id = 20100; + public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeSleuth; - public static OptionItem CrewCanBeSleuth; - public static OptionItem NeutralCanBeSleuth; public static OptionItem SleuthCanKnowKillerRole; public static Dictionary SleuthNotify = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Sleuth, canSetNum: true); - ImpCanBeSleuth = BooleanOptionItem.Create(Id + 10, "ImpCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); - CrewCanBeSleuth = BooleanOptionItem.Create(Id + 11, "CrewCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); - NeutralCanBeSleuth = BooleanOptionItem.Create(Id + 12, "NeutralCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); SleuthCanKnowKillerRole = BooleanOptionItem.Create(Id + 13, "SleuthCanKnowKillerRole", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); } diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs new file mode 100644 index 0000000000..aaeda20d4f --- /dev/null +++ b/Roles/AddOns/Common/Spurt.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static TOHE.Options; +using UnityEngine; + +namespace TOHE.Roles.AddOns.Common +{ + internal class Spurt : IAddon + { + private static OptionItem MinSpeed; + private static OptionItem Modulator; + private static OptionItem MaxSpeed; + private static OptionItem DisplaysCharge; + + private static readonly Dictionary LastPos = []; + public static readonly Dictionary StartingSpeed = []; + private static readonly Dictionary LastNum = []; + private static readonly Dictionary LastUpdate = []; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + const int id = 648950; + SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); + MinSpeed = FloatOptionItem.Create(id + 6, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + MaxSpeed = FloatOptionItem.Create(id + 7, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + Modulator =FloatOptionItem.Create(id + 8, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + DisplaysCharge = BooleanOptionItem.Create(id + 9, "EnableSpurtCharge", false, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); + } + + public static void Add() + { + foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) + { + if (pc.Is(CustomRoles.Spurt)) + { + LastPos[pc.PlayerId] = pc.Pos(); + LastNum[pc.PlayerId] = 0; + LastUpdate[pc.PlayerId] = Utils.TimeStamp; + StartingSpeed[pc.PlayerId] = speed; + } + } + } + + public static void DeathTask(PlayerControl player) + { + if (!player.Is(CustomRoles.Spurt)) return; + + Main.AllPlayerSpeed[player.PlayerId] = StartingSpeed[player.PlayerId]; + player.MarkDirtySettings(); + } + + private static int DetermineCharge(PlayerControl player) + { + float minSpeed = MinSpeed.GetFloat(); + float maxSpeed = MaxSpeed.GetFloat(); + + if (Mathf.Approximately(minSpeed, maxSpeed)) + return 100; + + return (int)((Main.AllPlayerSpeed[player.PlayerId] - minSpeed) / (maxSpeed - minSpeed) * 100); + } + + public static string GetSuffix(PlayerControl player, bool isforhud = false) + { + if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting) + return string.Empty; + + int fontsize = isforhud ? 100 : 55; + + return $"{string.Format(Translator.GetString("SpurtSuffix"), DetermineCharge(player))}"; + } + + public static void OnFixedUpdate(PlayerControl player) + { + var pos = player.Pos(); + bool moving = Vector2.Distance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); + LastPos[player.PlayerId] = pos; + + float modulator = Modulator.GetFloat(); + float ChargeBy = Mathf.Clamp(modulator / 20 * 1.5f, 0.05f, 0.6f); + float Decreaseby = Mathf.Clamp(modulator / 20 * 0.5f, 0.01f, 0.3f); + + int charge = DetermineCharge(player); + if (DisplaysCharge.GetBool() && !player.IsModClient() && LastNum[player.PlayerId] != charge) + { + LastNum[player.PlayerId] = charge; + long now = Utils.TimeStamp; + if (now != LastUpdate[player.PlayerId]) + { + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + LastUpdate[player.PlayerId] = now; + } + } + + if (!moving) + { + Main.AllPlayerSpeed[player.PlayerId] += Mathf.Clamp(ChargeBy, 0f, MaxSpeed.GetFloat() - Main.AllPlayerSpeed[player.PlayerId]); + return; + } + + Main.AllPlayerSpeed[player.PlayerId] -= Mathf.Clamp(Decreaseby, 0f, Main.AllPlayerSpeed[player.PlayerId] - MinSpeed.GetFloat()); + player.MarkDirtySettings(); + } + } +} diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index 79671ca219..502915105d 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -2,14 +2,12 @@ namespace TOHE.Roles.AddOns.Common; -public static class Statue +public class Statue : IAddon { private const int Id = 13800; + public AddonTypes Type => AddonTypes.Harmful; public static bool IsEnable = false; - public static OptionItem CanBeOnCrew; - public static OptionItem CanBeOnImp; - public static OptionItem CanBeOnNeutral; private static OptionItem SlowDown; private static OptionItem PeopleAmount; @@ -17,16 +15,13 @@ public static class Statue private static HashSet CountNearplr; private static Dictionary tempSpeed; - public static void SetupCustomOptions() + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Statue, canSetNum: true, tab: TabGroup.Addons); + Options.SetupAdtRoleOptions(Id, CustomRoles.Statue, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); SlowDown = FloatOptionItem.Create(Id + 10, "StatueSlow", new(0f, 1.25f, 0.25f), 0f, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Statue]) .SetValueFormat(OptionFormat.Multiplier); PeopleAmount = IntegerOptionItem.Create(Id + 11, "StatuePeopleToSlow", new(1, 5, 1), 3, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Statue]) .SetValueFormat(OptionFormat.Times); - CanBeOnImp = BooleanOptionItem.Create(Id + 12, "ImpCanBeStatue", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Statue]); - CanBeOnCrew = BooleanOptionItem.Create(Id + 13, "CrewCanBeStatue", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Statue]); - CanBeOnNeutral = BooleanOptionItem.Create(Id + 14, "NeutralCanBeStatue", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Statue]); } public static void Init() diff --git a/Roles/AddOns/Common/Stubborn.cs b/Roles/AddOns/Common/Stubborn.cs index 460bf020d4..5856c4d906 100644 --- a/Roles/AddOns/Common/Stubborn.cs +++ b/Roles/AddOns/Common/Stubborn.cs @@ -2,21 +2,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Stubborn +public class Stubborn : IAddon { private const int Id = 22500; + public AddonTypes Type => AddonTypes.Mixed; - public static OptionItem ImpCanBeStubborn; - public static OptionItem CrewCanBeStubborn; - public static OptionItem NeutralCanBeStubborn; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - - SetupAdtRoleOptions(Id, CustomRoles.Stubborn, canSetNum: true); - ImpCanBeStubborn = BooleanOptionItem.Create(Id + 10, "ImpCanBeStubborn", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Stubborn]); - CrewCanBeStubborn = BooleanOptionItem.Create(Id + 11, "CrewCanBeStubborn", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Stubborn]); - NeutralCanBeStubborn = BooleanOptionItem.Create(Id + 12, "NeutralCanBeStubborn", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Stubborn]); + SetupAdtRoleOptions(Id, CustomRoles.Stubborn, canSetNum: true, teamSpawnOptions: true); } } diff --git a/Roles/AddOns/Common/Susceptible.cs b/Roles/AddOns/Common/Susceptible.cs index e1a382b67b..d1add8acf0 100644 --- a/Roles/AddOns/Common/Susceptible.cs +++ b/Roles/AddOns/Common/Susceptible.cs @@ -2,23 +2,18 @@ namespace TOHE.Roles.AddOns.Common; -public class Susceptible +public class Susceptible : IAddon { private const int Id = 27100; - public static OptionItem CanBeOnCrew; - public static OptionItem CanBeOnImp; - public static OptionItem CanBeOnNeutral; + public AddonTypes Type => AddonTypes.Mixed; private static OptionItem EnabledDeathReasons; public static PlayerState.DeathReason randomReason; - public static void SetupCustomOptions() + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Susceptible, canSetNum: true, tab: TabGroup.Addons); + Options.SetupAdtRoleOptions(Id, CustomRoles.Susceptible, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); EnabledDeathReasons = BooleanOptionItem.Create(Id + 11, "OnlyEnabledDeathReasons", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Susceptible]); - CanBeOnImp = BooleanOptionItem.Create(Id + 12, "ImpCanBeSusceptible", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Susceptible]); - CanBeOnCrew = BooleanOptionItem.Create(Id + 13, "CrewCanBeSusceptible", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Susceptible]); - CanBeOnNeutral = BooleanOptionItem.Create(Id + 14, "NeutralCanBeSusceptible", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Susceptible]); } private static void ChangeRandomDeath() diff --git a/Roles/AddOns/Common/Tiebreaker.cs b/Roles/AddOns/Common/Tiebreaker.cs index 8d6b84251f..dae25a4d95 100644 --- a/Roles/AddOns/Common/Tiebreaker.cs +++ b/Roles/AddOns/Common/Tiebreaker.cs @@ -1,21 +1,15 @@ namespace TOHE.Roles.AddOns.Common; -public static class Tiebreaker +public class Tiebreaker : IAddon { private const int Id = 20200; - - public static OptionItem ImpCanBeTiebreaker; - public static OptionItem CrewCanBeTiebreaker; - public static OptionItem NeutralCanBeTiebreaker; + public AddonTypes Type => AddonTypes.Helpful; public static List VoteFor = []; - public static void SetupCustomOptions() + public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Tiebreaker, canSetNum: true); - ImpCanBeTiebreaker = BooleanOptionItem.Create(Id + 10, "ImpCanBeTiebreaker", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiebreaker]); - CrewCanBeTiebreaker = BooleanOptionItem.Create(Id + 11, "CrewCanBeTiebreaker", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiebreaker]); - NeutralCanBeTiebreaker = BooleanOptionItem.Create(Id + 12, "NeutralCanBeTiebreaker", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiebreaker]); + Options.SetupAdtRoleOptions(Id, CustomRoles.Tiebreaker, canSetNum: true, teamSpawnOptions: true); } public static void Clear() diff --git a/Roles/AddOns/Common/Tired.cs b/Roles/AddOns/Common/Tired.cs index ca73b4afe7..c2064e6391 100644 --- a/Roles/AddOns/Common/Tired.cs +++ b/Roles/AddOns/Common/Tired.cs @@ -4,31 +4,26 @@ namespace TOHE.Roles.AddOns.Common; -public class Tired +public class Tired : IAddon { private const int Id = 27300; + public AddonTypes Type => AddonTypes.Harmful; private static Dictionary playerIdList; // Target Action player for Vision - public static OptionItem CanBeOnCrew; - public static OptionItem CanBeOnImp; - public static OptionItem CanBeOnNeutral; private static OptionItem SetVision; private static OptionItem SetSpeed; private static OptionItem TiredDuration; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Tired, canSetNum: true, tab: TabGroup.Addons); + SetupAdtRoleOptions(Id, CustomRoles.Tired, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); SetVision = FloatOptionItem.Create(Id + 10, "TiredVision", new(0f, 2f, 0.25f), 0.25f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tired]) .SetValueFormat(OptionFormat.Multiplier); SetSpeed = FloatOptionItem.Create(Id + 11, "TiredSpeed", new(0.25f, 3f, 0.25f), 0.75f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tired]) .SetValueFormat(OptionFormat.Multiplier); TiredDuration = FloatOptionItem.Create(Id + 12, "TiredDur", new(2f, 15f, 0.5f), 5f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tired]) .SetValueFormat(OptionFormat.Seconds); - CanBeOnImp = BooleanOptionItem.Create(Id + 13, "ImpCanBeTired", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tired]); - CanBeOnCrew = BooleanOptionItem.Create(Id + 14, "CrewCanBeTired", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tired]); - CanBeOnNeutral = BooleanOptionItem.Create(Id + 15, "NeutralCanBeTired", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tired]); } public static void Init() diff --git a/Roles/AddOns/Common/Unlucky.cs b/Roles/AddOns/Common/Unlucky.cs index 1230a22c32..c761494c75 100644 --- a/Roles/AddOns/Common/Unlucky.cs +++ b/Roles/AddOns/Common/Unlucky.cs @@ -2,18 +2,16 @@ namespace TOHE.Roles.AddOns.Common; -public static class Unlucky +public class Unlucky : IAddon { private const int Id = 21000; + public AddonTypes Type => AddonTypes.Harmful; private static OptionItem UnluckyTaskSuicideChance; private static OptionItem UnluckyKillSuicideChance; private static OptionItem UnluckyVentSuicideChance; private static OptionItem UnluckyReportSuicideChance; private static OptionItem UnluckyOpenDoorSuicideChance; - public static OptionItem ImpCanBeUnlucky; - public static OptionItem CrewCanBeUnlucky; - public static OptionItem NeutralCanBeUnlucky; public enum StateSuicide { @@ -24,9 +22,9 @@ public enum StateSuicide OpenDoor } - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Unlucky, canSetNum: true); + SetupAdtRoleOptions(Id, CustomRoles.Unlucky, canSetNum: true, teamSpawnOptions: true); UnluckyKillSuicideChance = IntegerOptionItem.Create(Id + 10, "UnluckyKillSuicideChance", new(0, 100, 1), 2, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) .SetValueFormat(OptionFormat.Percent); UnluckyTaskSuicideChance = IntegerOptionItem.Create(Id + 11, "UnluckyTaskSuicideChance", new(0, 100, 1), 5, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) @@ -37,9 +35,6 @@ public static void SetupCustomOptions() .SetValueFormat(OptionFormat.Percent); UnluckyOpenDoorSuicideChance = IntegerOptionItem.Create(Id + 14, "UnluckyOpenDoorSuicideChance", new(0, 100, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) .SetValueFormat(OptionFormat.Percent); - ImpCanBeUnlucky = BooleanOptionItem.Create(Id + 15, "ImpCanBeUnlucky", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]); - CrewCanBeUnlucky = BooleanOptionItem.Create(Id + 16, "CrewCanBeUnlucky", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]); - NeutralCanBeUnlucky = BooleanOptionItem.Create(Id + 17, "NeutralCanBeUnlucky", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]); } public static bool SuicideRand(PlayerControl victim, StateSuicide state) { diff --git a/Roles/AddOns/Common/Unreportable.cs b/Roles/AddOns/Common/Unreportable.cs index accbe08d78..b14895c65a 100644 --- a/Roles/AddOns/Common/Unreportable.cs +++ b/Roles/AddOns/Common/Unreportable.cs @@ -2,19 +2,13 @@ namespace TOHE.Roles.AddOns.Common; -public static class Unreportable +public class Unreportable : IAddon { private const int Id = 20500; + public AddonTypes Type => AddonTypes.Harmful; - public static OptionItem ImpCanBeUnreportable; - public static OptionItem CrewCanBeUnreportable; - public static OptionItem NeutralCanBeUnreportable; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Unreportable, canSetNum: true); - ImpCanBeUnreportable = BooleanOptionItem.Create(Id + 10, "ImpCanBeUnreportable", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unreportable]); - CrewCanBeUnreportable = BooleanOptionItem.Create(Id + 11, "CrewCanBeUnreportable", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unreportable]); - NeutralCanBeUnreportable = BooleanOptionItem.Create(Id + 12, "NeutralCanBeUnreportable", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unreportable]); + SetupAdtRoleOptions(Id, CustomRoles.Unreportable, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Voidballot.cs b/Roles/AddOns/Common/Voidballot.cs index 06951b8981..ca98b90e32 100644 --- a/Roles/AddOns/Common/Voidballot.cs +++ b/Roles/AddOns/Common/Voidballot.cs @@ -2,19 +2,13 @@ namespace TOHE.Roles.AddOns.Common; -public static class VoidBallot +public class VoidBallot : IAddon { private const int Id = 21100; + public AddonTypes Type => AddonTypes.Harmful; - public static OptionItem ImpCanBeVoidBallot; - public static OptionItem CrewCanBeVoidBallot; - public static OptionItem NeutralCanBeVoidBallot; - - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.VoidBallot, canSetNum: true); - ImpCanBeVoidBallot = BooleanOptionItem.Create(Id + 10, "ImpCanBeVoidBallot", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoidBallot]); - CrewCanBeVoidBallot = BooleanOptionItem.Create(Id + 11, "CrewCanBeVoidBallot", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoidBallot]); - NeutralCanBeVoidBallot = BooleanOptionItem.Create(Id + 12, "NeutralCanBeVoidBallot", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoidBallot]); + SetupAdtRoleOptions(Id, CustomRoles.VoidBallot, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Watcher.cs b/Roles/AddOns/Common/Watcher.cs index 2fc487ed93..ba6bd61e87 100644 --- a/Roles/AddOns/Common/Watcher.cs +++ b/Roles/AddOns/Common/Watcher.cs @@ -3,20 +3,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Watcher +public class Watcher : IAddon { private const int Id = 20400; - - public static OptionItem ImpCanBeWatcher; - public static OptionItem CrewCanBeWatcher; - public static OptionItem NeutralCanBeWatcher; + public AddonTypes Type => AddonTypes.Helpful; - public static void SetupCustomOptions() + public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Watcher, canSetNum: true); - ImpCanBeWatcher = BooleanOptionItem.Create(Id + 10, "ImpCanBeWatcher", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Watcher]); - CrewCanBeWatcher = BooleanOptionItem.Create(Id + 11, "CrewCanBeWatcher", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Watcher]); - NeutralCanBeWatcher = BooleanOptionItem.Create(Id + 12, "NeutralCanBeWatcher", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Watcher]); + SetupAdtRoleOptions(Id, CustomRoles.Watcher, canSetNum: true, teamSpawnOptions: true); } public static void RevealVotes(IGameOptions opt) => opt.SetBool(BoolOptionNames.AnonymousVotes, false); diff --git a/Roles/AddOns/Common/Youtuber.cs b/Roles/AddOns/Common/Youtuber.cs index 58f3dab477..7c46743191 100644 --- a/Roles/AddOns/Common/Youtuber.cs +++ b/Roles/AddOns/Common/Youtuber.cs @@ -3,13 +3,14 @@ namespace TOHE.Roles.AddOns.Common; -public static class Youtuber +public class Youtuber : IAddon { private const int Id = 25500; + public AddonTypes Type => AddonTypes.Misc; public static OptionItem KillerWinsWithYouTuber; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Youtuber, canSetNum: true, tab: TabGroup.Addons); KillerWinsWithYouTuber = BooleanOptionItem.Create(Id + 10, "Youtuber_KillerWinsWithYouTuber", false, TabGroup.Addons, false) diff --git a/Roles/AddOns/Crewmate/Bloodthirst.cs b/Roles/AddOns/Crewmate/Bloodthirst.cs index 2d01b6bee1..3dcb4d1022 100644 --- a/Roles/AddOns/Crewmate/Bloodthirst.cs +++ b/Roles/AddOns/Crewmate/Bloodthirst.cs @@ -3,15 +3,12 @@ namespace TOHE.Roles.AddOns.Crewmate; -public static class Bloodthirst +public class Bloodthirst : IAddon { private const int Id = 21700; + public AddonTypes Type => AddonTypes.Mixed; - public static OptionItem ImpCanBeAutopsy; - public static OptionItem CrewCanBeAutopsy; - public static OptionItem NeutralCanBeAutopsy; - - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Bloodthirst, canSetNum: true); } diff --git a/Roles/AddOns/Crewmate/Ghoul.cs b/Roles/AddOns/Crewmate/Ghoul.cs index 715dc2c4f9..d86be8dadf 100644 --- a/Roles/AddOns/Crewmate/Ghoul.cs +++ b/Roles/AddOns/Crewmate/Ghoul.cs @@ -2,13 +2,14 @@ namespace TOHE.Roles.AddOns.Crewmate; -public class Ghoul +public class Ghoul : IAddon { private const int Id = 21900; + public AddonTypes Type => AddonTypes.Mixed; public static HashSet KillGhoul = []; public static bool IsEnable; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Ghoul, canSetNum: true, tab: TabGroup.Addons); } diff --git a/Roles/AddOns/Crewmate/Hurried.cs b/Roles/AddOns/Crewmate/Hurried.cs index b38018d113..418226e0d2 100644 --- a/Roles/AddOns/Crewmate/Hurried.cs +++ b/Roles/AddOns/Crewmate/Hurried.cs @@ -1,15 +1,16 @@  namespace TOHE.Roles.AddOns.Crewmate; -public static class Hurried +public class Hurried : IAddon { private const int Id = 21300; + public AddonTypes Type => AddonTypes.Harmful; public static OptionItem CanBeOnMadMate; public static OptionItem CanBeOnTaskBasedCrew; public static OptionItem CanBeConverted; - public static void SetupCustomOption() + public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Hurried, canSetNum: true); CanBeOnMadMate = BooleanOptionItem.Create(Id + 11, "MadmateCanBeHurried", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Hurried]); diff --git a/Roles/AddOns/Crewmate/Lazy.cs b/Roles/AddOns/Crewmate/Lazy.cs index 2169355a89..ef294fead9 100644 --- a/Roles/AddOns/Crewmate/Lazy.cs +++ b/Roles/AddOns/Crewmate/Lazy.cs @@ -2,14 +2,15 @@ namespace TOHE.Roles.AddOns.Crewmate; -public class Lazy +public class Lazy : IAddon { private const int Id = 19300; + public AddonTypes Type => AddonTypes.Helpful; private static OptionItem TasklessCrewCanBeLazy; private static OptionItem TaskBasedCrewCanBeLazy; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Lazy, canSetNum: true); TasklessCrewCanBeLazy = BooleanOptionItem.Create(Id + 10, "TasklessCrewCanBeLazy", false, TabGroup.Addons, false) diff --git a/Roles/AddOns/Crewmate/Nimble.cs b/Roles/AddOns/Crewmate/Nimble.cs index 1524b3bd5c..b24c711a52 100644 --- a/Roles/AddOns/Crewmate/Nimble.cs +++ b/Roles/AddOns/Crewmate/Nimble.cs @@ -2,11 +2,12 @@ namespace TOHE.Roles.AddOns.Crewmate; -public class Nimble +public class Nimble : IAddon { private const int Id = 19700; + public AddonTypes Type => AddonTypes.Helpful; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Nimble, canSetNum: true, tab: TabGroup.Addons); } diff --git a/Roles/AddOns/Crewmate/Rascal.cs b/Roles/AddOns/Crewmate/Rascal.cs index 579e3d79a1..86083f1024 100644 --- a/Roles/AddOns/Crewmate/Rascal.cs +++ b/Roles/AddOns/Crewmate/Rascal.cs @@ -2,13 +2,14 @@ namespace TOHE.Roles.AddOns.Crewmate; -public class Rascal +public class Rascal : IAddon { private const int Id = 20800; + public AddonTypes Type => AddonTypes.Harmful; private static OptionItem RascalAppearAsMadmate; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Rascal, canSetNum: true, tab: TabGroup.Addons); RascalAppearAsMadmate = BooleanOptionItem.Create(Id + 10, "RascalAppearAsMadmate", true, TabGroup.Addons, false) diff --git a/Roles/AddOns/Crewmate/Torch.cs b/Roles/AddOns/Crewmate/Torch.cs index a6ac1726e8..69e895eb79 100644 --- a/Roles/AddOns/Crewmate/Torch.cs +++ b/Roles/AddOns/Crewmate/Torch.cs @@ -3,13 +3,14 @@ namespace TOHE.Roles.AddOns.Crewmate; -public class Torch +public class Torch : IAddon { private const int Id = 20300; + public AddonTypes Type => AddonTypes.Helpful; private static OptionItem TorchVision; private static OptionItem TorchAffectedByLights; - public static void SetupCustomOptions() + public void SetupCustomOption() { SetupAdtRoleOptions(Id , CustomRoles.Torch, canSetNum: true); TorchVision = FloatOptionItem.Create(Id +10, "TorchVision", new(0.5f, 5f, 0.25f), 1.25f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Torch]) diff --git a/Roles/AddOns/Crewmate/Workhorse.cs b/Roles/AddOns/Crewmate/Workhorse.cs index b96168a5f5..635444d06b 100644 --- a/Roles/AddOns/Crewmate/Workhorse.cs +++ b/Roles/AddOns/Crewmate/Workhorse.cs @@ -4,9 +4,10 @@ namespace TOHE.Roles.AddOns.Crewmate; -public static class Workhorse +public class Workhorse : IAddon { private const int Id = 23730; + public AddonTypes Type => AddonTypes.Misc; private static readonly HashSet playerIdList = []; public static bool IsEnable = false; @@ -21,7 +22,7 @@ public static class Workhorse private static int NumLongTasks; private static int NumShortTasks; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Workhorse, zeroOne: true); OptionAssignOnlyToCrewmate = BooleanOptionItem.Create(Id + 10, "AssignOnlyToCrewmate", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); diff --git a/Roles/AddOns/IAddon.cs b/Roles/AddOns/IAddon.cs new file mode 100644 index 0000000000..9f66df860d --- /dev/null +++ b/Roles/AddOns/IAddon.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +//Thanks EHR for https://github.com/Gurge44/EndlessHostRoles/blob/main/Roles/AddOns/IAddon.cs and everything related ;) + +namespace TOHE.Roles.AddOns +{ + public enum AddonTypes + { + Impostor, + Helpful, + Harmful, + Misc, + Guesser, + Mixed, + Experimental + } + internal interface IAddon + { + public AddonTypes Type { get; } + public void SetupCustomOption(); + } +} diff --git a/Roles/AddOns/Impostor/Circumvent.cs b/Roles/AddOns/Impostor/Circumvent.cs index a0e8afadae..b3440e32e1 100644 --- a/Roles/AddOns/Impostor/Circumvent.cs +++ b/Roles/AddOns/Impostor/Circumvent.cs @@ -1,11 +1,12 @@  namespace TOHE.Roles.AddOns.Impostor; -public static class Circumvent +public class Circumvent : IAddon { private const int Id = 22600; + public AddonTypes Type => AddonTypes.Impostor; - public static void SetupCustomOption() + public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Circumvent, canSetNum: true, tab: TabGroup.Addons); } diff --git a/Roles/AddOns/Impostor/Clumsy.cs b/Roles/AddOns/Impostor/Clumsy.cs index ddde646fed..1cb50bb9cd 100644 --- a/Roles/AddOns/Impostor/Clumsy.cs +++ b/Roles/AddOns/Impostor/Clumsy.cs @@ -2,15 +2,16 @@ namespace TOHE.Roles.AddOns.Impostor; -public static class Clumsy +public class Clumsy : IAddon { private const int Id = 22700; + public AddonTypes Type => AddonTypes.Impostor; private static OptionItem ChanceToMiss; private static Dictionary HasMissed; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Clumsy, canSetNum: true, tab: TabGroup.Addons); ChanceToMiss = IntegerOptionItem.Create(22703, "ChanceToMiss", new(0, 100, 5), 50, TabGroup.Addons, false) diff --git a/Roles/AddOns/Impostor/LastImpostor.cs b/Roles/AddOns/Impostor/LastImpostor.cs index 8971709aa1..a253e6ca1f 100644 --- a/Roles/AddOns/Impostor/LastImpostor.cs +++ b/Roles/AddOns/Impostor/LastImpostor.cs @@ -1,14 +1,15 @@ namespace TOHE.Roles.AddOns.Impostor; -public static class LastImpostor +public class LastImpostor : IAddon { private const int Id = 22800; + public AddonTypes Type => AddonTypes.Impostor; public static byte currentId = byte.MaxValue; private static OptionItem CooldownReduction; - public static void SetupCustomOption() + public void SetupCustomOption() { Options.SetupSingleRoleOptions(Id, TabGroup.Addons, CustomRoles.LastImpostor, 1); CooldownReduction = FloatOptionItem.Create(Id + 15, "OverclockedReduction", new(5f, 95f, 5f), 50f, TabGroup.Addons, false) diff --git a/Roles/AddOns/Impostor/Mare.cs b/Roles/AddOns/Impostor/Mare.cs index 75a795a84b..d2910379e9 100644 --- a/Roles/AddOns/Impostor/Mare.cs +++ b/Roles/AddOns/Impostor/Mare.cs @@ -3,16 +3,17 @@ namespace TOHE.Roles.AddOns.Impostor; -public static class Mare +public class Mare : IAddon { private const int Id = 23000; + public AddonTypes Type => AddonTypes.Impostor; public static List playerIdList = []; public static OptionItem KillCooldownInLightsOut; private static OptionItem SpeedInLightsOut; private static bool idAccelerated = false; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Mare, canSetNum: true, tab: TabGroup.Addons); SpeedInLightsOut = FloatOptionItem.Create(Id + 10, "MareAddSpeedInLightsOut", new(0.1f, 0.5f, 0.1f), 0.3f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) diff --git a/Roles/AddOns/Impostor/Mimic.cs b/Roles/AddOns/Impostor/Mimic.cs index 2ffa50f425..2ae38d26c7 100644 --- a/Roles/AddOns/Impostor/Mimic.cs +++ b/Roles/AddOns/Impostor/Mimic.cs @@ -1,12 +1,13 @@ using static TOHE.Options; namespace TOHE.Roles.AddOns.Impostor; -public static class Mimic +public class Mimic : IAddon { private const int Id = 23100; + public AddonTypes Type => AddonTypes.Impostor; private static OptionItem CanSeeDeadRolesOpt; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Mimic, canSetNum: true, tab: TabGroup.Addons); CanSeeDeadRolesOpt = BooleanOptionItem.Create(Id + 10, "MimicCanSeeDeadRoles", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Mimic]); diff --git a/Roles/AddOns/Impostor/Stealer.cs b/Roles/AddOns/Impostor/Stealer.cs index 3abcabb71b..bef351130b 100644 --- a/Roles/AddOns/Impostor/Stealer.cs +++ b/Roles/AddOns/Impostor/Stealer.cs @@ -2,14 +2,15 @@ namespace TOHE.Roles.AddOns.Impostor; -public static class Stealer +public class Stealer : IAddon { private const int Id = 23200; - + public AddonTypes Type => AddonTypes.Impostor; + private static OptionItem TicketsPerKill; private static OptionItem HideAdditionalVotes; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.TicketsStealer, canSetNum: true, tab: TabGroup.Addons); TicketsPerKill = FloatOptionItem.Create(Id + 3, "TicketsPerKill", new(0.1f, 10f, 0.1f), 0.5f, TabGroup.Addons, false) diff --git a/Roles/AddOns/Impostor/Swift.cs b/Roles/AddOns/Impostor/Swift.cs index dc3670d946..fc7beedd7f 100644 --- a/Roles/AddOns/Impostor/Swift.cs +++ b/Roles/AddOns/Impostor/Swift.cs @@ -3,11 +3,12 @@ namespace TOHE.Roles.AddOns.Impostor; -public static class Swift +public class Swift : IAddon { private const int Id = 23300; - - public static void SetupCustomOption() + public AddonTypes Type => AddonTypes.Impostor; + + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Swift, canSetNum: true, tab: TabGroup.Addons); } diff --git a/Roles/AddOns/Impostor/Tricky.cs b/Roles/AddOns/Impostor/Tricky.cs index d0de821ef9..477bb5246a 100644 --- a/Roles/AddOns/Impostor/Tricky.cs +++ b/Roles/AddOns/Impostor/Tricky.cs @@ -1,13 +1,14 @@ using static TOHE.Options; namespace TOHE.Roles.AddOns.Impostor; -public static class Tricky +public class Tricky : IAddon { private const int Id = 19900; + public AddonTypes Type => AddonTypes.Impostor; private static OptionItem EnabledDeathReasons; //private static Dictionary randomReason = []; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Tricky, canSetNum: true, tab: TabGroup.Addons); EnabledDeathReasons = BooleanOptionItem.Create(Id + 11, "OnlyEnabledDeathReasons", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tricky]); @@ -18,13 +19,13 @@ public static void SetupCustomOption() //} private static PlayerState.DeathReason ChangeRandomDeath() { - PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().Where(reason => reason.IsReasonEnabled()).ToArray(); + PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().Where(IsReasonEnabled).ToArray(); if (deathReasons.Length == 0 || !deathReasons.Contains(PlayerState.DeathReason.Kill)) deathReasons.AddItem(PlayerState.DeathReason.Kill); var random = IRandom.Instance; int randomIndex = random.Next(deathReasons.Length); return deathReasons[randomIndex]; } - private static bool IsReasonEnabled(this PlayerState.DeathReason reason) + private static bool IsReasonEnabled(PlayerState.DeathReason reason) { if (reason is PlayerState.DeathReason.etc) return false; if (!EnabledDeathReasons.GetBool()) return true; diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 18e1b859be..81b90f230b 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -336,6 +336,10 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target, bo target.RpcSetRole(RoleTypes.GuardianAngel); break; + case CustomRoles.Spurt: + Spurt.DeathTask(target); + break; + } } diff --git a/TOHE.csproj b/TOHE.csproj index bdd8debb33..3dc9154bf5 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -7,7 +7,7 @@ Town Of Host Enhanced Moe preview - + C:\Program Files\Epic Games\AmongUs Debug;Release;Canary true True diff --git a/main.cs b/main.cs index a43c4791b7..bb9e2c3b9a 100644 --- a/main.cs +++ b/main.cs @@ -849,6 +849,7 @@ public enum CustomRoles Rascal, Reach, Rebound, + Spurt, Recruit, Seer, Silent, From 26585d435fc82c9353385c95164c941c0eb94839 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 12:12:30 +0200 Subject: [PATCH 174/778] fix stringjunk, some more implementations --- Modules/OptionHolder.cs | 20 +++++- Patches/PlayerControlPatch.cs | 16 ++--- Resources/Lang/en_US.json | 123 ++------------------------------ Resources/roleColor.json | 1 + Roles/AddOns/Common/Glow.cs | 4 +- Roles/AddOns/Common/Radar.cs | 4 +- Roles/AddOns/Common/Spurt.cs | 4 +- Roles/AddOns/Common/Statue.cs | 4 +- Roles/AddOns/IAddon.cs | 7 +- Roles/AddOns/Impostor/Swift.cs | 2 +- Roles/Core/CustomRoleManager.cs | 13 ++++ main.cs | 26 ++++++- 12 files changed, 85 insertions(+), 139 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index edde9fa1f9..1077cc7f6b 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -134,6 +134,7 @@ private enum RatesZeroOne //public static OptionItem EnableGM; public static float DefaultKillCooldown = Main.NormalOptions?.KillCooldown ?? 20; public static OptionItem GhostsDoTasks; + public static Dictionary> GroupedAddons = []; // ------------ System Settings Tab ------------ @@ -565,6 +566,18 @@ private enum RatesZeroOne ]; public static SuffixModes GetSuffixMode() => (SuffixModes)SuffixMode.GetValue(); + private static void GroupAddons() + { + GroupedAddons = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(x => x.GetInterfaces().ToList().Contains(typeof(IAddon))) + .Select(x => (IAddon)Activator.CreateInstance(x)) + .Where(x => x != null) + .GroupBy(x => x.Type) + .ToDictionary(x => x.Key, x => x.Select(y => Enum.Parse(y.GetType().Name, true)).ToList()); + } + public static int SnitchExposeTaskLeft = 1; @@ -617,6 +630,7 @@ private static System.Collections.IEnumerator CoLoadOptions() // Start Load Settings if (IsLoaded) yield break; OptionSaver.Initialize(); + GroupAddons(); yield return null; @@ -924,17 +938,17 @@ private static System.Collections.IEnumerator CoLoadOptions() foreach (var addonType in addonTypes) { - int index = 0; - TextOptionItem.Create(titleId, $"RoleType.{addonType.Key}", TabGroup.Addons) .SetGameMode(CustomGameMode.Standard) .SetColor(GetAddonTypeColor(addonType.Key)) .SetHeader(true); titleId += 10; + if (addonType.Key == AddonTypes.Impostor) + Madmate.SetupMenuOptions(); + foreach (var addon in addonType.Value) { - index++; addon.SetupCustomOption(); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 827b1ad504..1d04f44b7c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1177,6 +1177,13 @@ public static Task DoPostfix(PlayerControl __instance) min.OnFixedUpdates(player); } + if (!GameStates.IsLobby && player.Is(CustomRoles.Spurt) && !Mathf.Approximately(Main.AllPlayerSpeed[player.PlayerId], Spurt.StartingSpeed[player.PlayerId]) && !GameStates.IsInTask && !GameStates.IsMeeting) // fix ludicrous bug + { + Main.AllPlayerSpeed[player.PlayerId] = Spurt.StartingSpeed[player.PlayerId]; + player.MarkDirtySettings(); + } + + if (GameStates.IsInTask) { if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) @@ -1205,18 +1212,11 @@ public static Task DoPostfix(PlayerControl __instance) Logger.Info($"Reset {player.GetRealName()}'s outfit", "LateOutfits..OnFixedUpdate"); } - if (player.Is(CustomRoles.Statue) && player.IsAlive()) - Statue.OnFixedUpdate(player); - - if (player.Is(CustomRoles.Spurt) && player.IsAlive()) - Spurt.OnFixedUpdate(player); + player.OnFixedAddonUpdate(lowLoad); if (!lowLoad) { CustomRoleManager.OnFixedUpdateLowLoad(player); - - if (Glow.IsEnable) - Glow.OnFixedUpdate(player); if (Radar.IsEnable) Radar.OnFixedUpdate(player); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 084223a4fc..ab53e50898 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1540,12 +1540,6 @@ "LoverKnowRoles": "Lovers know the roles of each other", "TrapperBlockMoveTime": "Freeze time", "BecomeTrapperBlockMoveTime": "Freeze time", - "ImpCanBeTrapper": "Impostors can become Beartrap", - "CrewCanBeTrapper": "Crewmates can become Beartrap", - "NeutralCanBeTrapper": "Neutrals can become Beartrap", - "ImpCanBeGravestone": "Impostors can become Gravestone", - "CrewCanBeGravestone": "Crewmates can become Gravestone", - "NeutralCanBeGravestone": "Neutrals can become Gravestone", "TimeThiefDecreaseMeetingTime": "Lower Meeting Time by", "TimeThiefLowerLimitVotingTime": "Minimum Voting Time", "TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death", @@ -1609,21 +1603,6 @@ "CrewCanBeEgoist": "Crewmates can become Egoist", "ImpEgoistVisibalToAllies": "Impostors Can See Other Egoist Impostors", "EgoistCountAsConverted": "Egoist count as converted neutral", - "ImpCanBeSeer": "Impostors can become Seer", - "CrewCanBeSeer": "Crewmates can become Seer", - "NeutralCanBeSeer": "Neutrals can become Seer", - "ImpCanBeGuesser": "Impostors can become Guesser", - "CrewCanBeGuesser": "Crewmates can become Guesser", - "NeutralCanBeGuesser": "Neutrals can become Guesser", - "ImpCanBeWatcher": "Impostors can become Watcher", - "CrewCanBeWatcher": "Crewmates can become Watcher", - "NeutralCanBeWatcher": "Neutrals can become Watcher", - "ImpCanBeBait": "Impostors can become Bait", - "CrewCanBeBait": "Crewmates can become Bait", - "NeutralCanBeBait": "Neutrals can become Bait", - "ImpCanBeRainbow": "Impostors can become Rainbow", - "NeutralCanBeRainbow": "Neutrals can become Rainbow", - "CrewCanBeRainbow": "Crewmates can become Rainbow", "GuessRainbow": "He seems too obvious, doesn't he?", "RainbowColorChangeCoolDown": "The cooldown for changing colors", "RainbowInCamouflage": "Rainbow color changes during Camouflage", @@ -1799,36 +1778,6 @@ "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", "JackalCanKillSidekick": "Jackal can kill Sidekick", - "ImpCanBeNecroview": "Impostors can become Necroview", - "CrewCanBeNecroview": "Crewmates can become Necroview", - "NeutralCanBeNecroview": "Neutrals can become Necroview", - "ImpCanBeInLove": "Impostors can be in love", - "CrewCanBeInLove": "Crewmates can be in love", - "NeutralCanBeInLove": "Neutrals can be in love", - "ImpCanBeOblivious": "Impostors can become Oblivious", - "CrewCanBeOblivious": "Crewmates can become Oblivious", - "NeutralCanBeOblivious": "Neutrals can become Oblivious", - "ImpCanBeTiebreaker": "Impostors can become Tiebreaker", - "CrewCanBeTiebreaker": "Crewmates can become Tiebreaker", - "NeutralCanBeTiebreaker": "Neutrals can become Tiebreaker", - "HexesLookLikeSpells": "Hexes appear as spells", - "HexButtonText": "Hex", - "ObliviousBaitImmune": "Immune to Bait", - "ImpCanBeOnbound": "Impostors can become Onbound", - "CrewCanBeOnbound": "Crewmates can become Onbound", - "NeutralCanBeOnbound": "Neutrals can become Onbound", - - "ImpCanBeRebound": "Impostors can become Rebound", - "CrewCanBeRebound": "Crewmates can become Rebound", - "NeutralCanBeRebound": "Neutrals can become Rebound", - - "CrewCanBeMundane": "Crewmates can become Mundane", - "NeutralCanBeMundane": "Neutrals can become Mundane", - "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", - - "ImpCanBeUnreportable": "Impostors can become Disregarded", - "CrewCanBeUnreportable": "Crewmates can become Disregarded", - "NeutralCanBeUnreportable": "Neutrals can become Disregarded", "PacifistCooldown": "Ability Cooldown", "PacifistMaxOfUseage": "Max Number of Ability Uses", "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", @@ -1849,12 +1798,6 @@ "PresidentRevealTitle": "PRESIDENT REVEAL", "LuckyProbability": "Probability of surviving a kill", - "ImpCanBeLucky": "Impostors can become Lucky", - "CrewCanBeLucky": "Crewmates can become Lucky", - "NeutralCanBeLucky": "Neutrals can become Lucky", - "ImpCanBeFool": "Impostors can become Fool", - "CrewCanBeFool": "Crewmates can become Fool", - "NeutralCanBeFool": "Neutrals can become Fool", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", @@ -2733,9 +2676,6 @@ "BodyCannotBeReported": "Body could not be reported", "BurstKillDelay": "Burst Kill Delay", "BurstNotify": "That was a Burst! Get in a vent or die.", - "ImpCanBeBurst": "Impostors can become Burst", - "CrewCanBeBurst": "Crewmates can become Burst", - "NeutralCanBeBurst": "Neutrals can become Burst", "BurstFailed": "Burst failed to bomb you", "ShroudButtonText": "Shroud", "ShroudCooldown": "Shroud Cooldown", @@ -2774,9 +2714,6 @@ "BerserkerTransform": "The Berserker has transformed into War, Horseman of the Apocalypse! Cry 'Havoc!', and let slip the dogs of war.", "WarKillCooldown": "War kill cooldown", - "ImpCanBeUnlucky": "Impostors can become Unlucky", - "CrewCanBeUnlucky": "Crewmates can become Unlucky", - "NeutralCanBeUnlucky": "Neutrals can become Unlucky", "BlackmailerSkillCooldown": "Blackmail Cooldown", "BlackmailerMax": "Maximum times blackmailed players may speak", "BlackmailerDead": "Warning! {0} has been blackmailed by a Blackmailer!", @@ -2786,10 +2723,6 @@ "UnluckyVentSuicideChance": "Chance to suicide from venting", "UnluckyReportSuicideChance": "Chance to suicide from reporting bodies", "UnluckyOpenDoorSuicideChance": "Chance to suicide from opening a door", - "ImpCanBeVoidBallot": "Impostors can become VoidBallot", - "CrewCanBeVoidBallot": "Crewmates can become VoidBallot", - "NeutralCanBeVoidBallot": "Neutrals can become VoidBallot", - "ImpCanBeAware": "Impostors can become Aware", "NeutralCanBeAware": "Neutrals can become Aware", "CrewCanBeAware": "Crewmates can become Aware", "AwareKnowRole": "Knows the role of the player", @@ -2830,9 +2763,6 @@ "SwapTitle": "SWAPPER", "SwapperTryHideMsg": "Try to hide Swapper's command", "SwapperPreResult": "Currently, you selected to swap votes between {0} and {1}.\nIf you feel unsure, use /swap 253 to clear your selection.", - "ImpCanBeFragile": "Impostors can become Fragile", - "NeutralCanBeFragile": "Neutrals can become Fragile", - "CrewCanBeFragile": "Crewmates can become Fragile", "ImpCanKillFragile": "Impostors can force kill Fragile", "NeutralCanKillFragile": "Neutrals can force kill Fragile", "CrewCanKillFragile": "Crewmates can force kill Fragile", @@ -2849,39 +2779,16 @@ "BloodthirstPlayerCount": "Max players alive for Bloodthirst", "ReflectHarmfulInteractions": "Reflect harmful interactions", - "ImpCanBeDiseased": "Impostors can become Diseased", - "NeutralCanBeDiseased": "Neutrals can become Diseased", - "CrewCanBeDiseased": "Crewmates can become Diseased", "DiseasedCDOpt": "Increase the cooldown by", "DiseasedCDReset": "Cooldown returns to normal after a meeting", - "ImpCanBeAntidote": "Impostors can become Antidote", - "NeutralCanBeAntidote": "Neutrals can become Antidote", - "CrewCanBeAntidote": "Crewmates can become Antidote", "AntidoteCDOpt": "Decrease the cooldown by", "AntidoteCDReset": "Cooldown returns to normal after a meeting", - "ImpCanBeRadar": "Impostors can become Radar", - "NeutralCanBeRadar": "Neutrals can become Radar", - "CrewCanBeRadar": "Crewmates can become Radar", - - "ImpCanBeGlow": "Impostors can become Glow", - "NeutralCanBeGlow": "Neutrals can become Glow", - "CrewCanBeGlow": "Crewmates can become Glow", "GlowRadius": "Glow Radius", "GlowVisionOthers": "Vision Boost for nearby Players", "GlowVisionSelf": "Vision Boost for Glow", - "ImpCanBeStubborn": "Impostors can become Stubborn", - "NeutralCanBeStubborn": "Neutrals can become Stubborn", - "CrewCanBeStubborn": "Crewmates can become Stubborn", - - "ImpCanBeAvanger": "Impostors can become Avenger", - "NeutralCanBeAvanger": "Neutrals can become Avenger", - "CrewCanBeAvanger": "Crewmates can become Avenger", - "ImpCanBeSleuth": "Impostors can become Sleuth", - "CrewCanBeSleuth": "Crewmates can become Sleuth", - "NeutralCanBeSleuth": "Neutrals can become Sleuth", "SleuthCanKnowKillerRole": "Can find the role of the killer", "SleuthNoticeKiller": "\nThe killer's role is {0}.", "SleuthNoticeVictim": "{0}'s role is {1}.", @@ -3024,22 +2931,10 @@ "KeeperTitle": "Keeper", "MaulRadius": "Maul Radius", - "ImpCanBeAutopsy": "Impostors can become Autopsy", - "CrewCanBeAutopsy": "Crewmates can become Autopsy", - "NeutralCanBeAutopsy": "Neutrals can become Autopsy", - "ImpCanBeCyber": "Impostors can become Cyber", - "CrewCanBeCyber": "Crewmates can become Cyber", - "NeutralCanBeCyber": "Neutrals can become Cyber", "ImpKnowCyberDead": "Impostors know if Cyber died", "CrewKnowCyberDead": "Crewmates know if Cyber died", "NeutralKnowCyberDead": "Neutrals know if Cyber died", "CyberKnown": "Everyone can see Cyber", - "ImpCanBeInfluenced": "Impostors can become Influenced", - "CrewCanBeInfluenced": "Crewmates can become Influenced", - "NeutralCanBeInfluenced": "Neutrals can become Influenced", - "ImpCanBeBewilder": "Impostors can become Bewilder", - "CrewCanBeBewilder": "Crewmates can become Bewilder", - "NeutralCanBeBewilder": "Neutrals can become Bewilder", "KillerGetBewilderVision": "Killer gets Bewilder's vision", "ImpCanBeOiiai": "Impostors can be OIIAI", "CrewCanBeOiiai": "Crewmates can be OIIAI", @@ -3284,6 +3179,10 @@ "EnigmaClueLevelTitle": "Enigma Level Clue!", "EnigmaClueFriendCodeTitle": "Enigma Friendcode Clue!", + "ImpCanBeRole": "Impostors can become {role}", + "CrewCanBeRole": "Crewmates can become {role}", + "NeutralCanBeRole": "Neutrals can become {role}", + "VotesPerKill": "Votes gained for each kill", "PickpocketGetVote": "You've got {0} votes", "VultureArrowsPointingToDeadBody": "Arrows pointing to dead bodies", @@ -3642,9 +3541,6 @@ "VoteDead": "The player you voted for was exiled before the meeting concluded. Your vote was rescinded.", - "ImpCanBeSilent": "Impostors can become Silent", - "CrewCanBeSilent": "Crewmates can become Silent", - "NeutralCanBeSilent": "Neutrals can become Silent", "LastMessageReplay": "Last System Message Replay", "Contributor": "Contributor", @@ -3652,10 +3548,6 @@ "dbConnect.InitFailurePublic": "Error while connecting to TOHE API, this could be caused by your internet connection. And so Sponsor+ perks are not available, you may continue to play as usual without these.", "dbConnect.nullFriendCode": "This build of TOHE is not available to users with no friendcode!", - "ImpCanBeSusceptible": "Impostors can become Susceptible", - "CrewCanBeSusceptible": "Crewmates can become Susceptible", - "NeutralCanBeSusceptible": "Neutrals can become Susceptible", - "Quizmaster": "Quizmaster", "QuizmasterInfo": "Quiz people to kill them in meetings", "QuizmasterInfoLong": "(Neutrals):\nAs the Quizmaster, you can mark a player using your kill button. In the next meeting, the marked player will have \"?!\" next to their name. The player will die if they answer the question wrong or doesn't answer. The player will live if the Quizmaster is killed/ejected in the same meeting.\nThe Quizmaster cannot mark multiple people in the same round", @@ -3739,9 +3631,6 @@ "TiredVision": "Vision When Tired", "TiredSpeed": "Speed When Tired", "TiredDur": "Tired Duration", - "ImpCanBeTired": "Impostors can become Tired", - "CrewCanBeTired": "Crewmates can become Tired", - "NeutralCanBeTired": "Neutrals can become Tired", "TiredNotify": "Zzz..", @@ -3757,10 +3646,6 @@ "StatueSlow": "Statue Slowness", "StatuePeopleToSlow": "People Needed To Slow", - "ImpCanBeStatue": "Impostors can become Statue", - "CrewCanBeStatue": "Crewmates can become Statue", - "NeutralCanBeStatue": "Neutrals can become Statue", - "WardenIncreaseSpeed": "Increase Speed By", "WardenWarn": "DANGER! RUN!", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 87c250b22c..8ed52bffc7 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -236,6 +236,7 @@ "Statue": "#7E9C8A", "Warden": "#588733", "Hawk": "#606c80", + "Spurt": "#c9e8f5", "Ghastly": "#9ad6d4", "Glow": "#E2F147", "Radar": "#1eff1e" diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index 459ba11532..7d5cf59db2 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -71,9 +71,9 @@ public static void ApplyGameOptions(IGameOptions opt, PlayerControl player) opt.SetFloat(FloatOptionNames.CrewLightMod, setCrewVision); } - public static void OnFixedUpdate(PlayerControl player) + public void OnFixedUpdateLowLoad(PlayerControl player) { - if (player == null || !player.Is(CustomRoles.Glow)) return; + if (!IsEnable || player == null || !player.Is(CustomRoles.Glow)) return; if (!Utils.IsActive(SystemTypes.Electrical)) { InRadius[player.PlayerId].Clear(); diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index 7e1d3dcd8c..1b55836f80 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -52,9 +52,9 @@ public static void ReceiveRPC(MessageReader reader) ClosestPlayer[radarId] = closest; } - public static void OnFixedUpdate(PlayerControl radarPC) + public void OnFixedUpdateLowLoad(PlayerControl radarPC) { - if (radarPC == null || !radarPC.Is(CustomRoles.Radar) || !GameStates.IsInTask) return; + if (!IsEnable || radarPC == null || !radarPC.Is(CustomRoles.Radar) || !GameStates.IsInTask) return; if (Main.AllAlivePlayerControls.Length <= 1) return; if (!ClosestPlayer.ContainsKey(radarPC.PlayerId)) ClosestPlayer[radarPC.PlayerId] = byte.MaxValue; diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index aaeda20d4f..173fa71417 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -81,8 +81,10 @@ public static string GetSuffix(PlayerControl player, bool isforhud = false) return $"{string.Format(Translator.GetString("SpurtSuffix"), DetermineCharge(player))}"; } - public static void OnFixedUpdate(PlayerControl player) + public void OnFixedUpdate(PlayerControl player) { + if (!player.Is(CustomRoles.Spurt) || !player.IsAlive()) return; + var pos = player.Pos(); bool moving = Vector2.Distance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); LastPos[player.PlayerId] = pos; diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index 502915105d..3509b26ced 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -61,8 +61,10 @@ public static void AfterMeetingTasks() }, 6f); } - public static void OnFixedUpdate(PlayerControl victim) + public void OnFixedUpdate(PlayerControl victim) { + if (!victim.Is(CustomRoles.Statue) || !victim.IsAlive()) return; + foreach (var PVC in Main.AllAlivePlayerControls) { if (CountNearplr.Contains(PVC.PlayerId) && Vector2.Distance(PVC.transform.position, victim.transform.position) > 2f) diff --git a/Roles/AddOns/IAddon.cs b/Roles/AddOns/IAddon.cs index 9f66df860d..ff53ecf272 100644 --- a/Roles/AddOns/IAddon.cs +++ b/Roles/AddOns/IAddon.cs @@ -18,9 +18,14 @@ public enum AddonTypes Mixed, Experimental } - internal interface IAddon + public interface IAddon { public AddonTypes Type { get; } public void SetupCustomOption(); + + public void OnFixedUpdate(PlayerControl pc) + { } + public void OnFixedUpdateLowLoad(PlayerControl pc) + { } } } diff --git a/Roles/AddOns/Impostor/Swift.cs b/Roles/AddOns/Impostor/Swift.cs index fc7beedd7f..bed161fef1 100644 --- a/Roles/AddOns/Impostor/Swift.cs +++ b/Roles/AddOns/Impostor/Swift.cs @@ -6,7 +6,7 @@ namespace TOHE.Roles.AddOns.Impostor; public class Swift : IAddon { private const int Id = 23300; - public AddonTypes Type => AddonTypes.Impostor; + public AddonTypes Type => AddonTypes.Experimental; public void SetupCustomOption() { diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 81b90f230b..28536ae4b8 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using System; using System.Text; +using TOHE.Roles.AddOns; using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; @@ -14,6 +15,7 @@ namespace TOHE.Roles.Core; public static class CustomRoleManager { public static readonly Dictionary RoleClass = []; + public static readonly Dictionary AddonClasses = []; public static RoleBase GetStaticRoleClass(this CustomRoles role) => RoleClass.TryGetValue(role, out var roleClass) & roleClass != null ? roleClass : new DefaultSetup(); public static List AllEnabledRoles => Main.PlayerStates.Values.Select(x => x.RoleClass).ToList(); //Since there are classes which use object attributes and playerstate is not removed. public static bool HasEnabled(this CustomRoles role) => role.GetStaticRoleClass().IsEnable; @@ -490,4 +492,15 @@ public static void Add() SuffixOthers = AllEnabledRoles.Select(suffix => (Func)suffix.GetSuffixOthers).FilterDuplicates(); OtherCollectionsSet = true; } + + // ADDONS //////////////////////////// + + public static void OnFixedAddonUpdate(this PlayerControl pc, bool lowload) => pc.GetCustomSubRoles().Do(x => { + if (AddonClasses.TryGetValue(x, out var IAddon)) + IAddon.OnFixedUpdate(pc); + else return; + + if (!lowload) + IAddon.OnFixedUpdateLowLoad(pc); + }); } diff --git a/main.cs b/main.cs index bb9e2c3b9a..b1319979fb 100644 --- a/main.cs +++ b/main.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Text; using System.Text.Json; +using TOHE.Roles.AddOns; using TOHE.Roles.Core; using TOHE.Roles.Double; using TOHE.Roles.Neutral; @@ -358,7 +359,29 @@ public static void LoadRoleClasses() } catch (Exception err) { - TOHE.Logger.Error($"Error at LoadRoleClasses: {err}", "LoadRoleClasses"); + Utils.ThrowException(err); + } + } + public static void LoadAddonClasses() + { + TOHE.Logger.Info("Loading All AddonClasses...", "LoadAddonClasses"); + try + { + var AddonTypes = Assembly.GetAssembly(typeof(IAddon))! + .GetTypes() + .Where(myType => myType.IsClass && !myType.IsInterface && myType.IsSubclassOf(typeof(IAddon))); + + foreach (var role in CustomRolesHelper.AllRoles.Skip(500)) + { + Type AddonType = AddonTypes.FirstOrDefault(x => x.Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)); + CustomRoleManager.AddonClasses.Add(role, (IAddon)Activator.CreateInstance(AddonType)); + } + + TOHE.Logger.Info("AddonClasses Loaded Successfully", "LoadAddonClasses"); + } + catch (Exception err) + { + Utils.ThrowException(err); } } static void UpdateCustomTranslation() @@ -495,6 +518,7 @@ public override void Load() ExceptionMessage = ""; LoadRoleClasses(); + LoadAddonClasses(); LoadRoleColors(); //loads all the role colors from default and then tries to load custom colors if any. CustomWinnerHolder.Reset(); From 586d974c38cf672d8f692d8afc40a1d0ab0221b1 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:35:00 +0200 Subject: [PATCH 175/778] lotta fixes --- Modules/CustomRolesHelper.cs | 12 ++++++------ Modules/OptionHolder.cs | 2 +- Patches/HudPatch.cs | 28 ++++------------------------ Patches/MeetingHudPatch.cs | 4 ++-- Patches/PlayerControlPatch.cs | 3 --- Resources/Lang/en_US.json | 18 +++++++++++++----- Resources/roleColor.json | 2 +- Roles/AddOns/Common/Spurt.cs | 2 +- Roles/AddOns/Impostor/Stealer.cs | 8 ++++---- Roles/Core/CustomRoleManager.cs | 2 +- Roles/Core/RoleBase.cs | 6 ------ Roles/Crewmate/Monarch.cs | 2 +- Roles/Impostor/Chronomancer.cs | 17 ++++------------- main.cs | 15 ++++++++++----- 14 files changed, 48 insertions(+), 73 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index c5f40bdb62..9f0500c8ab 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -324,7 +324,7 @@ CustomRoles.Knighted or CustomRoles.Glitch or CustomRoles.Pickpocket or CustomRoles.Stubborn or - CustomRoles.TicketsStealer; + CustomRoles.Stealer; } public static bool IsSpeedRole(this CustomRoles role) { @@ -364,7 +364,7 @@ CustomRoles.Tricky or CustomRoles.Mare or CustomRoles.Clumsy or CustomRoles.Mimic or - CustomRoles.TicketsStealer or + CustomRoles.Stealer or CustomRoles.Circumvent or CustomRoles.Swift; } @@ -373,7 +373,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Only add-ons if (!role.IsAdditionRole()) return false; - if(Options.AddonCanBeSettings.TryGetValue(role, out var o) && (o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.GetCustomRole().IsCrewmate())) + if(Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && !pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()))) return false; // if player already has this addon @@ -611,7 +611,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.VoidBallot: if (pc.Is(CustomRoles.Mayor) || pc.Is(CustomRoles.Vindicator) - || pc.Is(CustomRoles.TicketsStealer) + || pc.Is(CustomRoles.Stealer) || pc.Is(CustomRoles.Pickpocket) || pc.Is(CustomRoles.Dictator) || pc.Is(CustomRoles.Influenced) @@ -770,7 +770,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; - case CustomRoles.TicketsStealer: + case CustomRoles.Stealer: if (pc.Is(CustomRoles.Vindicator) || pc.Is(CustomRoles.Bomber) || pc.Is(CustomRoles.VoidBallot) @@ -837,7 +837,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.BountyHunter) || pc.Is(CustomRoles.Lightning) || pc.Is(CustomRoles.Hangman) - || pc.Is(CustomRoles.TicketsStealer) + || pc.Is(CustomRoles.Stealer) || pc.Is(CustomRoles.Tricky)) return false; if (!pc.GetCustomRole().IsImpostor()) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 1077cc7f6b..0d11b0afbb 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -945,7 +945,7 @@ private static System.Collections.IEnumerator CoLoadOptions() titleId += 10; if (addonType.Key == AddonTypes.Impostor) - Madmate.SetupMenuOptions(); + Madmate.SetupCustomMenuOptions(); foreach (var addon in addonType.Value) { diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 94929495bf..9a5e521e91 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -83,15 +83,13 @@ public static void Postfix(HudManager __instance) // Set lower info text for modded players if (LowerInfoText == null) { - LowerInfoText = UnityEngine.Object.Instantiate(__instance.KillButton.buttonLabelText); - LowerInfoText.transform.parent = __instance.transform; - LowerInfoText.transform.localPosition = new Vector3(0, -2f, 0); + LowerInfoText = UnityEngine.Object.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform, true); LowerInfoText.alignment = TextAlignmentOptions.Center; - LowerInfoText.color = Palette.EnabledColor; + LowerInfoText.transform.localPosition = new(0, -2f, 0); LowerInfoText.overflowMode = TextOverflowModes.Overflow; LowerInfoText.enableWordWrapping = false; - LowerInfoText.fontSizeMin = 2.8f; - LowerInfoText.fontSizeMax = 2.8f; + LowerInfoText.color = Color.white; + LowerInfoText.fontSize = LowerInfoText.fontSizeMax = LowerInfoText.fontSizeMin = 2.8f; } switch (Options.CurrentGameMode) { @@ -100,24 +98,6 @@ public static void Postfix(HudManager __instance) LowerInfoText.text = roleClass?.GetLowerText(player, player, isForMeeting: Main.MeetingIsStarted, isForHud: true) ?? string.Empty; LowerInfoText.text += "\n" + Spurt.GetSuffix(player, true); - - if (roleClass != null) - { - float size = roleClass.SetModdedLowerText(out Color32? faceColor); - - if (faceColor != null) - { - LowerInfoText.SetFaceColor(faceColor.Value); - LowerInfoText.SetOutlineColor(new Color32(0, 0, 0, 255)); - } - if (LowerInfoText.fontSizeMin != size) - { - LowerInfoText.fontSizeMin = size; - LowerInfoText.fontSizeMax = size; - } - } - - break; } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 0bad479c5b..872a7d0111 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -221,7 +221,7 @@ public static bool Prefix(MeetingHud __instance) playerRoleClass?.AddVisualVotes(ps, ref statesList); - if (CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) + if (CheckRole(ps.TargetPlayerId, CustomRoles.Stealer)) { Stealer.AddVisualVotes(ps, ref statesList); } @@ -770,7 +770,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta } // Additional votes - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Stealer)) { VoteNum += Stealer.AddRealVotesNum(ps); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 1d04f44b7c..23a13f137a 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1218,9 +1218,6 @@ public static Task DoPostfix(PlayerControl __instance) { CustomRoleManager.OnFixedUpdateLowLoad(player); - if (Radar.IsEnable) - Radar.OnFixedUpdate(player); - if (Options.LadderDeath.GetBool() && player.IsAlive()) FallFromLadder.FixedUpdate(player); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ab53e50898..332f77c208 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -183,6 +183,7 @@ "GuessMaster": "Guess Master", "Transporter": "Transporter", "TimeManager": "Time Manager", + "Spurt": "Spurt", "Veteran": "Veteran", "Bastion": "Bastion", "Bodyguard": "Bodyguard", @@ -326,7 +327,7 @@ "Avanger": "Avenger", "Youtuber": "YouTuber", "Egoist": "Egoist", - "TicketsStealer": "Stealer", + "Stealer": "Stealer", "Paranoia": "Paranoia", "Mimic": "Mimic", "Guesser": "Guesser", @@ -498,6 +499,7 @@ "ObserverInfo": "You can see all shield-animations", "PacifistInfo": "Vent to reset kill cooldowns", "MonarchInfo": "Give your crew extra voting power!", + "SpurtInfo": "Spring Like A rabbit!", "StealthInfo": "Killing Blinds Everyone in the Room", "PenguinInfo": "Drag your victims", "OverseerInfo": "Reveal roles of other players", @@ -627,7 +629,7 @@ "YoutuberInfo": "Get killed first to win", "CelebrityInfo": "Everyone knows when you die", "EgoistInfo": "Win on your own", - "TicketsStealerInfo": "Gain votes with kills", + "StealerInfo": "Gain votes with kills", "ParanoiaInfo": "You're dead and alive simultaneously", "MimicInfo": "Reveal killed players' roles to impostors upon death", "GuesserInfo": "Guess roles of players in meetings to kill", @@ -920,7 +922,7 @@ "AvangerInfoLong": "(Add-ons):\nHost can set whether the Impostor can become an Avenger. When the Avenger is killed (voted out, and irregular kills will not count), the Avenger will revenge a random player.", "YoutuberInfoLong": "(Add-ons):\nOnly Crewmate will become YouTuber. When the YouTuber is the first player to die in the game, the YouTuber will win alone. If the YouTuber does not meet the win conditions, the YouTuber will follow the Crewmate to win. Note: Indirect killing methods such as being exiled, being guessed by the Guesser, etc., will not trigger the skills of the YouTuber.", "EgoistInfoLong": "(Add-ons):\nMadmate and Neutrals won't be Egoist. If the Egoist's team wins, the Egoist wins instead of their team.", - "TicketsStealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the Host sets the vote number, and the decimal is rounded down).\nAlso, extra votes from the Stealer are hidden during the meeting depending on the options.", + "StealerInfoLong": "(Add-ons):\nEvery time a Stealer kills a person, he gets an additional vote (the Host sets the vote number, and the decimal is rounded down).\nAlso, extra votes from the Stealer are hidden during the meeting depending on the options.", "ParanoiaInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Paranoia, you will be considered as two players in the game to determine when the game ends due to killers having the majority. Additionally, this grants you an extra vote, depending on options.", "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called. This message will include information on roles which the Mimic killed.", "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess the roles of players in meetings to kill them.\nGuessing the incorrect role kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", @@ -955,6 +957,7 @@ "StubbornInfoLong": "(Add-ons):\nWith the Stubborn add-on, Eraser can't erase your role, Cleanser can't cleanse you, Bandit can't steal from you, and Monarch can't knight you.\nAdditionally, you can't gain any new add-ons from the Merchant.", "SwiftInfoLong": "(Add-ons):\nAs the Swift, you will not make any movement when you kill.\nNote: Swift also ignores Bait", "UnluckyInfoLong": "(Add-ons):\nAs Unlucky, when you complete tasks, kill, venting, or open door, you have a chance to die.", + "SpurtInfoLong": "(Add-ons):\nWhen you start walking, you gain an enormous speed boost, which swiftly deteriorates, until you have to rest still for a while to rejuvenate your speed.", "VoidBallotInfoLong": "(Add-ons):\nHolder of this add-on will have 0 vote count.", "AwareInfoLong": "(Add-ons):\nAs the Aware, you get a notification in the next meeting if a revealing role had interacted with you.", "FragileInfoLong": "(Add-ons):\nAs Fragile, you will instantly die if someone tries to use the kill button on you (even if the role cannot directly kill).", @@ -1312,7 +1315,6 @@ "MenuTitle.Settings": "★ Settings ★", "MenuTitle.TaskSettings": "★ Task Management ★", "MenuTitle.Guessers": "★ Guesser Mode ★", - "MenuTitle.GuesserModeRoles": "★ Add-ons for Guesser Mode ★", "ShieldPersonDiedFirst": "Shield player who dead first in the last game", "ShowShieldedPlayerToAll": "Reveal shielded player to all", @@ -2036,6 +2038,12 @@ "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", "MediumNotifySelf": "You established contact with {0}. Please ask them questions and wait for them to respond.\n\nRemaining ability uses: {1}", "MediumKnowPlayerDead": "Someone died somewhere", + + "SpurtMinSpeed": "Min Speed", + "SpurtMaxSpeed": "Max Speed", + "SpurtModule": "Speed Modulator", + "EnableSpurtCharge": "Display The Charge", + "SpurtSuffix": "\n« Spurt: {0}% »", "ByBard": "by Bard", "ByBardGetFailed": "Oops, I seem to be out of inspiration.", @@ -2048,7 +2056,7 @@ "VeteranMaxUsage": "Ability use limit reached", "GrenadierSkillInUse": "Ability in use", "GrenadierSkillStop": "Ability expired", - "TicketsStealerGetTicket": "You've got {0} votes", + "StealerGetTicket": "You've got {0} votes", "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", "CleanerCleanBody": "The body has been cleaned", "QuickShooterStoraging": "Bullets stored successfully", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 8ed52bffc7..576542cea5 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -171,7 +171,7 @@ "Avanger": "#ffab1c", "Youtuber": "#fb749b", "Egoist": "#5600ff", - "TicketsStealer": "#ff1919", + "Stealer": "#ff1919", "Paranoia": "#3a648f", "Mimic": "#ff1919", "Guesser": "#f8cd46", diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 173fa71417..d3401d5fd8 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -23,7 +23,7 @@ internal class Spurt : IAddon public void SetupCustomOption() { - const int id = 648950; + const int id = 28800; SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); MinSpeed = FloatOptionItem.Create(id + 6, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) diff --git a/Roles/AddOns/Impostor/Stealer.cs b/Roles/AddOns/Impostor/Stealer.cs index bef351130b..d907406361 100644 --- a/Roles/AddOns/Impostor/Stealer.cs +++ b/Roles/AddOns/Impostor/Stealer.cs @@ -12,11 +12,11 @@ public class Stealer : IAddon public void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.TicketsStealer, canSetNum: true, tab: TabGroup.Addons); + SetupAdtRoleOptions(Id, CustomRoles.Stealer, canSetNum: true, tab: TabGroup.Addons); TicketsPerKill = FloatOptionItem.Create(Id + 3, "TicketsPerKill", new(0.1f, 10f, 0.1f), 0.5f, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.TicketsStealer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Stealer]); HideAdditionalVotes = BooleanOptionItem.Create(Id + 4, "HideAdditionalVotes", false, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.TicketsStealer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Stealer]); } public static int AddRealVotesNum(PlayerVoteArea ps) { @@ -39,7 +39,7 @@ public static void AddVisualVotes(PlayerVoteArea votedPlayer, ref List x.GetRealKiller()?.PlayerId == killer.PlayerId) + 1) * TicketsPerKill.GetFloat()) .ToString("0.0#####"))); } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 28536ae4b8..c4a1698444 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -354,7 +354,7 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target, bo { switch (subRole) { - case CustomRoles.TicketsStealer when !inMeeting && !isSuicide: + case CustomRoles.Stealer when !inMeeting && !isSuicide: Stealer.OnMurderPlayer(killer); break; diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 928241a06e..20d942e177 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -419,12 +419,6 @@ public virtual void SetAbilityButtonText(HudManager hud, byte playerId) public virtual string GetSuffix(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) => string.Empty; public virtual string GetProgressText(byte playerId, bool comms) => string.Empty; - public virtual float SetModdedLowerText(out Color32? FaceColor) - { - FaceColor = null; - return 2.8f; - } - // // IMPORTANT note about otherIcons: diff --git a/Roles/Crewmate/Monarch.cs b/Roles/Crewmate/Monarch.cs index 5637cf4f5c..2dde4323ec 100644 --- a/Roles/Crewmate/Monarch.cs +++ b/Roles/Crewmate/Monarch.cs @@ -103,7 +103,7 @@ public override string GetProgressText(byte PlayerId, bool comms) private static bool CanBeKnighted(PlayerControl pc) { return pc != null && !pc.GetCustomRole().IsNotKnightable() && - !pc.IsAnySubRole(x => x is CustomRoles.Knighted or CustomRoles.Stubborn or CustomRoles.TicketsStealer); + !pc.IsAnySubRole(x => x is CustomRoles.Knighted or CustomRoles.Stubborn or CustomRoles.Stealer); } public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl target) => seer.Is(CustomRoles.Monarch) && target.Is(CustomRoles.Knighted) ? Main.roleColors[CustomRoles.Knighted] : ""; diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index 791a94f0be..f8709c8fc3 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -98,12 +98,6 @@ private string GetCharge() return sb.ToString(); } - private string GetModdedCharge() - { - var sb = new StringBuilder($"{(int)Math.Round(((double)ChargedTime / FullCharge) * 100)}% "); - - return sb.ToString(); - } public void SetCooldown() { if (IsInMassacre) @@ -168,14 +162,11 @@ public override string GetLowerText(PlayerControl seer, PlayerControl seen = nul bool ismeeting = GameStates.IsMeeting || isForMeeting; if (seer == seen && !ismeeting) { - if (!seer.IsModClient()) return GetCharge(); - else if (isForHud) return GetModdedCharge(); + if (!isForHud && seer.IsModClient()) + return string.Empty; + + return GetCharge(); } return ""; } - public override float SetModdedLowerText(out Color32? FaceColor) - { - FaceColor = GetPercentColor(ChargedTime); - return 3.8f; - } } \ No newline at end of file diff --git a/main.cs b/main.cs index b1319979fb..18aa8f7105 100644 --- a/main.cs +++ b/main.cs @@ -367,13 +367,18 @@ public static void LoadAddonClasses() TOHE.Logger.Info("Loading All AddonClasses...", "LoadAddonClasses"); try { - var AddonTypes = Assembly.GetAssembly(typeof(IAddon))! - .GetTypes() - .Where(myType => myType.IsClass && !myType.IsInterface && myType.IsSubclassOf(typeof(IAddon))); - foreach (var role in CustomRolesHelper.AllRoles.Skip(500)) + var IAddonType = typeof(IAddon); + var AddonTypes = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface); + + foreach (var role in CustomRolesHelper.AllRoles.Where(x => x > CustomRoles.NotAssigned)) { Type AddonType = AddonTypes.FirstOrDefault(x => x.Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)); + if (AddonType == null) continue; + CustomRoleManager.AddonClasses.Add(role, (IAddon)Activator.CreateInstance(AddonType)); } @@ -884,7 +889,7 @@ public enum CustomRoles Susceptible, Swift, Tiebreaker, - TicketsStealer, //stealer + Stealer, //stealer Torch, Trapper, Tricky, From b26fc13efe0418a5f6bf26ed1a740f2416eb7f12 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:17:34 +0200 Subject: [PATCH 176/778] remove guwno bullshit --- Modules/OptionHolder.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 0d11b0afbb..fdb040158b 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1,12 +1,9 @@ using System; using System.Reflection; using TOHE.Modules; -using TOHE.Roles.AddOns.Common; -using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; using UnityEngine; using TOHE.Roles.Core; -using static Il2CppMono.Security.X509.X520; using TOHE.Roles.AddOns; namespace TOHE; From ae8b057ccbf168132a6049cb3798b863587f006c Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:26:19 +0200 Subject: [PATCH 177/778] improvements --- Roles/Crewmate/Merchant.cs | 52 +++++--------------------------------- 1 file changed, 7 insertions(+), 45 deletions(-) diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index c1c6c128ed..66bb6ec3f5 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -1,4 +1,5 @@ -using static TOHE.Options; +using TOHE.Roles.AddOns; +using static TOHE.Options; using static TOHE.Translator; namespace TOHE.Roles.Crewmate; @@ -18,45 +19,6 @@ internal class Merchant : RoleBase private static readonly Dictionary addonsSold = []; private static readonly Dictionary> bribedKiller = []; - private static readonly List helpfulAddons = - [ - CustomRoles.Watcher, - CustomRoles.Seer, - CustomRoles.Bait, - CustomRoles.Cyber, - CustomRoles.Trapper, - CustomRoles.Tiebreaker, - CustomRoles.Necroview, - CustomRoles.Bewilder, - CustomRoles.Burst, - CustomRoles.Sleuth, - CustomRoles.Autopsy, - CustomRoles.Lucky - ]; - - private static readonly List harmfulAddons = - [ - CustomRoles.Oblivious, - //CustomRoles.Sunglasses, - CustomRoles.VoidBallot, - CustomRoles.Fragile, - CustomRoles.Unreportable, // Disregarded - CustomRoles.Unlucky - ]; - - private static readonly List neutralAddons = - [ - CustomRoles.Guesser, - CustomRoles.Diseased, - CustomRoles.Antidote, - CustomRoles.Aware, - CustomRoles.Gravestone, - //CustomRoles.Glow, - CustomRoles.Onbound, - CustomRoles.Stubborn, - CustomRoles.Rebound, - ]; - private static OptionItem OptionMaxSell; private static OptionItem OptionMoneyPerSell; private static OptionItem OptionMoneyRequiredToBribe; @@ -105,17 +67,17 @@ public override void Init() if (OptionCanSellHelpful.GetBool()) { - addons.AddRange(helpfulAddons); + addons.AddRange(GroupedAddons[AddonTypes.Helpful]); } if (OptionCanSellHarmful.GetBool()) { - addons.AddRange(harmfulAddons); + addons.AddRange(GroupedAddons[AddonTypes.Harmful]); } if (OptionCanSellNeutral.GetBool()) { - addons.AddRange(neutralAddons); + addons.AddRange(GroupedAddons[AddonTypes.Mixed]); } if (OptionSellOnlyEnabledAddons.GetBool()) { @@ -179,8 +141,8 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount if (AllAlivePlayer.Any()) { - bool helpfulAddon = helpfulAddons.Contains(addon); - bool harmfulAddon = !helpfulAddon; + bool helpfulAddon = GroupedAddons[AddonTypes.Helpful].Contains(addon); + bool harmfulAddon = GroupedAddons[AddonTypes.Harmful].Contains(addon); if (helpfulAddon && OptionSellOnlyHarmfulToEvil.GetBool()) { From e43d78dfbec2f63da257c5cd6c72bbd4dde7996f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:33:45 +0200 Subject: [PATCH 178/778] Revert solution file --- TOHE.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOHE.csproj b/TOHE.csproj index 3dc9154bf5..ad362c3f06 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -7,7 +7,7 @@ Town Of Host Enhanced Moe preview - C:\Program Files\Epic Games\AmongUs + Debug;Release;Canary true True From 9ee779e8047f5c212c6aa10ca3caf731971f657f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:41:46 +0200 Subject: [PATCH 179/778] null check --- Roles/Core/CustomRoleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index c4a1698444..34d6448981 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -496,7 +496,7 @@ public static void Add() // ADDONS //////////////////////////// public static void OnFixedAddonUpdate(this PlayerControl pc, bool lowload) => pc.GetCustomSubRoles().Do(x => { - if (AddonClasses.TryGetValue(x, out var IAddon)) + if (AddonClasses.TryGetValue(x, out var IAddon) && IAddon != null) IAddon.OnFixedUpdate(pc); else return; From 75b6288cea754d78d822162ba9996b87f1a53bdc Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:00:46 +0200 Subject: [PATCH 180/778] some fancy stuff --- main.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 18aa8f7105..25a3b7defb 100644 --- a/main.cs +++ b/main.cs @@ -4,6 +4,7 @@ using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP.Utils.Collections; using Il2CppInterop.Runtime.Injection; +using MonoMod.Utils; using System; using System.IO; using System.Reflection; @@ -372,15 +373,21 @@ public static void LoadAddonClasses() var AddonTypes = Assembly .GetExecutingAssembly() .GetTypes() - .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface); + .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface) + .Select(x => (IAddon)Activator.CreateInstance(x)) + .Where(x => x != null) + .GroupBy(x => Enum.Parse(x.GetType().Name, true)) + .ToDictionary(x => x.Key, x => x.First()); - foreach (var role in CustomRolesHelper.AllRoles.Where(x => x > CustomRoles.NotAssigned)) + CustomRoleManager.AddonClasses.AddRange(AddonTypes); + + /*foreach (var role in CustomRolesHelper.AllRoles.Where(x => x > CustomRoles.NotAssigned)) { Type AddonType = AddonTypes.FirstOrDefault(x => x.Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)); if (AddonType == null) continue; CustomRoleManager.AddonClasses.Add(role, (IAddon)Activator.CreateInstance(AddonType)); - } + }*/ TOHE.Logger.Info("AddonClasses Loaded Successfully", "LoadAddonClasses"); } From 4f84e80dee81e5a06fb0e1d84e177043417869d1 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:56:24 +0200 Subject: [PATCH 181/778] Drakos Negative IQ moment --- main.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/main.cs b/main.cs index 25a3b7defb..0003ffb25a 100644 --- a/main.cs +++ b/main.cs @@ -368,26 +368,14 @@ public static void LoadAddonClasses() TOHE.Logger.Info("Loading All AddonClasses...", "LoadAddonClasses"); try { - var IAddonType = typeof(IAddon); - var AddonTypes = Assembly + CustomRoleManager.AddonClasses.AddRange(Assembly .GetExecutingAssembly() .GetTypes() .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface) .Select(x => (IAddon)Activator.CreateInstance(x)) .Where(x => x != null) - .GroupBy(x => Enum.Parse(x.GetType().Name, true)) - .ToDictionary(x => x.Key, x => x.First()); - - CustomRoleManager.AddonClasses.AddRange(AddonTypes); - - /*foreach (var role in CustomRolesHelper.AllRoles.Where(x => x > CustomRoles.NotAssigned)) - { - Type AddonType = AddonTypes.FirstOrDefault(x => x.Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)); - if (AddonType == null) continue; - - CustomRoleManager.AddonClasses.Add(role, (IAddon)Activator.CreateInstance(AddonType)); - }*/ + .ToDictionary(x => CustomRolesHelper.AllRoles.First(role => x.GetType().Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)), x => x)); TOHE.Logger.Info("AddonClasses Loaded Successfully", "LoadAddonClasses"); } From 44421a3923dccd9f6318347ea31ffc209b52ca38 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 7 Aug 2024 20:10:13 +0200 Subject: [PATCH 182/778] bruh --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index 0003ffb25a..12d63ab004 100644 --- a/main.cs +++ b/main.cs @@ -375,7 +375,7 @@ public static void LoadAddonClasses() .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface) .Select(x => (IAddon)Activator.CreateInstance(x)) .Where(x => x != null) - .ToDictionary(x => CustomRolesHelper.AllRoles.First(role => x.GetType().Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)), x => x)); + .ToDictionary(x => Enum.Parse(x.GetType().Name, true), x => x)); TOHE.Logger.Info("AddonClasses Loaded Successfully", "LoadAddonClasses"); } From 6d2226684415c72b285e45cc5bca76122eb845ab Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 11:49:58 +0800 Subject: [PATCH 183/778] DoBlockNameChange in start game --- Modules/Utils.cs | 206 ++++++++++++++++------------------ Patches/MeetingHudPatch.cs | 2 - Patches/onGameStartedPatch.cs | 34 ++++-- 3 files changed, 123 insertions(+), 119 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 206fe955bc..8b96eb25e7 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1521,141 +1521,131 @@ void SetRealName() string name = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; if (Main.HostRealName != "" && player.AmOwner) name = Main.HostRealName; - if (name == "") return; - if (AmongUsClient.Instance.IsGameStarted) + if (name == "" || !GameStates.IsLobby) return; + + if (player.AmOwner && player.IsModClient()) { - if (Options.FormatNameMode.GetInt() == 1 && Main.HostRealName == "") name = Palette.GetColorName(player.Data.DefaultOutfit.ColorId); + if (GameStates.IsOnlineGame || GameStates.IsLocalGame) + { + name = Options.HideHostText.GetBool() ? $"{name}" + : $"{GetString("HostText")}{GetString("Icon")}{name}"; + } + if (Options.CurrentGameMode == CustomGameMode.FFA) + name = $"{GetString("ModeFFA")}\r\n" + name; } - else + + var modtag = ""; + if (Options.ApplyVipList.GetValue() == 1 && player.FriendCode != PlayerControl.LocalPlayer.FriendCode) { - if (!GameStates.IsLobby) return; - if (player.AmOwner) + if (IsPlayerVIP(player.FriendCode)) { - if (!player.IsModClient()) return; + string colorFilePath = @$"./TOHE-DATA/Tags/VIP_TAGS/{player.FriendCode}.txt"; + //static color + if (!Options.GradientTagsOpt.GetBool()) { - if (GameStates.IsOnlineGame || GameStates.IsLocalGame) + string startColorCode = "ffff00"; + if (File.Exists(colorFilePath)) { - name = Options.HideHostText.GetBool() ? $"{name}" - : $"{GetString("HostText")}{GetString("Icon")}{name}"; + string ColorCode = File.ReadAllText(colorFilePath); + _ = ColorCode.Trim(); + if (CheckColorHex(ColorCode)) startColorCode = ColorCode; } - - - //name = $"{GetString("HostText")}♥" + name; + //"ffff00" + modtag = $"{GetString("VipTag")}"; } - if (Options.CurrentGameMode == CustomGameMode.FFA) - name = $"{GetString("ModeFFA")}\r\n" + name; - } - var modtag = ""; - if (Options.ApplyVipList.GetValue() == 1 && player.FriendCode != PlayerControl.LocalPlayer.FriendCode) - { - if (IsPlayerVIP(player.FriendCode)) + else //gradient color { - string colorFilePath = @$"./TOHE-DATA/Tags/VIP_TAGS/{player.FriendCode}.txt"; - //static color - if (!Options.GradientTagsOpt.GetBool()) - { - string startColorCode = "ffff00"; - if (File.Exists(colorFilePath)) - { - string ColorCode = File.ReadAllText(colorFilePath); - _ = ColorCode.Trim(); - if (CheckColorHex(ColorCode)) startColorCode = ColorCode; - } - //"ffff00" - modtag = $"{GetString("VipTag")}"; - } - else //gradient color + string startColorCode = "ffff00"; + string endColorCode = "ffff00"; + string ColorCode = ""; + if (File.Exists(colorFilePath)) { - string startColorCode = "ffff00"; - string endColorCode = "ffff00"; - string ColorCode = ""; - if (File.Exists(colorFilePath)) - { - ColorCode = File.ReadAllText(colorFilePath); - if (ColorCode.Split(" ").Length == 2) - { - startColorCode = ColorCode.Split(" ")[0]; - endColorCode = ColorCode.Split(" ")[1]; - } - } - if (!CheckGradientCode(ColorCode)) + ColorCode = File.ReadAllText(colorFilePath); + if (ColorCode.Split(" ").Length == 2) { - startColorCode = "ffff00"; - endColorCode = "ffff00"; + startColorCode = ColorCode.Split(" ")[0]; + endColorCode = ColorCode.Split(" ")[1]; } - //"33ccff", "ff99cc" - if (startColorCode == endColorCode) modtag = $"{GetString("VipTag")}"; - - else modtag = GradientColorText(startColorCode, endColorCode, GetString("VipTag")); } + if (!CheckGradientCode(ColorCode)) + { + startColorCode = "ffff00"; + endColorCode = "ffff00"; + } + //"33ccff", "ff99cc" + if (startColorCode == endColorCode) modtag = $"{GetString("VipTag")}"; + + else modtag = GradientColorText(startColorCode, endColorCode, GetString("VipTag")); } } - if (Options.ApplyModeratorList.GetValue() == 1 && player.FriendCode != PlayerControl.LocalPlayer.FriendCode) + } + if (Options.ApplyModeratorList.GetValue() == 1 && player.FriendCode != PlayerControl.LocalPlayer.FriendCode) + { + if (IsPlayerModerator(player.FriendCode)) { - if (IsPlayerModerator(player.FriendCode)) + string colorFilePath = @$"./TOHE-DATA/Tags/MOD_TAGS/{player.FriendCode}.txt"; + //static color + if (!Options.GradientTagsOpt.GetBool()) { - string colorFilePath = @$"./TOHE-DATA/Tags/MOD_TAGS/{player.FriendCode}.txt"; - //static color - if (!Options.GradientTagsOpt.GetBool()) - { - string startColorCode = "8bbee0"; - if (File.Exists(colorFilePath)) - { - string ColorCode = File.ReadAllText(colorFilePath); - _ = ColorCode.Trim(); - if (CheckColorHex(ColorCode)) startColorCode = ColorCode; - } - //"33ccff", "ff99cc" - modtag = $"{GetString("ModTag")}"; + string startColorCode = "8bbee0"; + if (File.Exists(colorFilePath)) + { + string ColorCode = File.ReadAllText(colorFilePath); + _ = ColorCode.Trim(); + if (CheckColorHex(ColorCode)) startColorCode = ColorCode; } - else //gradient color + //"33ccff", "ff99cc" + modtag = $"{GetString("ModTag")}"; + } + else //gradient color + { + string startColorCode = "8bbee0"; + string endColorCode = "8bbee0"; + string ColorCode = ""; + if (File.Exists(colorFilePath)) { - string startColorCode = "8bbee0"; - string endColorCode = "8bbee0"; - string ColorCode = ""; - if (File.Exists(colorFilePath)) - { - ColorCode = File.ReadAllText(colorFilePath); - if (ColorCode.Split(" ").Length == 2) - { - startColorCode = ColorCode.Split(" ")[0]; - endColorCode = ColorCode.Split(" ")[1]; - } - } - if (!CheckGradientCode(ColorCode)) + ColorCode = File.ReadAllText(colorFilePath); + if (ColorCode.Split(" ").Length == 2) { - startColorCode = "8bbee0"; - endColorCode = "8bbee0"; + startColorCode = ColorCode.Split(" ")[0]; + endColorCode = ColorCode.Split(" ")[1]; } - //"33ccff", "ff99cc" - if (startColorCode == endColorCode) modtag = $"{GetString("ModTag")}"; - - else modtag = GradientColorText(startColorCode, endColorCode, GetString("ModTag")); } + if (!CheckGradientCode(ColorCode)) + { + startColorCode = "8bbee0"; + endColorCode = "8bbee0"; + } + //"33ccff", "ff99cc" + if (startColorCode == endColorCode) modtag = $"{GetString("ModTag")}"; + + else modtag = GradientColorText(startColorCode, endColorCode, GetString("ModTag")); } } - if (player.AmOwner) + } + if (player.AmOwner) + { + name = Options.GetSuffixMode() switch { - name = Options.GetSuffixMode() switch - { - SuffixModes.TOHE => name += $"\r\nTOHE v{Main.PluginDisplayVersion}", - SuffixModes.Streaming => name += $"\r\n{GetString("SuffixMode.Streaming")}", - SuffixModes.Recording => name += $"\r\n{GetString("SuffixMode.Recording")}", - SuffixModes.RoomHost => name += $"\r\n{GetString("SuffixMode.RoomHost")}", - SuffixModes.OriginalName => name += $"\r\n{DataManager.player.Customization.Name}", - SuffixModes.DoNotKillMe => name += $"\r\n{GetString("SuffixModeText.DoNotKillMe")}", - SuffixModes.NoAndroidPlz => name += $"\r\n{GetString("SuffixModeText.NoAndroidPlz")}", - SuffixModes.AutoHost => name += $"\r\n{GetString("SuffixModeText.AutoHost")}", - _ => name - }; - } + SuffixModes.TOHE => name += $"\r\nTOHE v{Main.PluginDisplayVersion}", + SuffixModes.Streaming => name += $"\r\n{GetString("SuffixMode.Streaming")}", + SuffixModes.Recording => name += $"\r\n{GetString("SuffixMode.Recording")}", + SuffixModes.RoomHost => name += $"\r\n{GetString("SuffixMode.RoomHost")}", + SuffixModes.OriginalName => name += $"\r\n{DataManager.player.Customization.Name}", + SuffixModes.DoNotKillMe => name += $"\r\n{GetString("SuffixModeText.DoNotKillMe")}", + SuffixModes.NoAndroidPlz => name += $"\r\n{GetString("SuffixModeText.NoAndroidPlz")}", + SuffixModes.AutoHost => name += $"\r\n{GetString("SuffixModeText.AutoHost")}", + _ => name + }; + } - if (!name.Contains($"\r\r") && player.FriendCode.GetDevUser().HasTag() && (player.AmOwner || player.IsModClient())) - { - name = player.FriendCode.GetDevUser().GetTag() + "" + modtag + "" + name; - } - else name = modtag + name; + if (!name.Contains($"\r\r") && player.FriendCode.GetDevUser().HasTag() && (player.AmOwner || player.IsModClient())) + { + name = player.FriendCode.GetDevUser().GetTag() + "" + modtag + "" + name; } + else name = modtag + name; + + // Set name if (name != player.name && player.CurrentOutfitType == PlayerOutfitType.Default) player.RpcSetName(name); } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 0bad479c5b..ab58f20850 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -503,7 +503,6 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti { try { - Main.DoBlockNameChange = true; if (GameStates.IsInGame) { exiledPlayer.UpdateName(name, GetClientById(exiledPlayer.ClientId)); @@ -523,7 +522,6 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti if (GameStates.IsInGame && !player.Data.Disconnected) { player?.RpcSetName(realName); - Main.DoBlockNameChange = false; } if (GameStates.IsInGame && player.Data.Disconnected) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 90e51c5870..93636d5754 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -111,9 +111,10 @@ public static void Postfix(AmongUsClient __instance) IRandom.SetInstanceById(Options.RoleAssigningAlgorithm.GetValue()); + Main.DoBlockNameChange = true; + // Sync Player Names RPC.SyncAllPlayerNames(); - //Main.AllPlayerNames = []; GhostRoleAssign.Init(); @@ -140,31 +141,46 @@ public static void Postfix(AmongUsClient __instance) foreach (var pc in Main.AllPlayerControls) { + var outfit = pc.Data.DefaultOutfit; var colorId = pc.Data.DefaultOutfit.ColorId; - if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); + var currentName = ""; + if (AmongUsClient.Instance.AmHost) + { + if (Options.FormatNameMode.GetInt() == 1) + { + var coloredName = Palette.GetColorName(colorId); + currentName = coloredName; + pc.RpcSetName(coloredName); + } + else + { + string realName = Main.AllPlayerNames.TryGetValue(pc.PlayerId, out var name) ? name : ""; + //Logger.Info($"player id: {pc.PlayerId} {realName}", "FinallyBegin"); + if (realName == "") continue; + + currentName = realName; + pc.RpcSetName(realName); + } + } Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId) { - NormalOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set(pc.CurrentOutfit.PlayerName, pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.NamePlateId), + NormalOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set(currentName, pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.NamePlateId), }; - //Main.AllPlayerNames[pc.PlayerId] = pc?.Data?.PlayerName; Main.PlayerColors[pc.PlayerId] = Palette.PlayerColors[colorId]; if (GameStates.IsNormalGame) - Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); //移動速度をデフォルトの移動速度に変更 + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; pc.cosmetics.nameText.text = pc.name; - var outfit = pc.Data.DefaultOutfit; - Camouflage.PlayerSkins[pc.PlayerId] = new NetworkedPlayerInfo.PlayerOutfit().Set(outfit.PlayerName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId, outfit.NamePlateId); + Camouflage.PlayerSkins[pc.PlayerId] = new NetworkedPlayerInfo.PlayerOutfit().Set(currentName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId, outfit.NamePlateId); Main.clientIdList.Add(pc.GetClientId()); } - - Main.VisibleTasksCount = true; if (__instance.AmHost) { From 6597dc8b3ef7eba5f4ab75ce45fcd440d6e57f59 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 11:52:53 +0800 Subject: [PATCH 184/778] 674 => 692 --- TOHE.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOHE.csproj b/TOHE.csproj index bdd8debb33..3cc6e63158 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -24,7 +24,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From e68383a268a74476f90cf11c0329f8ca31a6b852 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 11:59:10 +0800 Subject: [PATCH 185/778] Check --- Roles/Crewmate/Veteran.cs | 8 ++++++++ Roles/Impostor/BountyHunter.cs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs index 18a3bf9645..3b8f5b15fa 100644 --- a/Roles/Crewmate/Veteran.cs +++ b/Roles/Crewmate/Veteran.cs @@ -61,6 +61,14 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { + var killerRole = killer.GetCustomRole(); + // Not should kill + if (killerRole is CustomRoles.Taskinator + or CustomRoles.Crusader + or CustomRoles.Bodyguard + or CustomRoles.Deputy) + return true; + if (killer.PlayerId != target.PlayerId && VeteranInProtect.TryGetValue(target.PlayerId, out var time)) if (time + VeteranSkillDuration.GetInt() >= GetTimeStamp()) { diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index 79d858df43..66fcad690e 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -129,6 +129,8 @@ public override void OnFixedUpdate(PlayerControl player) else { var targetId = GetTarget(player); + if (targetId == byte.MaxValue) return; + if (ChangeTimer[player.PlayerId] >= TargetChangeTime) { ResetTarget(player); From dc6a2dc1f2c50abf04b1b26ed94f7cb18af2f14a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 10 Aug 2024 06:53:34 +0200 Subject: [PATCH 186/778] remove redundant method --- Modules/ExtendedPlayerControl.cs | 1 - Roles/AddOns/Common/Spurt.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 95a1380dc5..9ffea92a61 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -349,7 +349,6 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, } player.ResetKillCooldown(); } - public static Vector2 Pos(this PlayerControl pc) => new(pc.transform.position.x, pc.transform.position.y); public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, bool force = false) { Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index d3401d5fd8..170125ffc5 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -44,7 +44,7 @@ public static void Add() { if (pc.Is(CustomRoles.Spurt)) { - LastPos[pc.PlayerId] = pc.Pos(); + LastPos[pc.PlayerId] = pc.GetCustomPosition(); LastNum[pc.PlayerId] = 0; LastUpdate[pc.PlayerId] = Utils.TimeStamp; StartingSpeed[pc.PlayerId] = speed; @@ -85,7 +85,7 @@ public void OnFixedUpdate(PlayerControl player) { if (!player.Is(CustomRoles.Spurt) || !player.IsAlive()) return; - var pos = player.Pos(); + var pos = player.GetCustomPosition(); bool moving = Vector2.Distance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); LastPos[player.PlayerId] = pos; From 4e4ffe68e0c3c4d2064ba9ccd47e23a8019d4519 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:07:16 +0800 Subject: [PATCH 187/778] OutfitManager.cs --- Modules/Camouflague.cs | 37 +------- Modules/ExtendedPlayerControl.cs | 75 ---------------- Modules/OutfitManager.cs | 146 +++++++++++++++++++++++++++++++ Roles/Impostor/Devourer.cs | 42 +-------- Roles/Neutral/Doppelganger.cs | 69 ++------------- 5 files changed, 157 insertions(+), 212 deletions(-) create mode 100644 Modules/OutfitManager.cs diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index 1b98ab19ff..04531981b2 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -1,4 +1,5 @@ using AmongUs.Data; +using TOHE.Modules; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; @@ -209,40 +210,6 @@ public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bo if (newOutfit.Compare(target.Data.DefaultOutfit)) return; Logger.Info($"playerId {target.PlayerId} newOutfit={newOutfit.GetString().RemoveHtmlTags()}", "RpcSetSkin"); - - // Start to set Outfit - var sender = CustomRpcSender.Create(name: $"Camouflage.RpcSetSkin({target.Data.PlayerName})"); - - target.SetColor(newOutfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) - .Write(target.Data.NetId) - .Write((byte)newOutfit.ColorId) - .EndRpc(); - - target.SetHat(newOutfit.HatId, newOutfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) - .Write(newOutfit.HatId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - target.SetSkin(newOutfit.SkinId, newOutfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) - .Write(newOutfit.SkinId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - target.SetVisor(newOutfit.VisorId, newOutfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) - .Write(newOutfit.VisorId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - target.SetPet(newOutfit.PetId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) - .Write(newOutfit.PetId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - sender.SendMessage(); + target.SetNewOutfit(newOutfit, setName: false, setNamePlate: false); } } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 9ffea92a61..22cbf32aa5 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -349,81 +349,6 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, } player.ResetKillCooldown(); } - public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, bool force = false) - { - Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; - - void Setoutfit() - { - var sender = CustomRpcSender.Create(name: $"Reset PlayerOufit for 『{player.Data.PlayerName}』"); - - player.SetName(Outfit.PlayerName); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(Outfit.PlayerName) - .EndRpc(); - - Main.AllPlayerNames[player.PlayerId] = Outfit.PlayerName; - - player.SetColor(Outfit.ColorId); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetColor) - .Write(player.Data.NetId) - .Write((byte)Outfit.ColorId) - .EndRpc(); - - player.SetHat(Outfit.HatId, Outfit.ColorId); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) - .Write(Outfit.HatId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - player.SetSkin(Outfit.SkinId, Outfit.ColorId); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) - .Write(Outfit.SkinId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - player.SetVisor(Outfit.VisorId, Outfit.ColorId); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) - .Write(Outfit.VisorId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - player.SetPet(Outfit.PetId); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) - .Write(Outfit.PetId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - player.SetNamePlate(Outfit.NamePlateId); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) - .Write(Outfit.NamePlateId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) - .EndRpc(); - - sender.SendMessage(); - - //cannot use currentoutfit type because of mushroom mixup . . - var OutfitTypeSet = player.CurrentOutfitType != PlayerOutfitType.Shapeshifted ? PlayerOutfitType.Default : PlayerOutfitType.Shapeshifted; - - player.Data.SetOutfit(OutfitTypeSet, Outfit); - - //Used instead of GameData.Instance.DirtyAllData(); - foreach (var innerNetObject in GameData.Instance.AllPlayers) - { - innerNetObject.SetDirtyBit(uint.MaxValue); - } - } - if (player.CheckCamoflague() && !force) - { - Main.LateOutfits[player.PlayerId] = Setoutfit; - } - else - { - Main.LateOutfits.Remove(player.PlayerId); - Setoutfit(); - } - } public static void SetKillCooldownV3(this PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) { if (player == null) return; diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs new file mode 100644 index 0000000000..94843d86f0 --- /dev/null +++ b/Modules/OutfitManager.cs @@ -0,0 +1,146 @@ +namespace TOHE.Modules; + +public static class OutfitManager +{ + public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, bool force = false) + { + Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; + + void Setoutfit() + { + var sender = CustomRpcSender.Create(name: $"Reset PlayerOufit for 『{player.Data.PlayerName}』"); + + player.SetName(Outfit.PlayerName); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(Outfit.PlayerName) + .EndRpc(); + + Main.AllPlayerNames[player.PlayerId] = Outfit.PlayerName; + + player.SetColor(Outfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetColor) + .Write(player.Data.NetId) + .Write((byte)Outfit.ColorId) + .EndRpc(); + + player.SetHat(Outfit.HatId, Outfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) + .Write(Outfit.HatId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); + + player.SetSkin(Outfit.SkinId, Outfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) + .Write(Outfit.SkinId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); + + player.SetVisor(Outfit.VisorId, Outfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) + .Write(Outfit.VisorId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); + + player.SetPet(Outfit.PetId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) + .Write(Outfit.PetId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + + player.SetNamePlate(Outfit.NamePlateId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) + .Write(Outfit.NamePlateId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) + .EndRpc(); + + sender.SendMessage(); + + //cannot use currentoutfit type because of mushroom mixup . . + var OutfitTypeSet = player.CurrentOutfitType != PlayerOutfitType.Shapeshifted ? PlayerOutfitType.Default : PlayerOutfitType.Shapeshifted; + + player.Data.SetOutfit(OutfitTypeSet, Outfit); + + //Used instead of GameData.Instance.DirtyAllData(); + foreach (var innerNetObject in GameData.Instance.AllPlayers) + { + innerNetObject.SetDirtyBit(uint.MaxValue); + } + } + if (player.CheckCamoflague() && !force) + { + Main.LateOutfits[player.PlayerId] = Setoutfit; + } + else + { + Main.LateOutfits.Remove(player.PlayerId); + Setoutfit(); + } + } + + public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit newOutfit, bool setName = true, bool setNamePlate = true, uint newLevel = 500) + { + // Start to set Outfit + var sender = CustomRpcSender.Create(name: $"SetOutfit({player.Data.PlayerName})"); + + if (setName) + { + player.SetName(newOutfit.PlayerName); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(newOutfit.PlayerName) + .EndRpc(); + + Main.AllPlayerNames[player.PlayerId] = newOutfit.PlayerName; + } + + player.SetColor(newOutfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetColor) + .Write(player.Data.NetId) + .Write((byte)newOutfit.ColorId) + .EndRpc(); + + player.SetHat(newOutfit.HatId, newOutfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) + .Write(newOutfit.HatId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); + + player.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) + .Write(newOutfit.SkinId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); + + player.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) + .Write(newOutfit.VisorId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); + + player.SetPet(newOutfit.PetId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) + .Write(newOutfit.PetId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + + if (setNamePlate) + { + player.SetNamePlate(newOutfit.NamePlateId); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) + .Write(newOutfit.NamePlateId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) + .EndRpc(); + } + + if (newLevel != 500) + { + player.SetLevel(newLevel); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetLevel) + .Write(newLevel) + .EndRpc(); + } + + sender.SendMessage(); + } +} diff --git a/Roles/Impostor/Devourer.cs b/Roles/Impostor/Devourer.cs index 67305a4eba..cf200e842b 100644 --- a/Roles/Impostor/Devourer.cs +++ b/Roles/Impostor/Devourer.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using TOHE.Modules; using static TOHE.Options; using static TOHE.Translator; @@ -90,7 +91,7 @@ private static void DoEatSkin(PlayerControl shapeshifter, PlayerControl target) { if (!Camouflage.IsCamouflage) { - SetSkin(target, ConsumedOutfit); + target.SetNewOutfit(ConsumedOutfit, setName: false, setNamePlate: false); } PlayerSkinsCosumed[shapeshifter.PlayerId].Add(target.PlayerId); @@ -123,7 +124,7 @@ private static void OnDevourerDied(PlayerControl devourer) Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == player); if (pc == null) continue; - SetSkin(pc, OriginalPlayerSkins[player]); + pc.SetNewOutfit(OriginalPlayerSkins[player], setName: false, setNamePlate: false); } } @@ -141,43 +142,6 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex OnDevourerDied(exiled.Object); } - private static void SetSkin(PlayerControl target, NetworkedPlayerInfo.PlayerOutfit outfit) - { - var sender = CustomRpcSender.Create(name: $"Devourer.RpcSetSkin({target.Data.PlayerName})"); - - target.SetColor(outfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) - .Write(target.Data.NetId) - .Write((byte)outfit.ColorId) - .EndRpc(); - - target.SetHat(outfit.HatId, outfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) - .Write(outfit.HatId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - target.SetSkin(outfit.SkinId, outfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) - .Write(outfit.SkinId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - target.SetVisor(outfit.VisorId, outfit.ColorId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) - .Write(outfit.VisorId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - target.SetPet(outfit.PetId); - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) - .Write(outfit.PetId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - sender.SendMessage(); - } - public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.AbilityButton.OverrideText(GetString("DevourerButtonText")); diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index 3b7f29f20e..a89c113ede 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -1,3 +1,4 @@ +using TOHE.Modules; using TOHE.Roles.Core; using TOHE.Roles.Impostor; using UnityEngine; @@ -49,64 +50,6 @@ public override void Add(byte playerId) public static bool CheckDoppelVictim(byte playerId) => DoppelVictim.ContainsKey(playerId); - private static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit, uint level) - { - var sender = CustomRpcSender.Create(name: $"Doppelganger.RpcChangeSkin({pc.Data.PlayerName})"); - - pc.SetName(newOutfit.PlayerName); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetName) - .Write(pc.Data.NetId) - .Write(newOutfit.PlayerName) - .EndRpc(); - - Main.AllPlayerNames[pc.PlayerId] = newOutfit.PlayerName; - - pc.SetColor(newOutfit.ColorId); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) - .Write(pc.Data.NetId) - .Write((byte)newOutfit.ColorId) - .EndRpc(); - - pc.SetHat(newOutfit.HatId, newOutfit.ColorId); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetHatStr) - .Write(newOutfit.HatId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - pc.SetSkin(newOutfit.SkinId, newOutfit.ColorId); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetSkinStr) - .Write(newOutfit.SkinId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - pc.SetVisor(newOutfit.VisorId, newOutfit.ColorId); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetVisorStr) - .Write(newOutfit.VisorId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - pc.SetPet(newOutfit.PetId); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) - .Write(newOutfit.PetId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - pc.SetNamePlate(newOutfit.NamePlateId); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetNamePlateStr) - .Write(newOutfit.NamePlateId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) - .EndRpc(); - - pc.SetLevel(level); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetLevel) - .Write(level) - .EndRpc(); - - sender.SendMessage(); - - DoppelPresentSkin[pc.PlayerId] = newOutfit; - } - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null || Camouflage.IsCamouflage || Camouflager.AbilityActivated || Utils.IsActive(SystemTypes.MushroomMixupSabotage)) return true; @@ -135,13 +78,13 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t DoppelVictim[target.PlayerId] = tname; - RpcChangeSkin(target, killerSkin, killerLvl); + target.SetNewOutfit(killerSkin, newLevel: killerLvl); + DoppelPresentSkin[target.PlayerId] = killerSkin; Logger.Info("Changed target skin", "Doppelganger"); - RpcChangeSkin(killer, targetSkin, targetLvl); - Logger.Info("Changed killer skin", "Doppelganger"); - //killer.Data.UpdateName(tname, Utils.GetClientById(killer.PlayerId)); - //target.Data.UpdateName(kname, Utils.GetClientById(target.PlayerId)); + killer.SetNewOutfit(targetSkin, newLevel: targetLvl); + DoppelPresentSkin[killer.PlayerId] = targetSkin; + Logger.Info("Changed killer skin", "Doppelganger"); SendSkillRPC(); RPC.SyncAllPlayerNames(); From 867dd54730ed0dc222fa25a75926a6ae13e5f824 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:11:04 +0800 Subject: [PATCH 188/778] Write => WritePacked (Fix bug) --- Modules/OutfitManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index 94843d86f0..998cfd5ef5 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -137,7 +137,7 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P { player.SetLevel(newLevel); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetLevel) - .Write(newLevel) + .WritePacked(newLevel) .EndRpc(); } From a407236050f2ba5ab384666dad2a7a8edcba88b4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:17:41 +0800 Subject: [PATCH 189/778] Fix bug? --- Roles/Neutral/Amnesiac.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 6086807cbd..bcc42e9f9a 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -117,11 +117,12 @@ public static void ReceiveRPC(MessageReader reader) } private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { - if (inMeeting) return; + if (inMeeting || Main.MeetingIsStarted) return; foreach (var pc in playerIdList.ToArray()) { var player = Utils.GetPlayerById(pc); - if (player == null || !player.IsAlive()) continue; + if (!player.IsAlive()) continue; + LocateArrow.Add(pc, target.transform.position); SendRPC(pc, true, target.transform.position); } From dbb3471c37ccc6b25f34f9af39efbafa5f50ec90 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:21:05 +0800 Subject: [PATCH 190/778] Fix PlrColorQuestion --- Roles/Neutral/Quizmaster.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 7388d5963a..2b27670a0b 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -486,7 +486,9 @@ public override void FixUnsetAnswers() _ => "None" }; + HasQuestionTranslation = false; HasAnswersTranslation = false; + ShowInvalid = false; if (PossibleAnswers.Contains(Answer)) PossibleAnswers.Remove(Answer); From 5d45aa53739a5abc9b14ee6ffa08d03ed66719de Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:29:33 +0800 Subject: [PATCH 191/778] Fix Killing Machine --- Modules/CustomRolesHelper.cs | 6 ++++-- Roles/Impostor/KillingMachine.cs | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 70ffe5126d..88aef9fb55 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -598,7 +598,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if (pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Lighter) || pc.Is(CustomRoles.Tired) - || pc.Is(CustomRoles.GuardianAngelTOHE)) + || pc.Is(CustomRoles.GuardianAngelTOHE) + || pc.Is(CustomRoles.KillingMachine)) return false; if (!pc.GetCustomRole().IsCrewmate()) return false; @@ -714,7 +715,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.Tired) || pc.Is(CustomRoles.GuardianAngelTOHE) - || pc.Is(CustomRoles.PunchingBag)) + || pc.Is(CustomRoles.PunchingBag) + || pc.Is(CustomRoles.KillingMachine)) return false; if ((pc.GetCustomRole().IsCrewmate() && !Bewilder.CrewCanBeBewilder.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Bewilder.NeutralCanBeBewilder.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Bewilder.ImpCanBeBewilder.GetBool())) return false; diff --git a/Roles/Impostor/KillingMachine.cs b/Roles/Impostor/KillingMachine.cs index a35e0dbd47..a0aab068fa 100644 --- a/Roles/Impostor/KillingMachine.cs +++ b/Roles/Impostor/KillingMachine.cs @@ -45,6 +45,12 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0.2f); } + public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) + => !reporter.Is(CustomRoles.KillingMachine); + + public override bool OnCheckStartMeeting(PlayerControl reporter) + => !reporter.Is(CustomRoles.KillingMachine); + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { killer.RpcMurderPlayer(target); From 0ebf408e6ff351f452ac484b7db8241d901d155e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:35:50 +0800 Subject: [PATCH 192/778] Remove FixedUpdate for Vector --- Roles/Neutral/Vector.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Roles/Neutral/Vector.cs b/Roles/Neutral/Vector.cs index 32c43469f0..a1f51a5c16 100644 --- a/Roles/Neutral/Vector.cs +++ b/Roles/Neutral/Vector.cs @@ -80,18 +80,18 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) } } } - public override void OnFixedUpdateLowLoad(PlayerControl player) - { - if (VectorVentCount[player.PlayerId] >= VectorVentNumWin.GetInt()) - { - VectorVentCount[player.PlayerId] = VectorVentNumWin.GetInt(); - if (!CustomWinnerHolder.CheckForConvertedWinner(player.PlayerId)) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Vector); - CustomWinnerHolder.WinnerIds.Add(player.PlayerId); - } - } - } + //public override void OnFixedUpdateLowLoad(PlayerControl player) + //{ + // if (VectorVentCount[player.PlayerId] >= VectorVentNumWin.GetInt()) + // { + // VectorVentCount[player.PlayerId] = VectorVentNumWin.GetInt(); + // if (!CustomWinnerHolder.CheckForConvertedWinner(player.PlayerId)) + // { + // CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Vector); + // CustomWinnerHolder.WinnerIds.Add(player.PlayerId); + // } + // } + //} public override Sprite GetAbilityButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Happy"); public override void SetAbilityButtonText(HudManager hud, byte playerId) { From 6394c5db3dcbe41535729dd72c68b4e3fa184676 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 13:46:39 +0800 Subject: [PATCH 193/778] Fix? --- Patches/ChatCommandPatch.cs | 2 +- Patches/CheckGameEndPatch.cs | 4 ++-- Roles/Neutral/Provocateur.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 6b63fb1086..68a4d00b84 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -3069,7 +3069,7 @@ public static void Postfix(ChatController __instance) if (player == null) return; (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; - Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); + //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); if (sendTo != byte.MaxValue && GameStates.IsLobby) { diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index ec983358e7..6d8a77c4a8 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -301,8 +301,8 @@ public static bool Prefix() WinnerIds.Add(pc.PlayerId); AdditionalWinnerTeams.Add(AdditionalWinners.Specter); break; - case CustomRoles.Provocateur when Provocateur.Provoked.TryGetValue(pc.PlayerId, out var tar): - if (!WinnerIds.Contains(tar)) + case CustomRoles.Provocateur: + if (Provocateur.Provoked.TryGetValue(pc.PlayerId, out var tarId) && !WinnerIds.Contains(tarId)) { WinnerIds.Add(pc.PlayerId); AdditionalWinnerTeams.Add(AdditionalWinners.Provocateur); diff --git a/Roles/Neutral/Provocateur.cs b/Roles/Neutral/Provocateur.cs index ff47bfd966..8e11cf233e 100644 --- a/Roles/Neutral/Provocateur.cs +++ b/Roles/Neutral/Provocateur.cs @@ -48,7 +48,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.RpcMurderPlayer(target); killer.RpcMurderPlayer(killer); killer.SetRealKiller(target); - Provoked.TryAdd(killer.PlayerId, target.PlayerId); + Provoked.Add(killer.PlayerId, target.PlayerId); return false; } public override void SetAbilityButtonText(HudManager hud, byte playerId) From 9c8c3f91eea48a8bfd5f7bd7356abb3d1e1e39ea Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 14:09:21 +0800 Subject: [PATCH 194/778] Fix build error --- Roles/Impostor/DollMaster.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Impostor/DollMaster.cs b/Roles/Impostor/DollMaster.cs index aba9517f63..9cc9715011 100644 --- a/Roles/Impostor/DollMaster.cs +++ b/Roles/Impostor/DollMaster.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using TOHE.Modules; using TOHE.Roles.Core; using TOHE.Roles.Neutral; using UnityEngine; From 6874234e7c15d74272ee92c1456765c5b87bfb02 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 10 Aug 2024 09:03:07 +0200 Subject: [PATCH 195/778] conflicts ans stuff --- Modules/CustomRolesHelper.cs | 12 +++++------- Modules/Utils.cs | 2 +- Patches/HudPatch.cs | 2 +- Roles/AddOns/Common/Spurt.cs | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 9f0500c8ab..ff90b28413 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -330,6 +330,8 @@ public static bool IsSpeedRole(this CustomRoles role) { return role is CustomRoles.Flash or + CustomRoles.Spurt or + CustomRoles.Statue or CustomRoles.Alchemist or CustomRoles.Tired; } @@ -392,6 +394,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Checking for conflicts with roles and other add-ons switch (role) { + case var Addon when (pc.IsAnySubRole(x => x.IsSpeedRole()) || pc.GetCustomRole().IsSpeedRole()) && Addon.IsSpeedRole(): + return false; + case CustomRoles.Autopsy: if (pc.Is(CustomRoles.Doctor) || pc.Is(CustomRoles.Tracefinder) @@ -1003,13 +1008,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Mare)) return false; break; - - case CustomRoles.Statue: - if (pc.Is(CustomRoles.Alchemist) - || pc.Is(CustomRoles.Flash) - || pc.Is(CustomRoles.Tired)) - return false; - break; } return true; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 6aef2d2d1b..b9a21a997a 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1865,7 +1865,7 @@ static int GetInfoSize(string RoleInfo) SelfSuffix.Append(seerRoleClass?.GetSuffix(seer, seer, isForMeeting: isForMeeting)); SelfSuffix.Append(CustomRoleManager.GetSuffixOthers(seer, seer, isForMeeting: isForMeeting)); - SelfSuffix.Append(Spurt.GetSuffix(seer)); + SelfSuffix.Append(Spurt.GetSuffix(seer, isformeeting: isForMeeting)); switch (Options.CurrentGameMode) diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 9a5e521e91..62b85d144c 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -97,7 +97,7 @@ public static void Postfix(HudManager __instance) var roleClass = player.GetRoleClass(); LowerInfoText.text = roleClass?.GetLowerText(player, player, isForMeeting: Main.MeetingIsStarted, isForHud: true) ?? string.Empty; - LowerInfoText.text += "\n" + Spurt.GetSuffix(player, true); + LowerInfoText.text += "\n" + Spurt.GetSuffix(player, true, isformeeting: Main.MeetingIsStarted); break; } diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 170125ffc5..f3c7ef027f 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -71,9 +71,9 @@ private static int DetermineCharge(PlayerControl player) return (int)((Main.AllPlayerSpeed[player.PlayerId] - minSpeed) / (maxSpeed - minSpeed) * 100); } - public static string GetSuffix(PlayerControl player, bool isforhud = false) + public static string GetSuffix(PlayerControl player, bool isforhud = false, bool isformeeting = false) { - if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting) + if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting || isformeeting) return string.Empty; int fontsize = isforhud ? 100 : 55; From dfe31fecbb5deb283f6dc71d37d2e2c33b5d67b3 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 10 Aug 2024 09:04:42 +0200 Subject: [PATCH 196/778] fix --- Modules/CustomRolesHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index ff90b28413..540053b781 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -375,7 +375,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Only add-ons if (!role.IsAdditionRole()) return false; - if(Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && !pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()))) + if(Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()))) return false; // if player already has this addon From a2d7c32329022286b454a88dc8435662e2252816 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 18:18:01 +0800 Subject: [PATCH 197/778] Fix bug --- Roles/Crewmate/Jailer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 50f7bb77e0..d5bdf7ef57 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -149,10 +149,15 @@ public override void OnReportDeadBody(PlayerControl sob, NetworkedPlayerInfo bak if (tpc == null) continue; if (NotifyJailedOnMeetingOpt.GetBool() && tpc.IsAlive()) + { _ = new LateTask(() => { - Utils.SendMessage(GetString("JailedNotifyMsg"), targetId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); - }, 0.3f, "Jailer Notify Jailed"); + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("JailedNotifyMsg"), targetId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); + } + }, 5f, $"Jailer Notify Jailed - id:{targetId}"); + } } } From c1dcb051aa22c9b2587e0a50c2b7bf20a1f93927 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 18:20:32 +0800 Subject: [PATCH 198/778] Add /apocalypseinfo in title settings --- Resources/Lang/en_US.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d211ccdeab..ea7bffaa5c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3454,19 +3454,19 @@ "RoleType.ImpSupport": "★ Impostor Support Roles", "RoleType.ImpConcealing": "★ Impostor Concealing Roles", "RoleType.ImpHindering": "★ Impostor Hindering Roles", - "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", + "RoleType.ImpGhost": "★ Impostor Ghost Roles /ghostinfo", "RoleType.Madmate": "★ Madmate Roles", "RoleType.CrewSupport": "★ Crewmate Support Roles", "RoleType.CrewInvestigative": "★ Crewmate Investigative Roles", "RoleType.CrewPower": "★ Crewmate Power Roles", "RoleType.CrewKilling": "★ Crewmate Killing Roles", "RoleType.CrewBasic": "★ Crewmate Basic Roles", - "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", + "RoleType.CrewGhost": "★ Crewmate Ghost Roles /ghostinfo", "RoleType.NeutralEvil": "★ Neutral Evil Roles", "RoleType.NeutralBenign": "★ Neutral Benign Roles", "RoleType.NeutralChaos": "★ Neutral Chaos Roles", "RoleType.NeutralKilling": "★ Neutral Killing Roles", - "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles", + "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles /apocalypseinfo", "RoleType.Harmful": "★ Harmful Add-ons", "RoleType.Support": "★ Supportive Add-ons", "RoleType.Helpful": "★ Helpful Add-ons", From 761d1c57a102d6d872814e6b0824778cf9ea556d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 10 Aug 2024 18:26:04 +0800 Subject: [PATCH 199/778] Main.CheckShapeshift is false if role use Unshift --- Patches/IntroPatch.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 54be7108d6..5ec5795735 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -565,15 +565,16 @@ public static void Postfix() { _ = new LateTask(() => { - Main.UnShapeShifter.Do(x => - { + Main.UnShapeShifter.Do(x => + { var PC = Utils.GetPlayerById(x); - var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); - PC.RpcShapeshift(randomplayer, false); - PC.RpcRejectShapeshift(); + var firstPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); + PC.RpcShapeshift(firstPlayer, false); + PC.RpcRejectShapeshift(); PC.ResetPlayerOutfit(force: true); - Main.GameIsLoaded = true; - }); + Main.CheckShapeshift[x] = false; + }); + Main.GameIsLoaded = true; }, 3f, "Set UnShapeShift Button"); } From acc92068f8952f2aa42c49a84884cc8304915ee5 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sat, 10 Aug 2024 06:09:14 -0500 Subject: [PATCH 200/778] use writer.WritePacked --- Roles/Impostor/DoubleAgent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 1ccc308626..533b1d5a00 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -291,7 +291,7 @@ private void SendRPC() { var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.None, -1); writer.WriteNetObject(_Player); - writer.Write((int)CurrentBombedTime); + writer.WritePacked((int)CurrentBombedTime); AmongUsClient.Instance.FinishRpcImmediately(writer); } @@ -355,4 +355,4 @@ private static void DestroyButtons(GameObject pressedButton) } } -// FieryFlower was here ඞ \ No newline at end of file +// FieryFlower was here ඞ From 5551efec9df88337cfbb89a4bbbcc7ad0c691a4b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 12:23:20 +0800 Subject: [PATCH 201/778] Invisible roles cannot get Rainbow --- Modules/CustomRolesHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 8ca32468d0..24820de982 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -996,7 +996,11 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Rainbow: if (pc.Is(CustomRoles.Doppelganger) - || pc.Is(CustomRoles.DollMaster)) + || pc.Is(CustomRoles.DollMaster) + || pc.Is(CustomRoles.Chameleon) + || pc.Is(CustomRoles.Swooper) + || pc.Is(CustomRoles.Alchemist) + || pc.Is(CustomRoles.Wraith)) return false; break; From 0d49873d2511fddbdacb0ca70d6d96cb6a798c07 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 14:22:15 +0800 Subject: [PATCH 202/778] First commit --- Modules/DoorsReset.cs | 6 +- Modules/OptionHolder.cs | 2 +- Resources/roleColor.json | 1 + Roles/Crewmate/Alchemist.cs | 3 + Roles/Crewmate/Mechanic.cs | 16 +-- Roles/Neutral/Troller.cs | 205 ++++++++++++++++++++++++++++++++++++ main.cs | 1 + 7 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 Roles/Neutral/Troller.cs diff --git a/Modules/DoorsReset.cs b/Modules/DoorsReset.cs index 5d2c1de48c..2d292b77d5 100644 --- a/Modules/DoorsReset.cs +++ b/Modules/DoorsReset.cs @@ -43,7 +43,7 @@ public static void ResetDoors() } } /// Open all doors on the map - private static void OpenAllDoors() + public static void OpenAllDoors() { foreach (var door in ShipStatus.Instance.AllDoors) { @@ -52,7 +52,7 @@ private static void OpenAllDoors() DoorsSystem.IsDirty = true; } /// Close all doors on the map - private static void CloseAllDoors() + public static void CloseAllDoors() { foreach (var door in ShipStatus.Instance.AllDoors) { @@ -61,7 +61,7 @@ private static void CloseAllDoors() DoorsSystem.IsDirty = true; } /// Randomly opens and closes all doors on the map - private static void OpenOrCloseAllDoorsRandomly() + public static void OpenOrCloseAllDoorsRandomly() { foreach (var door in ShipStatus.Instance.AllDoors) { diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 6c187d11aa..efa2e8071d 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -619,7 +619,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 28500 last id for roles/add-ons (Next use 28600) + // 28700 last id for roles/add-ons (Next use 28800) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 576542cea5..da8455f729 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -134,6 +134,7 @@ "Pursuer": "#617218", "Specter": "#662962", "Jinx": "#ed2f91", + "Troller": "#006994", "Maverick": "#781717", "CursedSoul": "#531269", "PotionMaster": "#663399", diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index 09679a3d41..44705a628d 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -509,6 +509,9 @@ public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, } public override void SwitchSystemUpdate(SwitchSystem __instance, byte amount, PlayerControl player) { + if (!FixNextSabo) return; + FixNextSabo = false; + __instance.ActualSwitches = 0; __instance.ExpectedSwitches = 0; diff --git a/Roles/Crewmate/Mechanic.cs b/Roles/Crewmate/Mechanic.cs index aadaada397..31cba853fe 100644 --- a/Roles/Crewmate/Mechanic.cs +++ b/Roles/Crewmate/Mechanic.cs @@ -61,8 +61,8 @@ public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, if (SkillLimit.GetFloat() > 0 && AbilityLimit + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 <= 0) break; if (amount is 64 or 65) { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 16); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 17); + __instance.RpcUpdateSystem(SystemTypes.Reactor, 16); + __instance.RpcUpdateSystem(SystemTypes.Reactor, 17); AbilityLimit -= UsesUsedWhenFixingReactorOrO2.GetFloat(); SendSkillRPC(); } @@ -72,8 +72,8 @@ public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, if (SkillLimit.GetFloat() > 0 && AbilityLimit + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 <= 0) break; if (amount is 64 or 65) { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 67); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 66); + __instance.RpcUpdateSystem(SystemTypes.Laboratory, 67); + __instance.RpcUpdateSystem(SystemTypes.Laboratory, 66); AbilityLimit -= UsesUsedWhenFixingReactorOrO2.GetFloat(); SendSkillRPC(); } @@ -83,8 +83,8 @@ public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, if (SkillLimit.GetFloat() > 0 && AbilityLimit + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 <= 0) break; if (amount is 64 or 65) { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66); + __instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67); + __instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66); AbilityLimit -= UsesUsedWhenFixingReactorOrO2.GetFloat(); SendSkillRPC(); } @@ -94,8 +94,8 @@ public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, if (SkillLimit.GetFloat() > 0 && AbilityLimit + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 <= 0) break; if (amount is 64 or 65) { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 16); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 17); + __instance.RpcUpdateSystem(SystemTypes.Comms, 16); + __instance.RpcUpdateSystem(SystemTypes.Comms, 17); AbilityLimit -= UsesUsedWhenFixingLightsOrComms.GetFloat(); SendSkillRPC(); } diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs new file mode 100644 index 0000000000..899c7b5a43 --- /dev/null +++ b/Roles/Neutral/Troller.cs @@ -0,0 +1,205 @@ +using TOHE.Modules; +using TOHE.Roles.Core; +using static TOHE.Options; +using static TOHE.Translator; + +namespace TOHE.Roles.Neutral; + +internal class Troller : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 28700; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Troller); + public override CustomRoles ThisRoleBase => CustomRoles.Engineer; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; + //==================================================================\\ + + private static OptionItem TrollsPerRound; + + private SystemTypes CurrantActiveSabotage = SystemTypes.Hallway; + private static readonly HashSet AllSabotages = + [ + SystemTypes.Reactor, + SystemTypes.Laboratory, + SystemTypes.HeliSabotage, + SystemTypes.LifeSupp, + SystemTypes.Comms, + SystemTypes.Electrical, + SystemTypes.MushroomMixupSabotage, + ]; + + enum RandomEvent + { + LowSpeed, + HighSpeed, + SabotageActivated, + SabotageDisabled, + AllDoorsOpen, + AllDoorsClose, + SetDoorsRandomly, + CooldownsResetToDefault, + CooldownsResetToZero, + LoseAddon, + GetBadAddon, + TelepostEveryoneToVents, + CallMeeting, + } + + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Troller); + TrollsPerRound = IntegerOptionItem.Create(Id + 10, "Troller_TrollsPerRound", new(1, 10, 1), 1, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Troller]); + OverrideTasksData.Create(Id + 15, TabGroup.NeutralRoles, CustomRoles.Troller); + } + public override void Add(byte playerId) + { + AbilityLimit = TrollsPerRound.GetInt(); + } + public override void Remove(byte playerId) + { + AbilityLimit = 0; + } + public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + if (!player.IsAlive() || AbilityLimit <= 0) return true; + + AbilityLimit--; + var allEvents = EnumHelper.GetAllValues().ToList(); + + if (Utils.AnySabotageIsActive()) + { + allEvents.Remove(RandomEvent.SabotageActivated); + } + else + { + allEvents.Remove(RandomEvent.SabotageDisabled); + } + + var randomEvent = allEvents.RandomElement(); + + switch (randomEvent) + { + case RandomEvent.LowSpeed: + case RandomEvent.HighSpeed: + var newSpeed = randomEvent is RandomEvent.LowSpeed ? 0.3f : 1.8f; + var tempSpeed = Main.AllPlayerSpeed.ToDictionary(k => k.Key, v => v.Value); + + foreach (var pcSpeed in Main.AllAlivePlayerControls) + { + Main.AllPlayerSpeed[pcSpeed.PlayerId] = newSpeed; + pcSpeed.Notify(GetString("TrollerChangesSpeed")); + } + Utils.MarkEveryoneDirtySettings(); + + _ = new LateTask(() => + { + foreach (var pcSpeed in Main.AllAlivePlayerControls) + { + Main.AllPlayerSpeed[pcSpeed.PlayerId] = tempSpeed[pcSpeed.PlayerId]; + pcSpeed.Notify(GetString("TrollerSpeedOut")); + } + Utils.MarkEveryoneDirtySettings(); + }, 10f, "Alchemist: Set Speed to default"); + break; + case RandomEvent.SabotageActivated: + + break; + case RandomEvent.SabotageDisabled: + var shipStatus = ShipStatus.Instance; + switch (CurrantActiveSabotage) + { + case SystemTypes.Reactor: + shipStatus.RpcUpdateSystem(SystemTypes.Reactor, 16); + shipStatus.RpcUpdateSystem(SystemTypes.Reactor, 17); + break; + case SystemTypes.Laboratory: + shipStatus.RpcUpdateSystem(SystemTypes.Laboratory, 67); + shipStatus.RpcUpdateSystem(SystemTypes.Laboratory, 66); + break; + case SystemTypes.LifeSupp: + shipStatus.RpcUpdateSystem(SystemTypes.LifeSupp, 67); + shipStatus.RpcUpdateSystem(SystemTypes.LifeSupp, 66); + break; + case SystemTypes.Comms: + shipStatus.RpcUpdateSystem(SystemTypes.Comms, 16); + shipStatus.RpcUpdateSystem(SystemTypes.Comms, 17); + break; + } + break; + case RandomEvent.AllDoorsOpen: + try + { + DoorsReset.OpenAllDoors(); + } + catch + { + } + break; + case RandomEvent.AllDoorsClose: + try + { + DoorsReset.CloseAllDoors(); + } + catch + { + } + break; + case RandomEvent.SetDoorsRandomly: + try + { + DoorsReset.OpenOrCloseAllDoorsRandomly(); + } + catch + { + } + break; + case RandomEvent.CooldownsResetToDefault: + + break; + case RandomEvent.CooldownsResetToZero: + + break; + case RandomEvent.LoseAddon: + + break; + case RandomEvent.GetBadAddon: + + break; + case RandomEvent.TelepostEveryoneToVents: + foreach (var pcTeleport in Main.AllAlivePlayerControls) + { + pcTeleport.RpcRandomVentTeleport(); + } + break; + case RandomEvent.CallMeeting: + var pcCallMeeting = Main.AllAlivePlayerControls.RandomElement(); + pcCallMeeting.NoCheckStartMeeting(null); + break; + } + + return true; + } + public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, byte amount, PlayerControl player) + { + if (!Main.MeetingIsStarted + && systemType is + SystemTypes.HeliSabotage or + SystemTypes.Laboratory or + SystemTypes.Reactor or + SystemTypes.Electrical or + SystemTypes.LifeSupp or + SystemTypes.Comms or + SystemTypes.MushroomMixupSabotage) + { + CurrantActiveSabotage = systemType; + } + } + public override void SwitchSystemUpdate(SwitchSystem __instance, byte amount, PlayerControl player) + { + if (!Main.MeetingIsStarted) + { + CurrantActiveSabotage = SystemTypes.Electrical; + } + } +} diff --git a/main.cs b/main.cs index 12d63ab004..06aa2b5e21 100644 --- a/main.cs +++ b/main.cs @@ -800,6 +800,7 @@ public enum CustomRoles Taskinator, Terrorist, Traitor, + Troller, Vector, VengefulRomantic, Virus, From 6935b4a51ca52ca50e576ab671ec52cc81c3b35f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 15:11:43 +0800 Subject: [PATCH 203/778] Remove RandomEvent.CallMeeting --- Roles/Neutral/Troller.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 899c7b5a43..e74155ae71 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -42,7 +42,7 @@ enum RandomEvent LoseAddon, GetBadAddon, TelepostEveryoneToVents, - CallMeeting, + //CallMeeting, } public override void SetupCustomOption() @@ -155,10 +155,22 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } break; case RandomEvent.CooldownsResetToDefault: - + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.HasImpKillButton() && pc.CanUseKillButton()) + { + pc.SetKillCooldown(); + } + } break; case RandomEvent.CooldownsResetToZero: - + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.HasImpKillButton() && pc.CanUseKillButton()) + { + pc.SetKillCooldown(0.3f); + } + } break; case RandomEvent.LoseAddon: @@ -172,10 +184,10 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount pcTeleport.RpcRandomVentTeleport(); } break; - case RandomEvent.CallMeeting: - var pcCallMeeting = Main.AllAlivePlayerControls.RandomElement(); - pcCallMeeting.NoCheckStartMeeting(null); - break; + //case RandomEvent.CallMeeting: + // var pcCallMeeting = Main.AllAlivePlayerControls.RandomElement(); + // pcCallMeeting.NoCheckStartMeeting(null); + // break; } return true; From 18789ca008b60e64146ad549959f5b11c6d3e483 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 11 Aug 2024 09:54:14 +0200 Subject: [PATCH 204/778] fix null error --- Modules/OptionHolder.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 6c187d11aa..424653d62e 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -944,6 +944,9 @@ private static System.Collections.IEnumerator CoLoadOptions() if (addonType.Key == AddonTypes.Impostor) Madmate.SetupCustomMenuOptions(); + if (addonType.Key == AddonTypes.Misc) + SetupLoversRoleOptionsToggle(23600); // KYS + foreach (var addon in addonType.Value) { From 32f9f342acf1cf6f0943eee88ebaf9b730302207 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 16:12:04 +0800 Subject: [PATCH 205/778] Fix some bugs + some changes --- Modules/ExtendedPlayerControl.cs | 3 +- Roles/Crewmate/Investigator.cs | 2 +- Roles/Impostor/Mastermind.cs | 2 +- Roles/Neutral/Troller.cs | 55 ++++++++++++++++++++------------ 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 22cbf32aa5..b259073ec5 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -645,7 +645,7 @@ public static bool CanUseKillButton(this PlayerControl pc) return false; } - public static bool HasKillButton(PlayerControl pc = null) + public static bool HasKillButton(this PlayerControl pc) { if (pc == null) return false; if (!pc.IsAlive() || pc.Data.Role.Role == RoleTypes.GuardianAngel || Pelican.IsEaten(pc.PlayerId)) return false; @@ -659,6 +659,7 @@ public static bool HasKillButton(PlayerControl pc = null) { CustomRoles.Impostor => true, CustomRoles.Shapeshifter => true, + CustomRoles.Phantom => true, _ => false }; } diff --git a/Roles/Crewmate/Investigator.cs b/Roles/Crewmate/Investigator.cs index ad1afa336e..e484519573 100644 --- a/Roles/Crewmate/Investigator.cs +++ b/Roles/Crewmate/Investigator.cs @@ -146,7 +146,7 @@ public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl t if (!InvestigatedList.TryGetValue(seer.PlayerId, out var targetList)) return string.Empty; if (!targetList.Contains(target.PlayerId)) return string.Empty; - if (ExtendedPlayerControl.HasKillButton(target) || CopyCat.playerIdList.Contains(target.PlayerId)) return "#FF1919"; + if (target.HasKillButton() || CopyCat.playerIdList.Contains(target.PlayerId)) return "#FF1919"; else return "#8CFFFF"; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) diff --git a/Roles/Impostor/Mastermind.cs b/Roles/Impostor/Mastermind.cs index c3eccdafc7..e2a2675015 100644 --- a/Roles/Impostor/Mastermind.cs +++ b/Roles/Impostor/Mastermind.cs @@ -68,7 +68,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return killer.CheckDoubleTrigger(target, () => { killer.SetKillCooldown(time: ManipulateCD); - if (ExtendedPlayerControl.HasKillButton(target) || CopyCat.playerIdList.Contains(target.PlayerId) || Main.TasklessCrewmate.Contains(target.PlayerId)) + if (target.HasKillButton() || CopyCat.playerIdList.Contains(target.PlayerId) || Main.TasklessCrewmate.Contains(target.PlayerId)) { ManipulateDelays.TryAdd(target.PlayerId, GetTimeStamp()); NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index e74155ae71..2b2868a0b4 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -96,34 +96,47 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount { foreach (var pcSpeed in Main.AllAlivePlayerControls) { - Main.AllPlayerSpeed[pcSpeed.PlayerId] = tempSpeed[pcSpeed.PlayerId]; + Main.AllPlayerSpeed[pcSpeed.PlayerId] = Main.AllPlayerSpeed[player.PlayerId] - newSpeed + tempSpeed[pcSpeed.PlayerId]; pcSpeed.Notify(GetString("TrollerSpeedOut")); } Utils.MarkEveryoneDirtySettings(); }, 10f, "Alchemist: Set Speed to default"); break; case RandomEvent.SabotageActivated: - + var shipStatusActivated = ShipStatus.Instance; + switch (CurrantActiveSabotage) + { + case SystemTypes.Reactor: + case SystemTypes.Laboratory: + case SystemTypes.HeliSabotage: + case SystemTypes.LifeSupp: + case SystemTypes.Comms: + shipStatusActivated.RpcUpdateSystem(CurrantActiveSabotage, 128); + break; + } break; case RandomEvent.SabotageDisabled: - var shipStatus = ShipStatus.Instance; + var shipStatusDisabled = ShipStatus.Instance; switch (CurrantActiveSabotage) { case SystemTypes.Reactor: - shipStatus.RpcUpdateSystem(SystemTypes.Reactor, 16); - shipStatus.RpcUpdateSystem(SystemTypes.Reactor, 17); - break; case SystemTypes.Laboratory: - shipStatus.RpcUpdateSystem(SystemTypes.Laboratory, 67); - shipStatus.RpcUpdateSystem(SystemTypes.Laboratory, 66); + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 16); + break; + case SystemTypes.HeliSabotage: + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 16); + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 17); break; case SystemTypes.LifeSupp: - shipStatus.RpcUpdateSystem(SystemTypes.LifeSupp, 67); - shipStatus.RpcUpdateSystem(SystemTypes.LifeSupp, 66); + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 66); + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 67); break; case SystemTypes.Comms: - shipStatus.RpcUpdateSystem(SystemTypes.Comms, 16); - shipStatus.RpcUpdateSystem(SystemTypes.Comms, 17); + var mapId = Utils.GetActiveMapId(); + + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 16); + if (mapId is 1 or 5) // Mira HQ or The Fungle + shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 17); break; } break; @@ -157,7 +170,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount case RandomEvent.CooldownsResetToDefault: foreach (var pc in Main.AllAlivePlayerControls) { - if (pc.HasImpKillButton() && pc.CanUseKillButton()) + if (pc.HasKillButton() && pc.CanUseKillButton()) { pc.SetKillCooldown(); } @@ -166,7 +179,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount case RandomEvent.CooldownsResetToZero: foreach (var pc in Main.AllAlivePlayerControls) { - if (pc.HasImpKillButton() && pc.CanUseKillButton()) + if (pc.HasKillButton() && pc.CanUseKillButton()) { pc.SetKillCooldown(0.3f); } @@ -207,11 +220,11 @@ SystemTypes.Comms or CurrantActiveSabotage = systemType; } } - public override void SwitchSystemUpdate(SwitchSystem __instance, byte amount, PlayerControl player) - { - if (!Main.MeetingIsStarted) - { - CurrantActiveSabotage = SystemTypes.Electrical; - } - } + //public override void SwitchSystemUpdate(SwitchSystem __instance, byte amount, PlayerControl player) + //{ + // if (!Main.MeetingIsStarted) + // { + // CurrantActiveSabotage = SystemTypes.Electrical; + // } + //} } From a5e404d0672b2d467ad628d185499aa41d45a9e2 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 17:13:52 +0800 Subject: [PATCH 206/778] A lot of fixes & Some changes --- Patches/ControlPatch.cs | 11 ---- Roles/Neutral/Troller.cs | 139 +++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 76 deletions(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index f80cd54558..f445d0d18c 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -397,17 +397,6 @@ public static void Postfix(/*ControllerManager __instance*/) } } - /*if (Input.GetKeyDown(KeyCode.L)) - { - Logger.Info($"{Utils.IsActive(SystemTypes.Reactor)}", "Check SystemType.Reactor"); - Logger.Info($"{Utils.IsActive(SystemTypes.LifeSupp)}", "Check SystemTypes.LifeSupp"); - Logger.Info($"{Utils.IsActive(SystemTypes.Laboratory)}", "Check SystemTypes.Laboratory"); - Logger.Info($"{Utils.IsActive(SystemTypes.HeliSabotage)}", "Check SystemTypes.HeliSabotage"); - Logger.Info($"{Utils.IsActive(SystemTypes.Comms)}", "Check SystemTypes.Comms"); - Logger.Info($"{Utils.IsActive(SystemTypes.Electrical)}", "Check SystemTypes.Electrical"); - Logger.Info($"{Utils.IsActive(SystemTypes.MushroomMixupSabotage)}", "Check SystemTypes.MushroomMixupSabotage"); - }*/ - // Clear vent if (Input.GetKeyDown(KeyCode.N)) { diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 2b2868a0b4..b6725a9e62 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -17,18 +17,9 @@ internal class Troller : RoleBase private static OptionItem TrollsPerRound; private SystemTypes CurrantActiveSabotage = SystemTypes.Hallway; - private static readonly HashSet AllSabotages = - [ - SystemTypes.Reactor, - SystemTypes.Laboratory, - SystemTypes.HeliSabotage, - SystemTypes.LifeSupp, - SystemTypes.Comms, - SystemTypes.Electrical, - SystemTypes.MushroomMixupSabotage, - ]; - - enum RandomEvent + private List AllEvents = []; + + enum Events { LowSpeed, HighSpeed, @@ -42,7 +33,7 @@ enum RandomEvent LoseAddon, GetBadAddon, TelepostEveryoneToVents, - //CallMeeting, + /* CallMeeting, */ } public override void SetupCustomOption() @@ -52,9 +43,22 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Troller]); OverrideTasksData.Create(Id + 15, TabGroup.NeutralRoles, CustomRoles.Troller); } + public override void Init() + { + AllEvents = []; + } public override void Add(byte playerId) { AbilityLimit = TrollsPerRound.GetInt(); + + AllEvents = [.. EnumHelper.GetAllValues()]; + + if (Utils.GetActiveMapName() is not (MapNames.Airship or MapNames.Polus or MapNames.Fungle)) + { + AllEvents.Remove(Events.AllDoorsOpen); + AllEvents.Remove(Events.AllDoorsClose); + AllEvents.Remove(Events.SetDoorsRandomly); + } } public override void Remove(byte playerId) { @@ -63,26 +67,30 @@ public override void Remove(byte playerId) public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { if (!player.IsAlive() || AbilityLimit <= 0) return true; - + AbilityLimit--; - var allEvents = EnumHelper.GetAllValues().ToList(); - if (Utils.AnySabotageIsActive()) + if (Utils.IsActive(SystemTypes.MushroomMixupSabotage) || Utils.IsActive(SystemTypes.Electrical)) { - allEvents.Remove(RandomEvent.SabotageActivated); + AllEvents.Remove(Events.SabotageActivated); + AllEvents.Remove(Events.SabotageDisabled); + } + else if (Utils.AnySabotageIsActive()) + { + AllEvents.Remove(Events.SabotageActivated); } else { - allEvents.Remove(RandomEvent.SabotageDisabled); + AllEvents.Remove(Events.SabotageDisabled); } - var randomEvent = allEvents.RandomElement(); + var randomEvent = AllEvents.RandomElement(); switch (randomEvent) { - case RandomEvent.LowSpeed: - case RandomEvent.HighSpeed: - var newSpeed = randomEvent is RandomEvent.LowSpeed ? 0.3f : 1.8f; + case Events.LowSpeed: + case Events.HighSpeed: + var newSpeed = randomEvent is Events.LowSpeed ? 0.3f : 1.8f; var tempSpeed = Main.AllPlayerSpeed.ToDictionary(k => k.Key, v => v.Value); foreach (var pcSpeed in Main.AllAlivePlayerControls) @@ -102,20 +110,48 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount Utils.MarkEveryoneDirtySettings(); }, 10f, "Alchemist: Set Speed to default"); break; - case RandomEvent.SabotageActivated: + case Events.SabotageActivated: var shipStatusActivated = ShipStatus.Instance; - switch (CurrantActiveSabotage) + List allSabotage = []; + switch ((MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId) + { + case MapNames.Skeld: + case MapNames.Dleks: + case MapNames.Mira: + allSabotage.Add(SystemTypes.Reactor); + allSabotage.Add(SystemTypes.LifeSupp); + allSabotage.Add(SystemTypes.Comms); + break; + case MapNames.Polus: + allSabotage.Add(SystemTypes.Laboratory); + allSabotage.Add(SystemTypes.Comms); + break; + case MapNames.Airship: + allSabotage.Add(SystemTypes.HeliSabotage); + allSabotage.Add(SystemTypes.Comms); + break; + case MapNames.Fungle: + allSabotage.Add(SystemTypes.Reactor); + allSabotage.Add(SystemTypes.Comms); + allSabotage.Add(SystemTypes.MushroomMixupSabotage); + break; + } + var randomSabotage = allSabotage.RandomElement(); + switch (randomSabotage) { case SystemTypes.Reactor: case SystemTypes.Laboratory: case SystemTypes.HeliSabotage: case SystemTypes.LifeSupp: case SystemTypes.Comms: - shipStatusActivated.RpcUpdateSystem(CurrantActiveSabotage, 128); + shipStatusActivated.RpcUpdateSystem(randomSabotage, 128); + break; + case SystemTypes.MushroomMixupSabotage: + shipStatusActivated.RpcUpdateSystem(randomSabotage, 1); break; } break; - case RandomEvent.SabotageDisabled: + case Events.SabotageDisabled: var shipStatusDisabled = ShipStatus.Instance; switch (CurrantActiveSabotage) { @@ -140,34 +176,16 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount break; } break; - case RandomEvent.AllDoorsOpen: - try - { - DoorsReset.OpenAllDoors(); - } - catch - { - } + case Events.AllDoorsOpen: + DoorsReset.OpenAllDoors(); break; - case RandomEvent.AllDoorsClose: - try - { - DoorsReset.CloseAllDoors(); - } - catch - { - } + case Events.AllDoorsClose: + DoorsReset.CloseAllDoors(); break; - case RandomEvent.SetDoorsRandomly: - try - { - DoorsReset.OpenOrCloseAllDoorsRandomly(); - } - catch - { - } + case Events.SetDoorsRandomly: + DoorsReset.OpenOrCloseAllDoorsRandomly(); break; - case RandomEvent.CooldownsResetToDefault: + case Events.CooldownsResetToDefault: foreach (var pc in Main.AllAlivePlayerControls) { if (pc.HasKillButton() && pc.CanUseKillButton()) @@ -176,7 +194,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } } break; - case RandomEvent.CooldownsResetToZero: + case Events.CooldownsResetToZero: foreach (var pc in Main.AllAlivePlayerControls) { if (pc.HasKillButton() && pc.CanUseKillButton()) @@ -185,13 +203,13 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } } break; - case RandomEvent.LoseAddon: + case Events.LoseAddon: break; - case RandomEvent.GetBadAddon: + case Events.GetBadAddon: break; - case RandomEvent.TelepostEveryoneToVents: + case Events.TelepostEveryoneToVents: foreach (var pcTeleport in Main.AllAlivePlayerControls) { pcTeleport.RpcRandomVentTeleport(); @@ -212,19 +230,10 @@ public override void UpdateSystem(ShipStatus __instance, SystemTypes systemType, SystemTypes.HeliSabotage or SystemTypes.Laboratory or SystemTypes.Reactor or - SystemTypes.Electrical or SystemTypes.LifeSupp or - SystemTypes.Comms or - SystemTypes.MushroomMixupSabotage) + SystemTypes.Comms) { CurrantActiveSabotage = systemType; } } - //public override void SwitchSystemUpdate(SwitchSystem __instance, byte amount, PlayerControl player) - //{ - // if (!Main.MeetingIsStarted) - // { - // CurrantActiveSabotage = SystemTypes.Electrical; - // } - //} } From 0889eca1fcf13e26d55a83e784f8ebf1f5612c44 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sun, 11 Aug 2024 04:16:22 -0500 Subject: [PATCH 207/778] Better RegionMenu --- Patches/RegionMenuPatch.cs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Patches/RegionMenuPatch.cs b/Patches/RegionMenuPatch.cs index 4cf7a62369..d0ebbb5d5e 100644 --- a/Patches/RegionMenuPatch.cs +++ b/Patches/RegionMenuPatch.cs @@ -1,28 +1,31 @@ -using UnityEngine; +using UnityEngine; namespace TOHE.Patches; -// Credits : KARPED1EM from https://github.com/KARPED1EM/TownOfNext/blob/TONX/TONX/Patches/RegionMenuPatch.cs [HarmonyPatch(typeof(RegionMenu))] public static class RegionMenuPatch { - public static Scroller Scroller; - - [HarmonyPatch(nameof(RegionMenu.Awake)), HarmonyPostfix] - public static void Awake_Postfix(RegionMenu __instance) + [HarmonyPatch(nameof(RegionMenu.OnEnable))] + [HarmonyPostfix] + public static void AdjustButtonPositions_Postfix(RegionMenu __instance) { - if (Scroller != null) return; + const int buttonsPerColumn = 6; + float buttonSpacing = 0.6f; + float buttonSpacingSide = 2.25f; + + List buttons = __instance.controllerSelectable.ToArray().ToList(); + + int columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; + float totalWidth = (columnCount - 1) * buttonSpacingSide; + float totalHeight = (buttonsPerColumn - 1) * buttonSpacing; - var back = __instance.ButtonPool.transform.FindChild("Backdrop"); - back.transform.localScale *= 10f; + Vector3 startPosition = new Vector3(-totalWidth / 2, totalHeight / 2, 0f); - Scroller = __instance.ButtonPool.transform.parent.gameObject.AddComponent(); - Scroller.Inner = __instance.ButtonPool.transform; - Scroller.MouseMustBeOverToScroll = true; - Scroller.ClickMask = back.GetComponent(); - Scroller.ScrollWheelSpeed = 0.7f; - Scroller.SetYBoundsMin(0f); - Scroller.SetYBoundsMax(4f); - Scroller.allowY = true; + for (int i = 0; i < buttons.Count; i++) + { + int col = i / buttonsPerColumn; + int row = i % buttonsPerColumn; + buttons[i].transform.localPosition = startPosition + new Vector3(col * buttonSpacingSide, -row * buttonSpacing, 0f); + } } } From c0bed6c06abbe616de8b496c06330eb77ecfbe93 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sun, 11 Aug 2024 04:31:59 -0500 Subject: [PATCH 208/778] maxColumns --- Patches/RegionMenuPatch.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Patches/RegionMenuPatch.cs b/Patches/RegionMenuPatch.cs index d0ebbb5d5e..2eaae3af96 100644 --- a/Patches/RegionMenuPatch.cs +++ b/Patches/RegionMenuPatch.cs @@ -9,13 +9,21 @@ public static class RegionMenuPatch [HarmonyPostfix] public static void AdjustButtonPositions_Postfix(RegionMenu __instance) { - const int buttonsPerColumn = 6; + const int maxColumns = 4; + int buttonsPerColumn = 6; float buttonSpacing = 0.6f; float buttonSpacingSide = 2.25f; List buttons = __instance.controllerSelectable.ToArray().ToList(); int columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; + + while (columnCount > maxColumns) + { + buttonsPerColumn++; + columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; + } + float totalWidth = (columnCount - 1) * buttonSpacingSide; float totalHeight = (buttonsPerColumn - 1) * buttonSpacing; From 0346b7f7f873507a22ad11966e4fb741601603b1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 17:42:28 +0800 Subject: [PATCH 209/778] Add some Events --- Roles/Neutral/Troller.cs | 78 ++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index b6725a9e62..2edb048b7b 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -15,6 +15,7 @@ internal class Troller : RoleBase //==================================================================\\ private static OptionItem TrollsPerRound; + private static OptionItem CanHaveCallMeetingEvent; private SystemTypes CurrantActiveSabotage = SystemTypes.Hallway; private List AllEvents = []; @@ -31,9 +32,11 @@ enum Events CooldownsResetToDefault, CooldownsResetToZero, LoseAddon, - GetBadAddon, + /* GetBadAddon, */ TelepostEveryoneToVents, - /* CallMeeting, */ + PullEveryone, + TwistEveryone, + CallMeeting } public override void SetupCustomOption() @@ -41,11 +44,13 @@ public override void SetupCustomOption() SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Troller); TrollsPerRound = IntegerOptionItem.Create(Id + 10, "Troller_TrollsPerRound", new(1, 10, 1), 1, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Troller]); + CanHaveCallMeetingEvent = BooleanOptionItem.Create(Id + 11, "Troller_CanHaveCallMeetingEvent", false, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Troller]); OverrideTasksData.Create(Id + 15, TabGroup.NeutralRoles, CustomRoles.Troller); } public override void Init() { - AllEvents = []; + AllEvents.Clear(); } public override void Add(byte playerId) { @@ -59,14 +64,18 @@ public override void Add(byte playerId) AllEvents.Remove(Events.AllDoorsClose); AllEvents.Remove(Events.SetDoorsRandomly); } + if (!CanHaveCallMeetingEvent.GetBool()) + { + AllEvents.Remove(Events.CallMeeting); + } } public override void Remove(byte playerId) { AbilityLimit = 0; } - public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + public override bool OnTaskComplete(PlayerControl troller, int completedTaskCount, int totalTaskCount) { - if (!player.IsAlive() || AbilityLimit <= 0) return true; + if (!troller.IsAlive() || AbilityLimit <= 0) return true; AbilityLimit--; @@ -104,7 +113,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount { foreach (var pcSpeed in Main.AllAlivePlayerControls) { - Main.AllPlayerSpeed[pcSpeed.PlayerId] = Main.AllPlayerSpeed[player.PlayerId] - newSpeed + tempSpeed[pcSpeed.PlayerId]; + Main.AllPlayerSpeed[pcSpeed.PlayerId] = Main.AllPlayerSpeed[pcSpeed.PlayerId] - newSpeed + tempSpeed[pcSpeed.PlayerId]; pcSpeed.Notify(GetString("TrollerSpeedOut")); } Utils.MarkEveryoneDirtySettings(); @@ -204,10 +213,25 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } break; case Events.LoseAddon: - - break; - case Events.GetBadAddon: - + var randomPC = Main.AllAlivePlayerControls.RandomElement(); + var addons = Main.PlayerStates[randomPC.PlayerId].SubRoles.ToList(); + foreach (var role in addons) + { + if (role is CustomRoles.LastImpostor || + role is CustomRoles.Lovers || // Causes issues involving Lovers Suicide + role.IsBetrayalAddon()) + { + addons.Remove(role); + } + } + if (!addons.Any()) + { + Logger.Info("No addons found on the target", "Troller"); + break; + } + var addon = addons.RandomElement(); + Main.PlayerStates[randomPC.PlayerId].RemoveSubRole(addon); + randomPC.MarkDirtySettings(); break; case Events.TelepostEveryoneToVents: foreach (var pcTeleport in Main.AllAlivePlayerControls) @@ -215,10 +239,36 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount pcTeleport.RpcRandomVentTeleport(); } break; - //case RandomEvent.CallMeeting: - // var pcCallMeeting = Main.AllAlivePlayerControls.RandomElement(); - // pcCallMeeting.NoCheckStartMeeting(null); - // break; + case Events.PullEveryone: + ExtendedPlayerControl.RpcTeleportAllPlayers(troller.GetCustomPosition()); + break; + case Events.TwistEveryone: + List changePositionPlayers = []; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (changePositionPlayers.Contains(pc.PlayerId) || Pelican.IsEaten(pc.PlayerId) || pc.onLadder || pc.inVent || pc.inMovingPlat || GameStates.IsMeeting) continue; + + var filtered = Main.AllAlivePlayerControls.Where(a => !a.inVent && !Pelican.IsEaten(a.PlayerId) && !a.onLadder && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); + if (filtered.Length == 0) break; + + var target = filtered.RandomElement(); + + changePositionPlayers.Add(target.PlayerId); + changePositionPlayers.Add(pc.PlayerId); + + pc.RPCPlayCustomSound("Teleport"); + + var originPs = target.GetCustomPosition(); + target.RpcTeleport(pc.GetCustomPosition()); + pc.RpcTeleport(originPs); + } + break; + case Events.CallMeeting: + var pcCallMeeting = Main.AllAlivePlayerControls.RandomElement(); + pcCallMeeting.NoCheckStartMeeting(null); + break; + //case Events.GetBadAddon: + // break; } return true; From bd9ae3b60bbe858392460c61cb5ebdeab18e835b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 17:45:02 +0800 Subject: [PATCH 210/778] Some fix --- Roles/Impostor/Twister.cs | 2 +- Roles/Neutral/Troller.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Roles/Impostor/Twister.cs b/Roles/Impostor/Twister.cs index a9e976f9e5..b604f5c6e1 100644 --- a/Roles/Impostor/Twister.cs +++ b/Roles/Impostor/Twister.cs @@ -57,7 +57,7 @@ public override void OnShapeshift(PlayerControl shapeshifter, PlayerControl targ } var filtered = Main.AllAlivePlayerControls.Where(a => - pc.CanBeTeleported() && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToList(); + a.CanBeTeleported() && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToList(); if (filtered.Count == 0) return; diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 2edb048b7b..8bed5e540d 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -246,10 +246,10 @@ role is CustomRoles.Lovers || // Causes issues involving Lovers Suicide List changePositionPlayers = []; foreach (var pc in Main.AllAlivePlayerControls) { - if (changePositionPlayers.Contains(pc.PlayerId) || Pelican.IsEaten(pc.PlayerId) || pc.onLadder || pc.inVent || pc.inMovingPlat || GameStates.IsMeeting) continue; + if (changePositionPlayers.Contains(pc.PlayerId) || !pc.CanBeTeleported()) continue; - var filtered = Main.AllAlivePlayerControls.Where(a => !a.inVent && !Pelican.IsEaten(a.PlayerId) && !a.onLadder && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); - if (filtered.Length == 0) break; + var filtered = Main.AllAlivePlayerControls.Where(a => a.CanBeTeleported() && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); + if (!filtered.Any()) break; var target = filtered.RandomElement(); From b32b77c4fb9e2a8377e390ed8a5abb7b7491ed58 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 17:45:27 +0800 Subject: [PATCH 211/778] walkingToVent in CanBeTeleported --- Modules/ExtendedPlayerControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index b259073ec5..1181879343 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1091,6 +1091,7 @@ public static bool CanBeTeleported(this PlayerControl player) // Check target status || !player.IsAlive() || player.inVent + || player.walkingToVent || player.inMovingPlat // Moving Platform on Airhip and Zipline on Fungle || player.MyPhysics.Animations.IsPlayingEnterVentAnimation() || player.onLadder || player.MyPhysics.Animations.IsPlayingAnyLadderAnimation() From 5b1f38c31efd2d69d29f5dff86b1f7e7eddb65f3 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 18:31:08 +0800 Subject: [PATCH 212/778] Add strings --- Patches/CheckGameEndPatch.cs | 4 ++++ Resources/Lang/en_US.json | 18 ++++++++++++++++-- Roles/Neutral/Troller.cs | 30 ++++++++++++++++++++++-------- main.cs | 1 + 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 6d8a77c4a8..3850f2a4a6 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -319,6 +319,10 @@ public static bool Prefix() WinnerIds.Add(Hater); } break; + case CustomRoles.Troller when pc.IsAlive(): + AdditionalWinnerTeams.Add(AdditionalWinners.Hater); + WinnerIds.Add(pc.PlayerId); + break; case CustomRoles.Romantic: if (Romantic.BetPlayer.TryGetValue(pc.PlayerId, out var betTarget) && (WinnerIds.Contains(betTarget) || (Main.PlayerStates.TryGetValue(betTarget, out var betTargetPS) && WinnerRoles.Contains(betTargetPS.MainRole)))) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d9d8d447c0..2eaf3350ef 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -281,6 +281,7 @@ "CursedSoul": "Cursed Soul", "Pickpocket": "Pickpocket", "Traitor": "Traitor", + "Troller": "Troller", "Vulture": "Vulture", "Taskinator": "Taskinator", "Benefactor": "Benefactor", @@ -582,6 +583,7 @@ "CursedSoulInfo": "Snatch souls and steal the win", "PickpocketInfo": "Steal votes from your kills", "TraitorInfo": "Eliminate the Impostors, then win", + "TrollerInfo": "Make random event by complete task", "VultureInfo": "Eat bodies by reporting to win", "TaskinatorInfo": "Silent tasks, deadly blasts", "BenefactorInfo": "Task complete, shield elite!", @@ -880,6 +882,7 @@ "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", + "TrollerInfoLong": "(Neutrals):\nAs a Troller, you can complete tasks so that random events can happen to players.\nFor example, changing the speed of all players, teleportation, influencing sabotage, etc.\nAlso you can wins with the winner team.\n\nGood luck!", "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", @@ -1799,6 +1802,17 @@ "GuessPresident": "President has revealed themselves. You can't guess them.", "PresidentRevealTitle": "PRESIDENT REVEAL", + "Troller_TrollsPerRound": "Trolls Per Round", + "Troller_CanHaveStartMeetingEvent": "Can Start Meeting By Event", + "TrollerChangesSpeed": "Troller changed everyone speed!", + "TrollerSpeedOut": "Speed returned back", + "Troller_ChangeYourCooldown": "Troller change your cooldown!", + "Troller_NoAddons": "No addons found on the random target", + "Troller_RemoveRandomAddon": "You removed add-on from random player", + "Troller_RemoveYourAddon": "Troller removed your random add-on", + "Troller_YouCausedSabotage": "You caused sabotage", + "Troller_YouFixedSabotage": "You fixed sabotage", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", @@ -2038,7 +2052,7 @@ "MediumNotifyTarget": "{0}, the Medium, has established contact with you. Before the end of this meeting, you have a chance to respond to their question. Type one of the following commands to answer:\nConfirm: /ms yes\nDeny: /ms no", "MediumNotifySelf": "You established contact with {0}. Please ask them questions and wait for them to respond.\n\nRemaining ability uses: {1}", "MediumKnowPlayerDead": "Someone died somewhere", - + "SpurtMinSpeed": "Min Speed", "SpurtMaxSpeed": "Max Speed", "SpurtModule": "Speed Modulator", @@ -2379,7 +2393,6 @@ "Preset_4": "Preset 4", "Preset_5": "Preset 5", "Standard": "Standard", - "FFA": "Free For All", "HidenSeekTOHE": "Hide And Seek", "GameMode": "Game Mode", "PressTabToNextPage": "Press Tab or Number for Next Page...", @@ -3477,6 +3490,7 @@ "AdditionalWinnerRoleText.Romantic": "Romantic", "AdditionalWinnerRoleText.VengefulRomantic": "Vengeful Romantic", "AdditionalWinnerRoleText.SchrodingersCat": "Schrodingers Cat", + "AdditionalWinnerRoleText.Troller": "Troller", "ErrorEndText": "An error occurred", "ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.", "ForceEnd": "Aborted", diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 8bed5e540d..5c9c703e58 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -1,4 +1,5 @@ -using TOHE.Modules; +using AmongUs.GameOptions; +using TOHE.Modules; using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; @@ -36,15 +37,15 @@ enum Events TelepostEveryoneToVents, PullEveryone, TwistEveryone, - CallMeeting + StartMeeting } public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Troller); - TrollsPerRound = IntegerOptionItem.Create(Id + 10, "Troller_TrollsPerRound", new(1, 10, 1), 1, TabGroup.NeutralRoles, false) + TrollsPerRound = IntegerOptionItem.Create(Id + 10, "Troller_TrollsPerRound", new(1, 10, 1), 2, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Troller]); - CanHaveCallMeetingEvent = BooleanOptionItem.Create(Id + 11, "Troller_CanHaveCallMeetingEvent", false, TabGroup.NeutralRoles, false) + CanHaveCallMeetingEvent = BooleanOptionItem.Create(Id + 11, "Troller_CanHaveStartMeetingEvent", false, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Troller]); OverrideTasksData.Create(Id + 15, TabGroup.NeutralRoles, CustomRoles.Troller); } @@ -66,13 +67,18 @@ public override void Add(byte playerId) } if (!CanHaveCallMeetingEvent.GetBool()) { - AllEvents.Remove(Events.CallMeeting); + AllEvents.Remove(Events.StartMeeting); } } public override void Remove(byte playerId) { AbilityLimit = 0; } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 0f; + } public override bool OnTaskComplete(PlayerControl troller, int completedTaskCount, int totalTaskCount) { if (!troller.IsAlive() || AbilityLimit <= 0) return true; @@ -95,6 +101,8 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun var randomEvent = AllEvents.RandomElement(); + Logger.Info($"Random Event: {randomEvent}", "Troller"); + switch (randomEvent) { case Events.LowSpeed: @@ -117,7 +125,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun pcSpeed.Notify(GetString("TrollerSpeedOut")); } Utils.MarkEveryoneDirtySettings(); - }, 10f, "Alchemist: Set Speed to default"); + }, 10f, "Troller: Set Speed to default"); break; case Events.SabotageActivated: var shipStatusActivated = ShipStatus.Instance; @@ -159,6 +167,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun shipStatusActivated.RpcUpdateSystem(randomSabotage, 1); break; } + troller.Notify(GetString("Troller_YouCausedSabotage")); break; case Events.SabotageDisabled: var shipStatusDisabled = ShipStatus.Instance; @@ -184,6 +193,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 17); break; } + troller.Notify(GetString("Troller_YouFixedSabotage")); break; case Events.AllDoorsOpen: DoorsReset.OpenAllDoors(); @@ -200,6 +210,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun if (pc.HasKillButton() && pc.CanUseKillButton()) { pc.SetKillCooldown(); + pc.Notify(GetString("Troller_ChangeYourCooldown")); } } break; @@ -209,6 +220,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun if (pc.HasKillButton() && pc.CanUseKillButton()) { pc.SetKillCooldown(0.3f); + pc.Notify(GetString("Troller_ChangeYourCooldown")); } } break; @@ -226,11 +238,13 @@ role is CustomRoles.Lovers || // Causes issues involving Lovers Suicide } if (!addons.Any()) { - Logger.Info("No addons found on the target", "Troller"); + troller.Notify(GetString("Troller_NoAddons")); break; } var addon = addons.RandomElement(); Main.PlayerStates[randomPC.PlayerId].RemoveSubRole(addon); + troller.Notify(GetString("Troller_RemoveRandomAddon")); + randomPC.Notify(GetString("Troller_RemoveYourAddon")); randomPC.MarkDirtySettings(); break; case Events.TelepostEveryoneToVents: @@ -263,7 +277,7 @@ role is CustomRoles.Lovers || // Causes issues involving Lovers Suicide pc.RpcTeleport(originPs); } break; - case Events.CallMeeting: + case Events.StartMeeting: var pcCallMeeting = Main.AllAlivePlayerControls.RandomElement(); pcCallMeeting.NoCheckStartMeeting(null); break; diff --git a/main.cs b/main.cs index 06aa2b5e21..765d79c207 100644 --- a/main.cs +++ b/main.cs @@ -988,6 +988,7 @@ public enum AdditionalWinners Pixie = CustomRoles.Pixie, Quizmaster = CustomRoles.Quizmaster, SchrodingersCat = CustomRoles.SchrodingersCat, + Troller = CustomRoles.Troller, // NiceMini = CustomRoles.NiceMini, // Baker = CustomRoles.Baker, } From 686596eafd432941b339aab7ced863144343f6f9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 18:33:46 +0800 Subject: [PATCH 213/778] Add task --- Patches/CheckGameEndPatch.cs | 2 +- Resources/Lang/en_US.json | 3 ++- Roles/Neutral/Troller.cs | 19 +++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 3850f2a4a6..fda74789f1 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -320,7 +320,7 @@ public static bool Prefix() } break; case CustomRoles.Troller when pc.IsAlive(): - AdditionalWinnerTeams.Add(AdditionalWinners.Hater); + AdditionalWinnerTeams.Add(AdditionalWinners.Troller); WinnerIds.Add(pc.PlayerId); break; case CustomRoles.Romantic: diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 2eaf3350ef..c3b6764756 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -882,7 +882,7 @@ "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", - "TrollerInfoLong": "(Neutrals):\nAs a Troller, you can complete tasks so that random events can happen to players.\nFor example, changing the speed of all players, teleportation, influencing sabotage, etc.\nAlso you can wins with the winner team.\n\nGood luck!", + "TrollerInfoLong": "(Neutrals):\nAs a Troller, you can complete tasks so that random events can happen to players.\nFor example, changing the speed of all players, teleportation, influencing sabotage, etc.\nAlso you can wins with the winner team.", "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", @@ -1806,6 +1806,7 @@ "Troller_CanHaveStartMeetingEvent": "Can Start Meeting By Event", "TrollerChangesSpeed": "Troller changed everyone speed!", "TrollerSpeedOut": "Speed returned back", + "Troller_YouChangedCooldown": "You changed the cooldown of all players", "Troller_ChangeYourCooldown": "Troller change your cooldown!", "Troller_NoAddons": "No addons found on the random target", "Troller_RemoveRandomAddon": "You removed add-on from random player", diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 5c9c703e58..c47f13781a 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -74,6 +74,8 @@ public override void Remove(byte playerId) { AbilityLimit = 0; } + public override bool HasTasks(NetworkedPlayerInfo player, CustomRoles role, bool ForRecompute) => !ForRecompute; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) { AURoleOptions.EngineerCooldown = 1f; @@ -205,24 +207,21 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun DoorsReset.OpenOrCloseAllDoorsRandomly(); break; case Events.CooldownsResetToDefault: - foreach (var pc in Main.AllAlivePlayerControls) - { - if (pc.HasKillButton() && pc.CanUseKillButton()) - { - pc.SetKillCooldown(); - pc.Notify(GetString("Troller_ChangeYourCooldown")); - } - } - break; case Events.CooldownsResetToZero: + var setToDefault = randomEvent is Events.CooldownsResetToDefault; foreach (var pc in Main.AllAlivePlayerControls) { if (pc.HasKillButton() && pc.CanUseKillButton()) { - pc.SetKillCooldown(0.3f); + if (setToDefault) + pc.SetKillCooldown(); + else + pc.SetKillCooldown(0.3f); + pc.Notify(GetString("Troller_ChangeYourCooldown")); } } + troller.Notify(GetString("Troller_YouChangedCooldown")); break; case Events.LoseAddon: var randomPC = Main.AllAlivePlayerControls.RandomElement(); From 53969338d6645c12d72c9a005e2248f225b528a8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 18:56:22 +0800 Subject: [PATCH 214/778] Hmm --- Roles/Neutral/Troller.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index c47f13781a..875f8ee716 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -18,7 +18,7 @@ internal class Troller : RoleBase private static OptionItem TrollsPerRound; private static OptionItem CanHaveCallMeetingEvent; - private SystemTypes CurrantActiveSabotage = SystemTypes.Hallway; + private SystemTypes CurrentActiveSabotage = SystemTypes.Hallway; private List AllEvents = []; enum Events @@ -173,26 +173,26 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun break; case Events.SabotageDisabled: var shipStatusDisabled = ShipStatus.Instance; - switch (CurrantActiveSabotage) + switch (CurrentActiveSabotage) { case SystemTypes.Reactor: case SystemTypes.Laboratory: - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 16); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 16); break; case SystemTypes.HeliSabotage: - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 16); - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 17); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 16); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 17); break; case SystemTypes.LifeSupp: - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 66); - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 67); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 66); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 67); break; case SystemTypes.Comms: var mapId = Utils.GetActiveMapId(); - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 16); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 16); if (mapId is 1 or 5) // Mira HQ or The Fungle - shipStatusDisabled.RpcUpdateSystem(CurrantActiveSabotage, 17); + shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 17); break; } troller.Notify(GetString("Troller_YouFixedSabotage")); @@ -296,7 +296,7 @@ SystemTypes.Reactor or SystemTypes.LifeSupp or SystemTypes.Comms) { - CurrantActiveSabotage = systemType; + CurrentActiveSabotage = systemType; } } } From 64f6eb6ccb0c713a54891ef73b13f26766dbb001 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 20:18:19 +0800 Subject: [PATCH 215/778] Change HighSpeed --- Roles/Neutral/Troller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 875f8ee716..53c67480d6 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -109,7 +109,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun { case Events.LowSpeed: case Events.HighSpeed: - var newSpeed = randomEvent is Events.LowSpeed ? 0.3f : 1.8f; + var newSpeed = randomEvent is Events.LowSpeed ? 0.3f : 5f; var tempSpeed = Main.AllPlayerSpeed.ToDictionary(k => k.Key, v => v.Value); foreach (var pcSpeed in Main.AllAlivePlayerControls) From 94ba719329ff5f3ea8f44258ab10fc7403d369c0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 20:20:59 +0800 Subject: [PATCH 216/778] Change --- Roles/Neutral/Troller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 53c67480d6..42fe7dbe30 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -132,7 +132,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun case Events.SabotageActivated: var shipStatusActivated = ShipStatus.Instance; List allSabotage = []; - switch ((MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId) + switch ((MapNames)Utils.GetActiveMapId()) { case MapNames.Skeld: case MapNames.Dleks: From b4ad94ccb3a6a5af860015ca67e78f38cd6cb6a0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 11 Aug 2024 20:26:54 +0800 Subject: [PATCH 217/778] Some changes --- Resources/Lang/en_US.json | 4 ++-- Roles/Neutral/Troller.cs | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index c3b6764756..10585f07e3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1804,8 +1804,8 @@ "Troller_TrollsPerRound": "Trolls Per Round", "Troller_CanHaveStartMeetingEvent": "Can Start Meeting By Event", - "TrollerChangesSpeed": "Troller changed everyone speed!", - "TrollerSpeedOut": "Speed returned back", + "Troller_ChangesSpeed": "Troller changed everyone speed!", + "Troller_SpeedOut": "Speed returned back", "Troller_YouChangedCooldown": "You changed the cooldown of all players", "Troller_ChangeYourCooldown": "Troller change your cooldown!", "Troller_NoAddons": "No addons found on the random target", diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 42fe7dbe30..358fe3e0cf 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -115,7 +115,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun foreach (var pcSpeed in Main.AllAlivePlayerControls) { Main.AllPlayerSpeed[pcSpeed.PlayerId] = newSpeed; - pcSpeed.Notify(GetString("TrollerChangesSpeed")); + pcSpeed.Notify(GetString("Troller_ChangesSpeed")); } Utils.MarkEveryoneDirtySettings(); @@ -124,7 +124,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun foreach (var pcSpeed in Main.AllAlivePlayerControls) { Main.AllPlayerSpeed[pcSpeed.PlayerId] = Main.AllPlayerSpeed[pcSpeed.PlayerId] - newSpeed + tempSpeed[pcSpeed.PlayerId]; - pcSpeed.Notify(GetString("TrollerSpeedOut")); + pcSpeed.Notify(GetString("Troller_SpeedOut")); } Utils.MarkEveryoneDirtySettings(); }, 10f, "Troller: Set Speed to default"); @@ -158,16 +158,12 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun var randomSabotage = allSabotage.RandomElement(); switch (randomSabotage) { - case SystemTypes.Reactor: - case SystemTypes.Laboratory: - case SystemTypes.HeliSabotage: - case SystemTypes.LifeSupp: - case SystemTypes.Comms: - shipStatusActivated.RpcUpdateSystem(randomSabotage, 128); - break; case SystemTypes.MushroomMixupSabotage: shipStatusActivated.RpcUpdateSystem(randomSabotage, 1); break; + default: + shipStatusActivated.RpcUpdateSystem(randomSabotage, 128); + break; } troller.Notify(GetString("Troller_YouCausedSabotage")); break; From bea3ebd9eb8fa11563ad6e1c57d528f78bb94a13 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 11 Aug 2024 12:58:18 -0400 Subject: [PATCH 218/778] this time for real --- Patches/ChatCommandPatch.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 68a4d00b84..6e02c2f1d4 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -180,7 +180,8 @@ public static bool Prefix(ChatController __instance) canceled = true; Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); break; - + + case "/apocinfo": case "/apocalypseinfo": canceled = true; Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); @@ -2013,6 +2014,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); break; + case "/apocinfo": case "/apocalypseinfo": Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); break; From 23d11d28ecc289045930bb6782bbcab9d91737c7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 12 Aug 2024 01:16:21 +0800 Subject: [PATCH 219/778] Dev 3 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 765d79c207..de5695626d 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0807.210.00020"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Dev 2"; + public const string PluginVersion = "2024.0812.210.00030"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Dev 3"; public const string SupportedVersionAU = "2024.6.18"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Dev 2 + public static readonly bool devRelease = true; // Latest: V2.1.0 Dev 3 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.2 From 11169c1947b2d6dd0c570d6ec3e6c7cdc708480b Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:00:17 +0000 Subject: [PATCH 220/778] Fixes --- .../Images/Skills/DoubleAgentPocketBomb.png | Bin 0 -> 6934 bytes Roles/Impostor/DoubleAgent.cs | 95 ++++++++---------- 2 files changed, 42 insertions(+), 53 deletions(-) create mode 100644 Resources/Images/Skills/DoubleAgentPocketBomb.png diff --git a/Resources/Images/Skills/DoubleAgentPocketBomb.png b/Resources/Images/Skills/DoubleAgentPocketBomb.png new file mode 100644 index 0000000000000000000000000000000000000000..45a7c7ef37e81a07fe79af3ad2e4dee1d9ff6a01 GIT binary patch literal 6934 zcmV+x8|mbUP)THLdbGS0%289HZ4#Ptya`( zD*~=OU98n=t$mbQ6(3qEt;-`V)TM}4r7m@?Y-M+^ARq)1z$`a=$W88++-;Wre`lFF zXJ*cK&di+~E>G_Z?=zD*+jrjo_paY^jhUrc3Cv1hRsypUn3ce+1ZE{LD}h-F%t~NZ z0<#kMe=UL3^Wp7_^GrtVJVtzm_*n1z5`8WfFBBgso+svgM#Kli`^5d?-Qr#19pXOm zL*o6Z0aKWX?>-{|5ddikt4DmI_(Jh%;uYe#;;hH5`h;7=0LCYj7w5dsN$)$K6CUr^ z=VtLE;s?Y}iic+kKr;pqfq-!7@n?(A6`w3#?t#$lfe>tO(zX{pp!^RsV_bYtJSg5S zeoXvR@&EE~fdhcg2_=20X=$85;Q466IpQ~pPxV0Iw>B{#LDSJrA1JhG)B|X*K7S+r zq4<9BvjV3Q&}or{f79uB(L@av7;|*uOT|}+Uo2iEZWm*I4tKkJp`$&3vqy}t`L6gL zfdWv6g%0q<5fGhf`3uEw5?>@<+C(65%=F-GVjMI* zo97)ZCPG^##sp540ETC65#J;Jj`-IChTob>(+Y^dAV;`Ze3|%6G3Kff0U?lYi@zm) zN<1va87Co3AtsTIaK89Y#rUg6i0JECF`eL>;=2S2|85#h8z2H>m0J0S;@6AUiRroZ zJP+skaq+EU61`?dI|L7e)mM9;^#~L#91;_a(Mi55PzaryLiGqJ@dE|MNjmw>;!DIU z#e}d)h#vTJ@rT5ip=N>*^?Y(1+WE)gxO#>v5Q;^hM zDn4IK1m))vwuaz1Zx_>J$)ukPqMa9r|3dtN1P!3GkYG}dxl8;F@k3MfD@_5!1B0mP z6=K5K)5X-6lK2aH8Mz4crkOx!Go}`e!R*=yQ1E5c%+Tzwipg6B8hw#w#r`CDQQlKiW0;>i}5k!4Ktkxx5?5mUxeH?LKbMs%ftjU%e<;}UGLbi5AndD7|6qa zU(7g#&4E)<{fzj|x>`7MPF|p$lm=_^ZBd5W75VVkpCZf6l@tinuIuGKZJS@ue{Zhwn6#qI=Yo>70 z7beWy#e?QUG7sC}mI3q7njt6ZX*MxL{5>%a-$sB!f=nd2B&n9v1&HUQdi4N8yftE5 zIfP+S93dJqt%TtJwUmP zI+MD4S?>{hI(xd792^?i**&Lc)!_bt^LA_o5PS-tP!l7vszr48BgFWVpQuAWEbS$7 zi=@O+mKRqu>*HPG-xHJkMP2_n9s9Jn>E-F*n;>We9{R1V%dA~JXg{_Q415-?I472qAqGp%s{LI`iE&ElC83HzNd7MGQ^>j!6hn z*Z!l9{cF7f_s_pCXWFkHH@)}g%%%NB^R}e-Ipsu}1vzl|U(I7r+!qT9rs;DhpSkAY zg-hNy+R}>l2YoBTQ+HBKbbBT`Wc`alh-o zlTM(-Rwm!41b_Sd$uC|lFy1}Z(#jk#WKxu%cV>#kzD%*Ob?*4sjpy##dZ9GpD>7R@ z+i+qTX|z7-yb{0OA9q z=AFBX=G}=dKw9aHX=%xtR4Qf0$4AY)xr@v(OON*|Vy;X=k`XY_`VHY=51;b=Q&zv_ z))i}C-QUsm&U`9Ga_f-5*s*Z*;J>`A@0r`=BdFvW%vsmuMB%fiopbzfYg^Br&hAqO z+B#mxbpqu``IXVv{Pu}wF4_DfPCkmzupf!vq|V0xTook&gvuqgDgYtmjf$-PLC0~iEZ~$m+Z5Owhbc)BjLf*Z1liAn5UFR$`#~dwC z0_2?Gf#LuB#Kxb5>ps*nY_+ub#}O(hQ=RRIxbNm7Dy*xjVPsc-h8Bw#z?6_IiPl zx#5&EU$~{`h*u3~+pa{A%Z3NXKJnAr;*vn>d35kiwdQ=qmB`J<+MTipaWpeUqRbVe zdpL8ZWyTVrsHb>ir8%dwaKO&32efrq$7kppd(}j?iyu zv;V+OY0Duqr-yQrOJzS&mg&Oz#|l82>e56mH)rSW-ADBH_Tmsn=S+@&`SOjAeEPMU zo_JQ~%K9Gd`CRd(JGboI)-!MW{`StyWG1tALX8wq>C^UEM-!-OJS~3c>aprdw-pf2 zTrnO_br$W2imV9fh{1aK}ta1Q4s1pK2B^TyC-wK4z)1ch~Ci+@Paljx?y% z^zYwc4vh{u+ENC^jkba`fBrE#w>$(sIy!20?$|MT`m&`ryu9z3n_sc@DaP*;(~I_O z-#?PgKDoQAr*Nnxd%U>);y&aZN&XnhEdr!!!nYL=X$Jt1dtkEYeo-clQbKHbq*mUr zy7xuWhUJb>OE>qObKsq7^?3hqdwZ8-)&>Umn301A1j?ddtW^TVkH6+0Spmw}Smv<} z8#}%>`s^1IBM~@p?SXwG`#QR|_4Uj%QBUQwX;b@_c2j zGvWYYoE$)?Sw+#qI#4#uHO>lxfuPgrw8BxMXb9n_o!`;|BCYQZ7y*zt%NKktF#O-T zyXTvu7WJB3Zo+KY`e+ywd_n*at~qWcWu`I-loxaiIEBN8Xvx9h`xFxYyuUqm1ef0S znpy?NNI<+;8H0@PuubEk`pGzSO~QfuM zC#*Tg)rd?=4xG@o&9sTz#jV*^(;|Q}j<&exc*^1e6YSxY|K^w{__l19S+;bYz{r`t z?T?$u{6y*6f^(06`4T9rmSY6Ubg*bHI)2FUn|0ASyZS~x_RG6|e9o?|?>TOupRsrw zG29)h!*Q|=5JFr`mTe>uK$w?QMI?Q}JOeO}U&9;#30Y4?E85!HOlN1O>F((=UEQ7f z-tK@@(iR64&xCp5QVA4K_?120Kc2~C1h~6W36Yk%iywWtVQ<*f*m}G#c&=J{^g2DS&FtLslo=fx zs&e+2I)H6$?a=ef?s8kfT-68~-Zg+?TN;Nll*%>!b8H%d1a09i_wXyBp>|(2=FBiJ z(NT+zbA;d%-_o9gcR=z&UiZ7J5v7Prz&JmMiqevhA1Osn;gHr3bAwKw-cR{u&$FA% z!9xcE_i@4S?>m1!^AnmvEcl5Fqggf)A{!t?08IXhGFd#ZYDvJiCrx*7tRm~wpxQk^ z+!sesxqM#XY0gYePKYO+?|E@SzZVKVc!B^dbiSZ)oUsE;pqlIaRuRtn&OIn-&Re|j zIHxWB`?s0lk^Yi%cjCCxJ(>arH$gU!$J~uYt|77w;Q|Qb9#Nn%C%0-2>osG}l+qF{ zQeNP`tSd+;pD#GVA0H=L7C4!l6N2V*c{2$pgsP=$+(f%l@E$VmN9dKjif_2&TW-&S zUi0j}P0H$bI#F0pccA{IYxeBrwVFa$57x%ps9fjq&awf*F%rTkK$vRDto0DB&pS&A z9Ks%FTU_mc3k6IL@qgM8JlRV@_M1q`5v;xgihHh1iN{1rXpR573#?u60Sj3L*@F2i zr6pU;;LskYJw4s7Tz2eQA9|NlGv?2x0FA$n#C8ixC?Sf|5F)Lp`W0YQIW38~WQ6Ca z(B})USaj__Le9%9;Tq6se)9owO^*u_T7;(L^U@dzE$@TlMM?f!gaz-wT6#hESasEw zXC8UPvF5P(pj9_ew$E~Y|p#a3KGVnDbVQ50`f3lw^QwMLyz zw|J(8_y{dP!0yc)FvQsTOo3d(Z5Nv0vg>1f09|eIZGA1lR|`KFNL0Q!$L7sls^?~^ zz+mv|;M|5o-r%F`HGFp)ki>w{`-!Cyd@aXL6lR@9MEviVD-e}H%ecC~IbdWYd}&Xq znCO7vY6x0^ugS@b(F}eMnyb*8SF1|pyOw-d`9M))C<|4#4_Dli%ucDgdF>zKrdd^$ z`fV2g2-8=h0AU)CwThO~xCTP^$&H@M5e?%Wjt|L~H3WeJL{aBru6)x~zBc)uF|{?s z^F5A2xynyx@Xvi))|{VE*m0+!^8`gU3fsw;9!?0vk^#DBt@6nV{cbHS_`|J!gyp|@ zI_-i&*6!pRHOlQS5Uw97n<~2nNCrTqe8TrUJ=3%$by$_S)xK;@5F(p~_<%%pN#aGj zhWIASduN=d3ktnb-<7pHJ|&PO26GI0a2y!bAOxIxNehDS9Mll^_uxHNrK2m?aVk+m zwkO0~Qx_m4e$2SAm61_PCv~g!;Ba{AhpA|W>%mhpOMYe^5?Kkrz|9B>8A zXgIfhL8fVd;A5FqWTUEaB%6j1VZ{ML;Rn^MxriShz~qEV3YAeD(-jWl{Nuj1RKHQ6 zmq%zpN#xaE+}=}4Qo~JOBBiZU6QWK>E>*Rvz{yyis-X)TAVgT~>0#S3fsBeWea+=* z*#BC9_>Bq*KCB^?3KwUF90_}ZF5NvOs2~Wz19@fZ(?Ikv+nO1-!S3vn5ZP*3L|9CI zqr4vFM;Jt?Dvl(2G^7^A0uT^%l^{#U9Epd43xJUHy0HoR zUDJtRht*|KM8nxHrD{@P+$0A~7Snrh@KJ;xscHHs-uV>jtNfgVo^@q_6xlb&E-~%3LiE~CS7gqfDwnb0aNQ#EeBgny& zM-d(SQE|;}oveU(2r^NXHRe^N_P`TqgfsT}t~O^AnG%gpxZ~J|(+mZl7X-mWsni}u z4ufgEi+=C!>-JrX#X0+kB?O*&n1t z{rk5j$O$|E5=2opGZfT{Zf}awjt6x7PTQi-M1bf9*c^X5Zk&A7d?U7KVuuIwtgE` zvxxxl?v2p}VU`|{Yt(W|tSe*hEN3dmO`YMz`UeKKMV2(CYK9z$0uCJDD}fuR0HZ5q z?%SY_W9ijN4v}|IX8*Y5WULC~^XxBgttTZ0#M_`AlLaWYyo7b^Zx^>*ku}3>(&n#& z4c*u8?iCr|o6F|}opGbr4Ge{6Iy@IS@gnDY!7$clz5>_P z0;5Ue=2(K4i=#qaq2B*rDMbF{NXFR!U_DS*{;gA;h|NqZu4m z&?AE<4GitnWVIcxH?TP4I5QNbJv^%*zcr}m1S#nj8bIk^97$E4@Y#Ehkc1GQRcv1%lR^8qu#yCesU6`&9 zik;#kno{Xg_i_qtc4#NrF@ko|@zA1UFRz3_^Q$ zI*KB|z)qqWEEi9#9a91%01A~}BCFpO+xF19*8`=`e-kZvd!trau}kT+kj6KzzqZ5t z+uO6X7Zug{P=;jLmzW(ZqKJ@|tdp@0%o)2iBr;Y04I4FC&h=g>%(6Dh0qm)08#lQ? z$Jw^2w91kQ?3PY*f=c#NSr}6#oRw+w5#>N9?w)I|YZ4eFeyj+K1B22C5ha^tQm?8D z3`eGQ_L7>Xdw;Bj?Kj>cW(6hN*+tzOb*5B7nIV)UT{~tLd~w3OYIDJSOtDwoG|XUI ziYwCQ>MOJ62On;4?w^m)L)bHmtpja~KncbfbfR;zBhmu`!!}$IDfVUo!hMLQ*e{B` z-zmidicJFvgBj**>fhgJMhElmK_tbNB5XRwF04^Rr()I(RopL&e*Biq!V1iGVs3>V}5M}8024D#DxEt-;Hr) zkp$0gjt{BzAOXYV2lmaygc05nrNn6@PVoa`A~M?w)+Fzi^c>nmWr5-#fkzwx2L6Gq zs3?hiQ$PSrQz%YAQ+kL%VNoM$PZ1BTv270+d=|Zrzwv}QLwo9uRuVLvsXBz}Y^E7r zpdNiOW)*)yrGljIR@?rG3EEI~%CrK)(*z12P?9GCW2}T+1rwI^*_bfQ!p-81Vy3v( z9FMjLz@OU5EKBx1q@;s+N=m+oqAG#*66XIxU{tMuN_xYn^O6c>s?Px!|4%uPT(J!g zn-Ty_Be@XfjDj6=#u#xT|M6>Tm_{Nzw3E%(0AQ84o-o>fUmpk}!3V_*O*7(W-7;<} z!a8h#l-|(&OP^>8{>2~6O5$Ip@}Bix3lP*|m?%iXOXmGQ0-|a?N4+9smFU07*qoM6N<$f^ltG^8f$< literal 0 HcmV?d00001 diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 533b1d5a00..d803d0f548 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -160,12 +160,47 @@ public override void AfterMeetingTasks() CanBombInMeeting = true; } - // If enabled and if DoubleAgent is last Impostor become set role. - public override void OnFixedUpdate(PlayerControl pc) + // Active bomb timer update and check. + private void OnFixedUpdateOthers(PlayerControl player) + { + if (!CurrentBombedPlayers.Contains(player.PlayerId)) return; + + if (!player.IsAlive()) // If Player is dead clear bomb. + ClearBomb(); + + if (BombIsActive && (GameStates.IsInTask && GameStates.IsInGame) && !(GameStates.IsMeeting && GameStates.IsExilling)) + { + var OldCurrentBombedTime = (int)CurrentBombedTime; + + CurrentBombedTime -= Time.deltaTime; + + if (OldCurrentBombedTime > (int)CurrentBombedTime && CurrentBombedTime < (BombExplosionTimer.GetFloat() + 1)) + SendRPC(); + + if (CurrentBombedTime < 1) + BoomBoom(player); + } + } + + // Set timer on Double Agent for Non-Modded Clients. + public override void OnFixedUpdateLowLoad(PlayerControl pc) { + if (BombIsActive) + { + if (!pc.IsModClient()) + { + string Duration = Utils.ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); + if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Item1 != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); + } + + if (CurrentBombedPlayers.Any(playerId => Utils.GetPlayerById(playerId) == null)) // If playerId is a null Player clear bomb. + ClearBomb(); + } + + // If enabled and if DoubleAgent is last Impostor become set role. if (ChangeRoleToOnLast.GetValue() != 0 && StartedWithMoreThanOneImp && GameStates.IsInTask && !GameStates.IsMeeting && !GameStates.IsExilling) { - if (pc.Is(CustomRoles.DoubleAgent) && Main.AllAlivePlayerControls.Count(player => player.Is(Custom_Team.Impostor)) < 2) + if (pc.Is(CustomRoles.DoubleAgent) && Main.AliveImpostorCount < 2) { var Role = CRoleChangeRoles[ChangeRoleToOnLast.GetValue()]; if (ChangeRoleToOnLast.GetValue() == 1) // Random @@ -197,44 +232,6 @@ public override void OnFixedUpdate(PlayerControl pc) pc.Notify(Utils.ColorString(Utils.GetRoleColor(pc.GetCustomRole()), GetString("DoubleAgentRoleChange") + RoleName)); } } - - if (CurrentBombedPlayers.Any(playerId => Utils.GetPlayerById(playerId) == null)) // If playerId is a null Player clear bomb. - ClearBomb(); - } - - // Active bomb timer update and check. - private void OnFixedUpdateOthers(PlayerControl player) - { - if (!CurrentBombedPlayers.Contains(player.PlayerId)) return; - - if (!player.IsAlive()) // If Player is dead clear bomb. - ClearBomb(); - - if (BombIsActive && (GameStates.IsInTask && GameStates.IsInGame) && !(GameStates.IsMeeting && GameStates.IsExilling)) - { - var OldCurrentBombedTime = (int)CurrentBombedTime; - - CurrentBombedTime -= Time.deltaTime; - - if (OldCurrentBombedTime > (int)CurrentBombedTime && CurrentBombedTime < (BombExplosionTimer.GetFloat() + 1)) - SendRPC(); - - if (CurrentBombedTime < 1) - BoomBoom(player); - } - } - - // Set timer on Double Agent for Non-Modded Clients. - public override void OnFixedUpdateLowLoad(PlayerControl pc) - { - if (BombIsActive) - { - if (!pc.IsModClient()) - { - string Duration = Utils.ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); - if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Item1 != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); - } - } } // Players go bye bye ¯\_(ツ)_/¯ @@ -246,13 +243,14 @@ private void BoomBoom(PlayerControl player) { if (CheckForPlayersInRadius(player, target) <= ExplosionRadius.GetFloat()) { - if (player.inVent) return; + if (player.inVent) continue; Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Bombed; target.RpcMurderPlayer(target); + target.SetRealKiller(player); } } - PlaySoundForAll("Boom"); + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); ClearBomb(); _Player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); @@ -260,15 +258,6 @@ private void BoomBoom(PlayerControl player) private static float CheckForPlayersInRadius(PlayerControl player, PlayerControl target) => Vector2.Distance(player.GetCustomPosition(), target.GetCustomPosition()); - // Play specific sound for all players. - private static void PlaySoundForAll(string Sound) - { - foreach (PlayerControl player in Main.AllPlayerControls) - { - player.RPCPlayCustomSound(Sound); - } - } - // Set bomb mark on player. public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { @@ -298,7 +287,7 @@ private void SendRPC() // Receive and set bomb timer from Host when active. public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - CurrentBombedTime = reader.ReadInt32(); + CurrentBombedTime = reader.ReadPackedInt32(); } // Use button for Modded! From c5f0fd30d15e2795b14e9db36b3851f87d646c23 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:24:23 +0200 Subject: [PATCH 221/778] fix --- Resources/Lang/en_US.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 10585f07e3..c2a2cf6261 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2325,6 +2325,15 @@ "Experimental.Roles": "★ Experimental Roles (NOTICE: Use with caution, as these require testing)", "ActiveRolesList": "Active Roles List", "ForExample": "Example Use", + "ImpCanBeGuesser": "Impostors can become Guesser", + "CrewCanBeGuesser": "Crewmates can become Guesser", + "NeutralCanBeGuesser": "Neutrals can become Guesser", + "CrewCanBeMundane": "Crewmates can become Mundane", + "NeutralCanBeMundane": "Neutrals can become Mundane", + "ObliviousBaitImmune": "Immune to Bait", + "ImpCanBeInLove": "Impostors can be in love", + "CrewCanBeInLove": "Crewmates can be in love", + "NeutralCanBeInLove": "Neutrals can be in love", "updateButton": "Update", "updatePleaseWait": "Please Wait...", "updateManually": "Update failed.\nPlease try again or Update Manually.", From fa9d8e7ca549564b6050e20054a25bc85c110d0c Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:32:10 +0200 Subject: [PATCH 222/778] change neutral color --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index c2a2cf6261..737bd72571 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3215,7 +3215,7 @@ "ImpCanBeRole": "Impostors can become {role}", "CrewCanBeRole": "Crewmates can become {role}", - "NeutralCanBeRole": "Neutrals can become {role}", + "NeutralCanBeRole": "Neutrals can become {role}", "VotesPerKill": "Votes gained for each kill", "PickpocketGetVote": "You've got {0} votes", From 3100add9f93948c0af48b08985d63723fe9fb145 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:53:17 +0200 Subject: [PATCH 223/778] fix death msg --- Modules/ExtendedPlayerControl.cs | 7 +++++++ Modules/GameState.cs | 1 + Modules/RPC.cs | 1 + Patches/ChatCommandPatch.cs | 8 ++++---- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 1181879343..0c5187bc2a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -12,6 +12,7 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -1241,6 +1242,12 @@ public static PlayerControl GetRealKiller(this PlayerControl target) var killerId = Main.PlayerStates[target.Data.PlayerId].GetRealKiller(); return killerId == byte.MaxValue ? null : Utils.GetPlayerById(killerId); } + public static PlayerControl GetRealKiller(this PlayerControl target, out CustomRoles killerRole) + { + var killerId = Main.PlayerStates[target.Data.PlayerId].GetRealKiller(); + killerRole = Main.PlayerStates[target.Data.PlayerId].RoleofKiller; + return killerId == byte.MaxValue ? null : Utils.GetPlayerById(killerId); + } public static PlayerControl GetRealKillerById(this byte targetId) { var killerId = Main.PlayerStates[targetId].GetRealKiller(); diff --git a/Modules/GameState.cs b/Modules/GameState.cs index cd8711ff4b..deb65b33d5 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -20,6 +20,7 @@ public class PlayerState(byte playerId) public CountTypes countTypes = CountTypes.OutOfGame; public bool IsDead { get; set; } = false; public bool Disconnected { get; set; } = false; + public CustomRoles RoleofKiller = CustomRoles.NotAssigned; #pragma warning disable IDE1006 // Naming Styles public DeathReason deathReason { get; set; } = DeathReason.etc; #pragma warning restore IDE1006 diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 90dff4e382..21c5f65f5c 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -1014,6 +1014,7 @@ public static void SetRealKiller(byte targetId, byte killerId) var state = Main.PlayerStates[targetId]; state.RealKiller.Item1 = DateTime.Now; state.RealKiller.Item2 = killerId; + state.RoleofKiller = Main.PlayerStates[killerId].MainRole; if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRealKiller, SendOption.Reliable, -1); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 6e02c2f1d4..282e0431d3 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -453,9 +453,9 @@ public static bool Prefix(ChatController __instance) else { Logger.Info("GetRealKiller()", "/death command"); - var killer = PlayerControl.LocalPlayer.GetRealKiller(); + var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); string killerName = killer == null ? "N/A" : killer.GetRealName(); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(killer.GetCustomRole()); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); break; @@ -2188,9 +2188,9 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can } else { - var killer = player.GetRealKiller(); + var killer = player.GetRealKiller(out var MurderRole); string killerName = killer == null ? "N/A" : killer.GetRealName(); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(killer.GetCustomRole()); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); break; } From b958893865704d874a301cc2319867f5d48c0454 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:54:40 +0200 Subject: [PATCH 224/778] remove using --- Modules/ExtendedPlayerControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 0c5187bc2a..1292dbf1aa 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -12,7 +12,6 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; namespace TOHE; From f5832ba345d0a0316e5ed3e21790ca5bcb971167 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:05:30 +0200 Subject: [PATCH 225/778] fix missing colors --- Resources/roleColor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/roleColor.json b/Resources/roleColor.json index da8455f729..0f2023f6e3 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -1,8 +1,8 @@ { "Crewmate": "#8cffff", - "Engineer": "#8cffff", - "Scientist": "#8cffff", - "GuardianAngel": "#ffffff", + "Engineer": "#FF6A00", + "Scientist": "#8ee98e", + "GuardianAngel": "#77e6d1", "Noisemaker": "#9100E3", "Tracker": "#1BB313", "CrewmateTOHE": "#8cffff", From 3359b9959cdff78193fe5f169272d1308ee69eaa Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:08:57 +0200 Subject: [PATCH 226/778] fix phantom also --- Modules/CustomRolesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 24820de982..d7014b4992 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -284,7 +284,8 @@ Custom_RoleType.ImpostorHindering or return role is CustomRoles.Impostor or - CustomRoles.Shapeshifter; + CustomRoles.Shapeshifter or + CustomRoles.Phantom; } public static bool IsAbleToBeSidekicked(this CustomRoles role) From 6c0744f1efc9d06e52814a5882bbbb7f48fcbf3e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:04:26 +0200 Subject: [PATCH 227/778] more fix --- Resources/Lang/en_US.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 737bd72571..325167ff5b 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2076,6 +2076,8 @@ "CleanerCleanBody": "The body has been cleaned", "QuickShooterStoraging": "Bullets stored successfully", "PoisonerTargetDead": "Target died", + "HexesLookLikeSpells": "Hexes appear as spells", + "HexButtonText": "Hex", "BloodthirstAdded": "Your bloodthirst is now active!", "WarlockNoTarget": "Manipulation failed due to no target", "WarlockNoTargetYet": "You haven't marked a target.", From 00f1b28a717658dcfaacfe6f70bbd8decbd0d5d7 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Tue, 13 Aug 2024 01:59:45 -0400 Subject: [PATCH 228/778] Fix egoist win text --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b3163cea03..24ec67c890 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3415,7 +3415,7 @@ "WinnerRoleText.Pelican": "Pelican Wins!", "WinnerRoleText.Youtuber": "YouTuber Wins!", "WinnerRoleText.Necromancer": "Necromancer Wins!", - "WinnerRoleText.Egoist": "Egoists Win!", + "WinnerRoleText.Egoist": "Egoist Wins!", "WinnerRoleText.Demon": "Demon Wins!", "WinnerRoleText.Stalker": "Stalker Wins!", "WinnerRoleText.Workaholic": "Workaholic Wins!", From 729d4fccb14729e6261a713688ddf2bbf62722fc Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Tue, 13 Aug 2024 02:25:16 -0400 Subject: [PATCH 229/778] Egoist(s) --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 24ec67c890..7520e19159 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3415,7 +3415,7 @@ "WinnerRoleText.Pelican": "Pelican Wins!", "WinnerRoleText.Youtuber": "YouTuber Wins!", "WinnerRoleText.Necromancer": "Necromancer Wins!", - "WinnerRoleText.Egoist": "Egoist Wins!", + "WinnerRoleText.Egoist": "Egoist(s) Wins!", "WinnerRoleText.Demon": "Demon Wins!", "WinnerRoleText.Stalker": "Stalker Wins!", "WinnerRoleText.Workaholic": "Workaholic Wins!", From b74bf5facca89e702ed60dc108f5e295ffd2eeff Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 13 Aug 2024 19:20:58 +0800 Subject: [PATCH 230/778] Some changes --- Resources/Lang/en_US.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 2ac56e720b..18634476c1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1506,7 +1506,7 @@ "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", "LawyerCanTargetImpostor": "Can Target Impostors", - "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", + "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", "LawyerCanTargetCrewmate": "Can Target Crewmates", "LawyerCanTargetJester": "Can Target Jester", @@ -2931,11 +2931,11 @@ "JailerJailCooldown": "Jail cooldown", "JailerMaxExecution": "Maximum executions", - "JailerNBCanBeExe": "Can execute Neutral Benign", - "JailerNCCanBeExe": "Can execute Neutral Chaos", - "JailerNECanBeExe": "Can execute Neutral Evil", - "JailerNKCanBeExe": "Can execute Neutral Killing", - "JailerNACanBeExe": "Can execute Neutral Apocalypse", + "JailerNBCanBeExe": "Can execute Neutral Benign", + "JailerNCCanBeExe": "Can execute Neutral Chaos", + "JailerNECanBeExe": "Can execute Neutral Evil", + "JailerNKCanBeExe": "Can execute Neutral Killing", + "JailerNACanBeExe": "Can execute Neutral Apocalypse", "JailerCKCanBeExe": "Can execute Crew Killing", "JailerTargetAlreadySelected": "You have already selected a target", "SuccessfullyJailed": "Target successfully jailed", From 30a314e8e8f8d15a4cb680f8b4b771bafb8db0b8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 13 Aug 2024 19:37:35 +0800 Subject: [PATCH 231/778] Fix FFA stuck meeting --- GameModes/FFAManager.cs | 10 +--------- Patches/IntroPatch.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index 497a97abe5..a75e113503 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -102,14 +102,6 @@ public static void Init() _ = new LateTask( ()=> { - try - { - Utils.SetChatVisibleForEveryone(); - } - catch (Exception error) - { - Logger.Error($"Error: {error}", "FFA Init"); - } RoundTime = FFA_GameTime.GetInt() + 8; var now = Utils.GetTimeStamp() + 8; foreach (PlayerControl pc in Main.AllAlivePlayerControls) @@ -117,7 +109,7 @@ public static void Init() KBScore.TryAdd(pc.PlayerId, 0); if (FFA_DisableVentingWhenKCDIsUp.GetBool()) FFALastKill.TryAdd(pc.PlayerId, now); } - }, 10f, "Set Chat Visible for Everyone"); + }, 15f, "Set Chat Visible for Everyone"); } private static void SendRPCSyncFFAPlayer(byte playerId) { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 5ec5795735..e572e0f790 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -561,6 +561,20 @@ public static void Postfix() }, 3f, "Set Dev Ghost-Roles"); } + bool chatVisible = Options.CurrentGameMode switch + { + CustomGameMode.FFA => true, + _ => false + }; + try + { + if (chatVisible) Utils.SetChatVisibleForEveryone(); + } + catch (Exception error) + { + Logger.Error($"Error: {error}", "FFA chat visible"); + } + if (Main.UnShapeShifter.Any()) { _ = new LateTask(() => From 13f8197561b65555ef4521d842d9d19ae36322d5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 13 Aug 2024 19:28:02 +0200 Subject: [PATCH 232/778] badum --- Patches/TextBoxPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index e0aca7148f..78a29d65ab 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -77,7 +77,7 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp /// /// "Fixes" an issue where empty TextBoxes have wrong cursor positions. /// -[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] +/*[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] internal static class CursorPosPatch { public static bool Prefix(TextMeshPro self, ref Vector2 __result) @@ -90,7 +90,7 @@ public static bool Prefix(TextMeshPro self, ref Vector2 __result) return true; } -} +}*/ // 2024.8.13 break this From 74265ae41e6d919221d8ff24f1c90129af0fa9be Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:20:32 +0200 Subject: [PATCH 233/778] LOL --- Patches/TextBoxPatch.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index 78a29d65ab..69715a5328 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -77,7 +77,8 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp /// /// "Fixes" an issue where empty TextBoxes have wrong cursor positions. /// -/*[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] +[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos), typeof(TextMeshPro))] +[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos), typeof(TextMeshPro), typeof(int))] internal static class CursorPosPatch { public static bool Prefix(TextMeshPro self, ref Vector2 __result) @@ -90,7 +91,7 @@ public static bool Prefix(TextMeshPro self, ref Vector2 __result) return true; } -}*/ // 2024.8.13 break this +} From 2624a1dac19ac0d6e0d07986b9a94f1211957e29 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 14 Aug 2024 03:55:34 +0800 Subject: [PATCH 234/778] Fix missing code --- Modules/CustomRolesHelper.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index d7014b4992..865cb6e7b1 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1015,6 +1015,13 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Mare)) return false; break; + + case CustomRoles.Statue: + if (pc.Is(CustomRoles.Alchemist) + || pc.Is(CustomRoles.Flash) + || pc.Is(CustomRoles.Tired)) + return false; + break; } return true; From 695f4c486297f4d6dae48f96f20eb97fa6b57376 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 14 Aug 2024 04:14:03 +0800 Subject: [PATCH 235/778] Fix --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index 762596e549..cbb8891a08 100644 --- a/main.cs +++ b/main.cs @@ -48,7 +48,7 @@ public class Main : BasePlugin /******************* Change one of the three variables to true before making a release. *******************/ public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 4 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 - public static readonly bool fullRelease = true; // Latest: V2.0.3 + public static readonly bool fullRelease = false; // Latest: V2.0.3 public static bool hasAccess = true; From 388de512473e9d9a973349c0b28bc874b9d97bcd Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 14 Aug 2024 04:27:41 +0800 Subject: [PATCH 236/778] Fix bug --- Patches/GameOptionsMenuPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index f7b1fe2601..fc4e7237bf 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -611,7 +611,7 @@ private static bool InitializePrefix(StringOption __instance) } private static void SetupHelpIcon(CustomRoles role, StringOption option) { - var template = option.transform.FindChild("MinusButton (1)"); + var template = option.transform.FindChild("MinusButton"); var icon = Object.Instantiate(template, template.parent, true); icon.name = $"{role}HelpIcon"; var text = icon.FindChild("Plus_TMP").GetComponent(); @@ -693,7 +693,7 @@ public static bool DecreasePrefix(StringOption __instance) { //Credit For SetupHelpIcon to EHR https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/GameOptionsMenuPatch.cs - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index) && !__instance.transform.FindChild("MinusButton (1)").GetComponent().activeSprites.activeSelf) + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index) && !__instance.transform.FindChild("MinusButton").GetComponent().activeSprites.activeSelf) { var item = OptionItem.AllOptions[index]; var name = item.GetName(); From c062170051117a3f3c189213ba778b57cdd84443 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 14 Aug 2024 04:35:11 +0800 Subject: [PATCH 237/778] Temporarily removed --- Patches/GameOptionsMenuPatch.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index fc4e7237bf..2d9259df05 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -601,7 +601,7 @@ private static bool InitializePrefix(StringOption __instance) _ => 0.35f, }; - SetupHelpIcon(role, __instance); + //SetupHelpIcon(role, __instance); } __instance.TitleText.text = name; __instance.AdjustButtonsActiveState(); @@ -609,19 +609,19 @@ private static bool InitializePrefix(StringOption __instance) } return true; } - private static void SetupHelpIcon(CustomRoles role, StringOption option) - { - var template = option.transform.FindChild("MinusButton"); - var icon = Object.Instantiate(template, template.parent, true); - icon.name = $"{role}HelpIcon"; - var text = icon.FindChild("Plus_TMP").GetComponent(); - text.text = "?"; - text.color = Color.white; - icon.FindChild("InactiveSprite").GetComponent().color = Color.black; - icon.FindChild("ActiveSprite").GetComponent().color = Color.gray; - icon.localPosition += new Vector3(-0.8f, 0f, 0f); - icon.SetAsLastSibling(); - } + //private static void SetupHelpIcon(CustomRoles role, StringOption option) + //{ + // var template = option.transform.FindChild("MinusButton"); + // var icon = Object.Instantiate(template, template.parent, true); + // icon.name = $"{role}HelpIcon"; + // var text = icon.FindChild("Plus_TMP").GetComponent(); + // text.text = "?"; + // text.color = Color.white; + // icon.FindChild("InactiveSprite").GetComponent().color = Color.black; + // icon.FindChild("ActiveSprite").GetComponent().color = Color.gray; + // icon.localPosition += new Vector3(-0.8f, 0f, 0f); + // icon.SetAsLastSibling(); + //} [HarmonyPatch(nameof(StringOption.UpdateValue)), HarmonyPrefix] private static bool UpdateValuePrefix(StringOption __instance) From ccd70b7a95a062c1d80b2127be1b27182a4a4c27 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:35:36 +0200 Subject: [PATCH 238/778] fix --- Patches/GameOptionsMenuPatch.cs | 72 ++++++++++++++++++--------------- Patches/TextBoxPatch.cs | 12 ++++-- TOHE.csproj | 2 +- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 2d9259df05..43561b250d 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -242,6 +242,7 @@ private static void OptionBehaviourSetSizeAndPosition(OptionBehaviour optionBeha case OptionTypes.String: optionBehaviour.transform.FindChild("PlusButton").localPosition += new Vector3(option.IsText ? 500f : 1.7f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); optionBehaviour.transform.FindChild("MinusButton").localPosition += new Vector3(option.IsText ? 500f : 0.9f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); + var valueTMP = optionBehaviour.transform.FindChild("Value_TMP (1)"); valueTMP.localPosition += new Vector3(1.3f, 0f, 0f); valueTMP.GetComponent().sizeDelta = new(2.3f, 0.4f); @@ -601,7 +602,7 @@ private static bool InitializePrefix(StringOption __instance) _ => 0.35f, }; - //SetupHelpIcon(role, __instance); + SetupHelpIcon(role, __instance); } __instance.TitleText.text = name; __instance.AdjustButtonsActiveState(); @@ -609,19 +610,44 @@ private static bool InitializePrefix(StringOption __instance) } return true; } - //private static void SetupHelpIcon(CustomRoles role, StringOption option) - //{ - // var template = option.transform.FindChild("MinusButton"); - // var icon = Object.Instantiate(template, template.parent, true); - // icon.name = $"{role}HelpIcon"; - // var text = icon.FindChild("Plus_TMP").GetComponent(); - // text.text = "?"; - // text.color = Color.white; - // icon.FindChild("InactiveSprite").GetComponent().color = Color.black; - // icon.FindChild("ActiveSprite").GetComponent().color = Color.gray; - // icon.localPosition += new Vector3(-0.8f, 0f, 0f); - // icon.SetAsLastSibling(); - //} + + //Credit For SetupHelpIcon to EHR https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/GameOptionsMenuPatch.cs + private static void SetupHelpIcon(CustomRoles role, StringOption __instance) + { + var template = __instance.transform.FindChild("MinusButton"); + var icon = GameObject.Instantiate(template, template.parent, true); + icon.gameObject.SetActive(true); + icon.name = $"{role}HelpIcon"; + var text = icon.GetComponentInChildren(); + text.text = "?"; + text.color = Color.white; + icon.FindChild("ButtonSprite").GetComponent().color = Color.black; + var GameOptionsButton = icon.GetComponent(); + GameOptionsButton.OnClick = new(); + GameOptionsButton.OnClick.AddListener((Action)(() => { + + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index)) + { + var item = OptionItem.AllOptions[index]; + var name = item.GetName(); + if (Enum.GetValues().Find(x => GetString($"{x}") == name.RemoveHtmlTags(), out var role)) + { + var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); + var str = Translator.GetString($"{roleName}InfoLong"); + int Lenght = str.Length > 360 ? 360 : str.Length; + var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; + var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); + var info = $"{ColorRole}: {infoLong}"; + GameSettingMenu.Instance.MenuDescriptionText.text = info; + } + } + })); + GameOptionsButton.interactableColor = Color.black; + GameOptionsButton.interactableHoveredColor = Color.grey; + icon.localPosition += new Vector3(-0.8f, 0f, 0f); + icon.SetAsLastSibling(); + + } [HarmonyPatch(nameof(StringOption.UpdateValue)), HarmonyPrefix] private static bool UpdateValuePrefix(StringOption __instance) @@ -691,24 +717,6 @@ public static bool IncreasePrefix(StringOption __instance) [HarmonyPatch(nameof(StringOption.Decrease)), HarmonyPrefix] public static bool DecreasePrefix(StringOption __instance) { - //Credit For SetupHelpIcon to EHR https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/GameOptionsMenuPatch.cs - - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index) && !__instance.transform.FindChild("MinusButton").GetComponent().activeSprites.activeSelf) - { - var item = OptionItem.AllOptions[index]; - var name = item.GetName(); - if (Enum.GetValues().Find(x => GetString($"{x}") == name.RemoveHtmlTags(), out var role)) - { - var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); - var str = Translator.GetString($"{roleName}InfoLong"); - int Lenght = str.Length > 360 ? 360 : str.Length; - var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; - var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); - var info = $"{ColorRole}: {infoLong}"; - GameSettingMenu.Instance.MenuDescriptionText.text = info; - return false; - } - } if (__instance.Value == 0) { __instance.Value = __instance.Values.Length - 1; diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index 69715a5328..9577f95e3a 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -6,7 +6,11 @@ namespace TOHE.Patches; // Originally code by Gurge44. Reference: https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/TextBoxPatch.cs -[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] +// Update 2024.8.13 drastically changed how the textbox works, so this is currently not working + + + +/*[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] class TextBoxTMPSetTextPatch { public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string input, [HarmonyArgument(1)] string inputCompo = "") @@ -70,14 +74,14 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp return false; } -} +}*/ //Thanks https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Fixes/CursorPosPatch.cs /// /// "Fixes" an issue where empty TextBoxes have wrong cursor positions. /// -[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos), typeof(TextMeshPro))] +/*[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos), typeof(TextMeshPro))] [HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos), typeof(TextMeshPro), typeof(int))] internal static class CursorPosPatch { @@ -91,7 +95,7 @@ public static bool Prefix(TextMeshPro self, ref Vector2 __result) return true; } -} +} */ diff --git a/TOHE.csproj b/TOHE.csproj index 88e0fc2ee1..bc11608bc9 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -7,7 +7,7 @@ Town Of Host Enhanced Moe preview - + C:\Program Files\Epic Games\AmongUs Debug;Release;Canary true True From 15f7563ebab5e4cd08405c3328774abc4d2a6523 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:37:04 +0200 Subject: [PATCH 239/778] revert --- TOHE.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOHE.csproj b/TOHE.csproj index bc11608bc9..ca5573ec1e 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -7,7 +7,7 @@ Town Of Host Enhanced Moe preview - C:\Program Files\Epic Games\AmongUs + Debug;Release;Canary true True From f897f74a15598ec2c11c4bea87548b10225df8cd Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:42:06 +0200 Subject: [PATCH 240/778] fix? --- Modules/RPC.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 21c5f65f5c..37297e60ec 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -1014,7 +1014,7 @@ public static void SetRealKiller(byte targetId, byte killerId) var state = Main.PlayerStates[targetId]; state.RealKiller.Item1 = DateTime.Now; state.RealKiller.Item2 = killerId; - state.RoleofKiller = Main.PlayerStates[killerId].MainRole; + state.RoleofKiller = Main.PlayerStates.TryGetValue(killerId, out var kState) ? kState.MainRole : CustomRoles.NotAssigned; if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRealKiller, SendOption.Reliable, -1); From c3a14520843db88266444d66a93ee0eaed30b7ab Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:49:06 +0200 Subject: [PATCH 241/778] incase custom description --- Patches/GameOptionsMenuPatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 43561b250d..e946ae52e6 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -635,7 +635,8 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); var str = Translator.GetString($"{roleName}InfoLong"); int Lenght = str.Length > 360 ? 360 : str.Length; - var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; + int strIndex = str.Contains('\n')? str.IndexOf("\n") : 0; + var infoLong = str[(strIndex + 1)..Lenght]; var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); var info = $"{ColorRole}: {infoLong}"; GameSettingMenu.Instance.MenuDescriptionText.text = info; From 0de728e4efdc6ea2daf1b2c9b0b8d1c572af055b Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:49:20 +0200 Subject: [PATCH 242/778] nothing --- Patches/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index e946ae52e6..22fe9107ff 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -635,7 +635,7 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); var str = Translator.GetString($"{roleName}InfoLong"); int Lenght = str.Length > 360 ? 360 : str.Length; - int strIndex = str.Contains('\n')? str.IndexOf("\n") : 0; + int strIndex = str.Contains('\n') ? str.IndexOf("\n") : 0; var infoLong = str[(strIndex + 1)..Lenght]; var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); var info = $"{ColorRole}: {infoLong}"; From 370174b2dc8e4b46f58b3756491b53011e3a7972 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:51:36 +0200 Subject: [PATCH 243/778] nvm it already does that --- Patches/GameOptionsMenuPatch.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 22fe9107ff..43561b250d 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -635,8 +635,7 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); var str = Translator.GetString($"{roleName}InfoLong"); int Lenght = str.Length > 360 ? 360 : str.Length; - int strIndex = str.Contains('\n') ? str.IndexOf("\n") : 0; - var infoLong = str[(strIndex + 1)..Lenght]; + var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); var info = $"{ColorRole}: {infoLong}"; GameSettingMenu.Instance.MenuDescriptionText.text = info; From fcd5a9d9ca182cd3d42a2a1e158170a55d09cc9d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:34:07 +0200 Subject: [PATCH 244/778] change color --- Patches/GameOptionsMenuPatch.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 43561b250d..5215a7b37e 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -621,7 +621,9 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) var text = icon.GetComponentInChildren(); text.text = "?"; text.color = Color.white; - icon.FindChild("ButtonSprite").GetComponent().color = Color.black; + _ = ColorUtility.TryParseHtmlString("#117055", out var clr); + _ = ColorUtility.TryParseHtmlString("#33d6a3", out var clr2); + icon.FindChild("ButtonSprite").GetComponent().color = clr; var GameOptionsButton = icon.GetComponent(); GameOptionsButton.OnClick = new(); GameOptionsButton.OnClick.AddListener((Action)(() => { @@ -642,8 +644,8 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) } } })); - GameOptionsButton.interactableColor = Color.black; - GameOptionsButton.interactableHoveredColor = Color.grey; + GameOptionsButton.interactableColor = clr; + GameOptionsButton.interactableHoveredColor = clr2; icon.localPosition += new Vector3(-0.8f, 0f, 0f); icon.SetAsLastSibling(); From 54ac3a26f479357f98263f547347042106f8a722 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:03:46 +0200 Subject: [PATCH 245/778] lol fix it, (Thanks again EHR) --- Patches/TextBoxPatch.cs | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index 9577f95e3a..a028faee9e 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -6,38 +6,40 @@ namespace TOHE.Patches; // Originally code by Gurge44. Reference: https://github.com/Gurge44/EndlessHostRoles/blob/main/Patches/TextBoxPatch.cs -// Update 2024.8.13 drastically changed how the textbox works, so this is currently not working - - - -/*[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] +[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] class TextBoxTMPSetTextPatch { + // The only characters to treat specially are \r, \n and \b, allow all other characters to be written public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string input, [HarmonyArgument(1)] string inputCompo = "") { - if (!GameStates.IsModHost) return true; - bool flag = false; char ch = ' '; + __instance.AdjustCaretPosition(input.Length - __instance.text.Length); __instance.tempTxt.Clear(); - foreach (var str in input) + foreach (char c in input) { - char upperInvariant = str; - if (ch != ' ' || upperInvariant != ' ') + char upperInvariant = c; + if (ch == ' ' && upperInvariant == ' ') + { + __instance.AdjustCaretPosition(-1); + } + else { switch (upperInvariant) { - case '\r' or '\n': + case '\r': + case '\n': flag = true; break; case '\b': __instance.tempTxt.Length = Math.Max(__instance.tempTxt.Length - 1, 0); + __instance.AdjustCaretPosition(-2); break; } if (__instance.ForceUppercase) upperInvariant = char.ToUpperInvariant(upperInvariant); - if (upperInvariant is not '\b' and not '\n' and not '\r') + if (upperInvariant is not '\r' and not '\n' and not '\b') { __instance.tempTxt.Append(upperInvariant); ch = upperInvariant; @@ -46,9 +48,13 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp } if (!__instance.tempTxt.ToString().Equals(DestroyableSingleton.Instance.GetString(StringNames.EnterName), StringComparison.OrdinalIgnoreCase) && __instance.characterLimit > 0) + { + int length = __instance.tempTxt.Length; __instance.tempTxt.Length = Math.Min(__instance.tempTxt.Length, __instance.characterLimit); - input = __instance.tempTxt.ToString(); + __instance.AdjustCaretPosition(-(length - __instance.tempTxt.Length)); + } + input = __instance.tempTxt.ToString(); if (!input.Equals(__instance.text) || !inputCompo.Equals(__instance.compoText)) { __instance.text = input; @@ -70,14 +76,16 @@ public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] string inp } if (flag) __instance.OnEnter.Invoke(); - __instance.Pipe.transform.localPosition = __instance.outputText.CursorPos(); + __instance.SetPipePosition(); return false; } -}*/ +} //Thanks https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Fixes/CursorPosPatch.cs +//2024.8.13 break this + /// /// "Fixes" an issue where empty TextBoxes have wrong cursor positions. /// From 9df1e163a4ccafd5a1be4e0e5a964c2bc5d052e6 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 14 Aug 2024 23:57:17 +0800 Subject: [PATCH 246/778] Alpha 5 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index cbb8891a08..d209d1132d 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0813.210.00040"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 4"; + public const string PluginVersion = "2024.0814.210.00050"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 5"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 4 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 5 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 8d8b68f6022b4728566d0d2ba8ec019082904c28 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:03:59 +0000 Subject: [PATCH 247/778] new role --- Modules/CustomRolesHelper.cs | 1 + Resources/Lang/en_US.json | 7 ++ Roles/(Ghosts)/Impostor/Possessor.cs | 167 +++++++++++++++++++++++++++ main.cs | 1 + 4 files changed, 176 insertions(+) create mode 100644 Roles/(Ghosts)/Impostor/Possessor.cs diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 865cb6e7b1..1a5a909c7f 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1152,6 +1152,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Pelican => CountTypes.Pelican, CustomRoles.Minion => CountTypes.Impostor, CustomRoles.Bloodmoon => CountTypes.Impostor, + CustomRoles.Possessor => CountTypes.Impostor, CustomRoles.Demon => CountTypes.Demon, CustomRoles.BloodKnight => CountTypes.BloodKnight, CustomRoles.Cultist => CountTypes.Cultist, diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 18634476c1..9c09261005 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -109,6 +109,7 @@ "Witch": "Witch", "Nemesis": "Nemesis", "Bloodmoon": "Bloodmoon", + "Possessor": "Possessor", "Puppeteer": "Puppeteer", "Mastermind": "Mastermind", "TimeThief": "Time Thief", @@ -412,6 +413,7 @@ "BeforeNemesisInfo": "You can't kill yet", "AfterNemesisInfo": "Now start killing", "BloodmoonInfo": "Seek havoc upon the crewmates", + "PossessorInfo": "Possess and lead crewmates away from others", "PuppeteerInfo": "Make players kill for you", "MastermindInfo": "Make others kill for you", "TimeThiefInfo": "Lower meeting time by killing", @@ -711,6 +713,7 @@ "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", + "PossessorInfoLong": "(Impostors [Ghost]):\nAs the Possessor, you are able to possess players when others aren't in the Alert Range. Lead the possessed player as far as possible from other players that are in the Focus Range. Once the possession duration is up, the possessed player will be killed if others aren't in the Focus Range. If you run into another player in the Alert Range while possessing, the Possessor will immediately unpossess.", "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", @@ -2730,6 +2733,10 @@ "MinimumPlayersAliveToKill": "Minimum Players Alive To Kill", "BloodMoonCanKillNum": "Max BloodLettings", "BloodMoonTimeTilDie": "Time Until Death", + "PossessorPossessCooldown": "Possession Cooldown", + "PossessorPossessDuration": "Possession Duration", + "PossessorAlertRange": "Alert Range", + "PossessorFocusRange": "Focus Range", "DeathTimer": "Death In: {DeathTimer}s", "BerserkerKillCooldown": "Berserker kill cooldown", "BerserkerMax": "Max level that Berserker can reach", diff --git a/Roles/(Ghosts)/Impostor/Possessor.cs b/Roles/(Ghosts)/Impostor/Possessor.cs new file mode 100644 index 0000000000..996654fe98 --- /dev/null +++ b/Roles/(Ghosts)/Impostor/Possessor.cs @@ -0,0 +1,167 @@ +using AmongUs.GameOptions; +using TOHE.Roles.Core; +using UnityEngine; +using static TOHE.Options; +using static TOHE.Translator; + +namespace TOHE.Roles._Ghosts_.Impostor; + +internal class Possessor : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 29800; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Possessor); + public override CustomRoles ThisRoleBase => CustomRoles.GuardianAngel; + public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorGhosts; + //==================================================================\\ + public static bool controllingPlayer = false; + public static byte controllingTargetId = byte.MaxValue; + public static float controllingLastSpeed = float.MinValue; + public static float possessTime = float.MinValue; + + public static OptionItem PossessCooldown; + public static OptionItem PossessDuration; + public static OptionItem AlertRange; + public static OptionItem FocusRange; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Possessor); + PossessCooldown = FloatOptionItem.Create(Id + 10, "PossessorPossessCooldown", new(2.5f, 120f, 2.5f), 25f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) + .SetValueFormat(OptionFormat.Seconds); + PossessDuration = FloatOptionItem.Create(Id + 11, "PossessorPossessDuration", new(2.5f, 120f, 2.5f), 10f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) + .SetValueFormat(OptionFormat.Seconds); + AlertRange = FloatOptionItem.Create(Id + 12, "PossessorAlertRange", new(1f, 10f, 0.5f), 2.5f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) + .SetValueFormat(OptionFormat.Multiplier); + FocusRange = FloatOptionItem.Create(Id + 13, "PossessorFocusRange", new(5f, 25f, 2.5f), 10f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) + .SetValueFormat(OptionFormat.Multiplier); + } + public override void Init() + { + } + public override void Add(byte PlayerId) + { + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOther); + } + // EAC bans players when GA uses sabotage + public override bool CanUseSabotage(PlayerControl pc) => false; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.GuardianAngelCooldown = controllingPlayer ? 0f : PossessCooldown.GetFloat(); + AURoleOptions.ProtectionDurationSeconds = 0f; + } + + private void OnFixedUpdateOther(PlayerControl target) + { + if (target.PlayerId == controllingTargetId) + { + if (controllingPlayer && possessTime >= 0) + { + if (CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) > 5f) + { + _Player.RpcTeleport((_Player.GetCustomPosition() + target.GetCustomPosition()) / 2); + } + + foreach (var allPlayers in Main.AllAlivePlayerControls.Where(pc => pc != target)) + { + if (CheckRange(target.GetCustomPosition(), allPlayers.GetCustomPosition()) < AlertRange.GetFloat()) + { + controllingPlayer = false; + } + } + + if (CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) < 1f) + { + if (target.MyPhysics.Animations.IsPlayingRunAnimation()) + { + target.RpcTeleport(target.GetCustomPosition()); + target.MyPhysics.RpcCancelPet(); + } + } + else if (CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) < 3.5f) + { + if (!target.petting && CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) > 1f) + { + target.MyPhysics.RpcPet(_Player.GetCustomPosition(), new Vector2(500f, 500f)); + } + else if (!target.MyPhysics.Animations.IsPlayingRunAnimation()) + { + target.MyPhysics.RpcCancelPet(); + target.RpcTeleport(target.GetCustomPosition()); + } + } + + possessTime -= Time.deltaTime; + } + else + { + target.MyPhysics.RpcCancelPet(); + target.RpcTeleport(target.GetCustomPosition()); + Main.AllPlayerSpeed[target.PlayerId] = controllingLastSpeed; + target.MarkDirtySettings(); + controllingTargetId = byte.MaxValue; + controllingLastSpeed = float.MinValue; + _Player.RpcGuardAndKill(_Player); + + if (controllingPlayer) + { + float checkPos = float.MaxValue; + foreach (var allPlayers in Main.AllAlivePlayerControls.Where(pc => pc != target)) + { + if (CheckRange(_Player.GetCustomPosition(), allPlayers.GetCustomPosition()) < checkPos) + { + checkPos = CheckRange(_Player.GetCustomPosition(), allPlayers.GetCustomPosition()); + } + } + if (checkPos >= FocusRange.GetFloat()) + { + target.RpcMurderPlayer(target); + target.SetDeathReason(PlayerState.DeathReason.Curse); + target.SetRealKiller(_Player); + } + } + + controllingPlayer = false; + _Player.RpcResetAbilityCooldown(); + } + } + } + + private static float CheckRange(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); + + public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) + { + if (target.GetCustomRole().IsImpostorTeam()) + { + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Possessor), GetString("DollMaster_CannotPossessImpTeammate"))); + return false; + } + + if (!controllingPlayer) + { + // Cancel if Target is around other players + foreach (var allPlayers in Main.AllAlivePlayerControls.Where(pc => pc != target)) + { + if (CheckRange(target.GetCustomPosition(), allPlayers.GetCustomPosition()) < AlertRange.GetFloat()) + { + _Player.RpcResetAbilityCooldown(); + return false; + } + } + + _Player.RpcGuardAndKill(target); + controllingTargetId = target.PlayerId; + controllingLastSpeed = Main.AllPlayerSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); + possessTime = PossessDuration.GetFloat(); + controllingPlayer = true; + } + else + { + controllingPlayer = false; + } + + return false; + } +} diff --git a/main.cs b/main.cs index d209d1132d..fc0d2d38c6 100644 --- a/main.cs +++ b/main.cs @@ -582,6 +582,7 @@ public enum CustomRoles // Impostor Ghost Bloodmoon, Minion, + Possessor, //Impostor Anonymous, From 3aea0850e43933519848f5239be386f2bf49aed8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 15 Aug 2024 21:01:33 +0800 Subject: [PATCH 248/778] Troller reset ability --- Roles/Neutral/Troller.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 358fe3e0cf..9d447670a8 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -55,7 +55,7 @@ public override void Init() } public override void Add(byte playerId) { - AbilityLimit = TrollsPerRound.GetInt(); + ResetAbility(); AllEvents = [.. EnumHelper.GetAllValues()]; @@ -74,6 +74,7 @@ public override void Remove(byte playerId) { AbilityLimit = 0; } + private void ResetAbility() => AbilityLimit = TrollsPerRound.GetInt(); public override bool HasTasks(NetworkedPlayerInfo player, CustomRoles role, bool ForRecompute) => !ForRecompute; public override void ApplyGameOptions(IGameOptions opt, byte playerId) @@ -81,6 +82,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.EngineerCooldown = 1f; AURoleOptions.EngineerInVentMaxTime = 0f; } + public override void AfterMeetingTasks() => ResetAbility(); public override bool OnTaskComplete(PlayerControl troller, int completedTaskCount, int totalTaskCount) { if (!troller.IsAlive() || AbilityLimit <= 0) return true; From d1d83b851976e76a687c032262ef9d7091fe462d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 15 Aug 2024 21:10:12 +0800 Subject: [PATCH 249/778] Again fix Quizmaster last color --- Roles/Neutral/Quizmaster.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 2b27670a0b..68e65044a3 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -486,7 +486,6 @@ public override void FixUnsetAnswers() _ => "None" }; - HasQuestionTranslation = false; HasAnswersTranslation = false; ShowInvalid = false; @@ -495,18 +494,15 @@ public override void FixUnsetAnswers() for (int numOfQuestionsDone = 0; numOfQuestionsDone < 3; numOfQuestionsDone++) { - var prefix = ""; if (numOfQuestionsDone == positionForRightAnswer) { AnswerLetter = new List { "A", "B", "C" }[positionForRightAnswer]; - Answer = GetString(prefix + Answer); - Answers.Add(prefix + Answer); + Answers.Add(Answer); } else { - string thatAnswer = PossibleAnswers[rnd.Next(PossibleAnswers.Count)]; - thatAnswer = GetString(prefix + thatAnswer); - Answers.Add(prefix + thatAnswer); + string thatAnswer = PossibleAnswers.RandomElement(); + Answers.Add(thatAnswer); PossibleAnswers.Remove(thatAnswer); } } From 95b6218ba66f4e81798953618b5e9908f8967a5c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 15 Aug 2024 21:18:19 +0800 Subject: [PATCH 250/778] Some changes --- Patches/ChatCommandPatch.cs | 7 +++---- Patches/MeetingHudPatch.cs | 12 ++++++------ Roles/Neutral/Doppelganger.cs | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 282e0431d3..d8d06c4ebb 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -3075,11 +3075,10 @@ public static void Postfix(ChatController __instance) if (sendTo != byte.MaxValue && GameStates.IsLobby) { - if (Utils.GetPlayerInfoById(sendTo) != null) + var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); + if (networkedPlayerInfo != null) { - var targetinfo = Utils.GetPlayerInfoById(sendTo); - - if (targetinfo.DefaultOutfit.ColorId == -1) + if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) { var delaymessage = Main.MessagesToSend[0]; Main.MessagesToSend.RemoveAt(0); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 1529b30e36..65c6027076 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -298,23 +298,23 @@ public static bool Prefix(MeetingHud __instance) bool braked = false; if (tie) { - byte target = byte.MaxValue; + byte targetId = byte.MaxValue; foreach (var data in VotingData.Where(x => x.Key < 15 && x.Value == max).ToArray()) { if (Tiebreaker.VoteFor.Contains(data.Key)) { - if (target != byte.MaxValue) + if (targetId != byte.MaxValue) { - target = byte.MaxValue; + targetId = byte.MaxValue; break; } - target = data.Key; + targetId = data.Key; } } - if (target != byte.MaxValue) + if (targetId != byte.MaxValue) { Logger.Info("Flat breakers cover expulsion of players", "Tiebreaker Vote"); - exiledPlayer = GetPlayerInfoById(target); + exiledPlayer = GetPlayerInfoById(targetId); tie = false; braked = true; } diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index a89c113ede..747d718cc8 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -70,11 +70,11 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t var killerSkin = new NetworkedPlayerInfo.PlayerOutfit() .Set(kname, killer.CurrentOutfit.ColorId, killer.CurrentOutfit.HatId, killer.CurrentOutfit.SkinId, killer.CurrentOutfit.VisorId, killer.CurrentOutfit.PetId, killer.CurrentOutfit.NamePlateId); - var killerLvl = Utils.GetPlayerInfoById(killer.PlayerId).PlayerLevel; + var killerLvl = killer.Data.PlayerLevel; var targetSkin = new NetworkedPlayerInfo.PlayerOutfit() .Set(tname, target.CurrentOutfit.ColorId, target.CurrentOutfit.HatId, target.CurrentOutfit.SkinId, target.CurrentOutfit.VisorId, target.CurrentOutfit.PetId, target.CurrentOutfit.NamePlateId); - var targetLvl = Utils.GetPlayerInfoById(target.PlayerId).PlayerLevel; + var targetLvl = target.Data.PlayerLevel; DoppelVictim[target.PlayerId] = tname; From ddf13c5441985f5943a478b8e6484a2a8e260edb Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:55:03 -0500 Subject: [PATCH 251/778] fixes --- Roles/(Ghosts)/Impostor/Possessor.cs | 124 +++++++++++++-------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/Roles/(Ghosts)/Impostor/Possessor.cs b/Roles/(Ghosts)/Impostor/Possessor.cs index 996654fe98..5c9425d81a 100644 --- a/Roles/(Ghosts)/Impostor/Possessor.cs +++ b/Roles/(Ghosts)/Impostor/Possessor.cs @@ -1,58 +1,58 @@ -using AmongUs.GameOptions; -using TOHE.Roles.Core; -using UnityEngine; -using static TOHE.Options; -using static TOHE.Translator; - -namespace TOHE.Roles._Ghosts_.Impostor; - -internal class Possessor : RoleBase -{ - //===========================SETUP================================\\ - private const int Id = 29800; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Possessor); - public override CustomRoles ThisRoleBase => CustomRoles.GuardianAngel; - public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorGhosts; - //==================================================================\\ - public static bool controllingPlayer = false; - public static byte controllingTargetId = byte.MaxValue; - public static float controllingLastSpeed = float.MinValue; - public static float possessTime = float.MinValue; - - public static OptionItem PossessCooldown; - public static OptionItem PossessDuration; - public static OptionItem AlertRange; - public static OptionItem FocusRange; +using AmongUs.GameOptions; +using TOHE.Roles.Core; +using UnityEngine; +using static TOHE.Options; +using static TOHE.Translator; - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Possessor); - PossessCooldown = FloatOptionItem.Create(Id + 10, "PossessorPossessCooldown", new(2.5f, 120f, 2.5f), 25f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) +namespace TOHE.Roles._Ghosts_.Impostor; + +internal class Possessor : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 29800; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Possessor); + public override CustomRoles ThisRoleBase => CustomRoles.GuardianAngel; + public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorGhosts; + //==================================================================\\ + private static bool controllingPlayer = false; + private static byte controllingTargetId = byte.MaxValue; + private static float controllingLastSpeed = float.MinValue; + private static float possessTime = float.MinValue; + + private static OptionItem PossessCooldown; + private static OptionItem PossessDuration; + private static OptionItem AlertRange; + private static OptionItem FocusRange; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Possessor); + PossessCooldown = FloatOptionItem.Create(Id + 10, "PossessorPossessCooldown", new(2.5f, 120f, 2.5f), 25f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) .SetValueFormat(OptionFormat.Seconds); - PossessDuration = FloatOptionItem.Create(Id + 11, "PossessorPossessDuration", new(2.5f, 120f, 2.5f), 10f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) + PossessDuration = FloatOptionItem.Create(Id + 11, "PossessorPossessDuration", new(2.5f, 120f, 2.5f), 10f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) .SetValueFormat(OptionFormat.Seconds); AlertRange = FloatOptionItem.Create(Id + 12, "PossessorAlertRange", new(1f, 10f, 0.5f), 2.5f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) .SetValueFormat(OptionFormat.Multiplier); FocusRange = FloatOptionItem.Create(Id + 13, "PossessorFocusRange", new(5f, 25f, 2.5f), 10f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) - .SetValueFormat(OptionFormat.Multiplier); - } - public override void Init() - { - } - public override void Add(byte PlayerId) + .SetValueFormat(OptionFormat.Multiplier); + } + public override void Init() + { + } + public override void Add(byte PlayerId) + { + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOther); + } + // EAC bans players when GA uses sabotage + public override bool CanUseSabotage(PlayerControl pc) => false; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) { - CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOther); - } - // EAC bans players when GA uses sabotage - public override bool CanUseSabotage(PlayerControl pc) => false; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.GuardianAngelCooldown = controllingPlayer ? 0f : PossessCooldown.GetFloat(); - AURoleOptions.ProtectionDurationSeconds = 0f; + AURoleOptions.GuardianAngelCooldown = controllingPlayer ? 0f : PossessCooldown.GetFloat(); + AURoleOptions.ProtectionDurationSeconds = 0f; } - private void OnFixedUpdateOther(PlayerControl target) - { + private void OnFixedUpdateOther(PlayerControl target) + { if (target.PlayerId == controllingTargetId) { if (controllingPlayer && possessTime >= 0) @@ -115,8 +115,8 @@ private void OnFixedUpdateOther(PlayerControl target) } if (checkPos >= FocusRange.GetFloat()) { - target.RpcMurderPlayer(target); target.SetDeathReason(PlayerState.DeathReason.Curse); + target.RpcMurderPlayer(target); target.SetRealKiller(_Player); } } @@ -124,19 +124,19 @@ private void OnFixedUpdateOther(PlayerControl target) controllingPlayer = false; _Player.RpcResetAbilityCooldown(); } - } - } - - private static float CheckRange(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); - - public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) - { + } + } + + private static float CheckRange(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); + + public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) + { if (target.GetCustomRole().IsImpostorTeam()) { killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Possessor), GetString("DollMaster_CannotPossessImpTeammate"))); return false; - } - + } + if (!controllingPlayer) { // Cancel if Target is around other players @@ -156,12 +156,12 @@ public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) target.MarkDirtySettings(); possessTime = PossessDuration.GetFloat(); controllingPlayer = true; - } + } else { controllingPlayer = false; - } - - return false; - } -} + } + + return false; + } +} From 86b3497403746946bb64e969ec55a9c96e25caf4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 16 Aug 2024 22:59:19 +0800 Subject: [PATCH 252/778] Minus and Plus SetInteractable --- Patches/GameOptionsMenuPatch.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 5215a7b37e..849d284417 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -480,7 +480,6 @@ private static bool InitializePrefix(NumberOption __instance) { var item = OptionItem.AllOptions[index]; __instance.TitleText.text = item.GetName(); - __instance.AdjustButtonsActiveState(); return false; } @@ -512,13 +511,13 @@ private static bool FixedUpdatePrefix(NumberOption __instance) { if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index)) { - var item = OptionItem.AllOptions[index]; - //Logger.Info($"{item.Name}, {index}", "NumberOption.FixedUpdate.TryGetValue"); + __instance.MinusBtn.SetInteractable(true); + __instance.PlusBtn.SetInteractable(true); if (__instance.oldValue != __instance.Value) { __instance.oldValue = __instance.Value; - __instance.ValueText.text = GetValueString(__instance, __instance.Value, item); + __instance.ValueText.text = GetValueString(__instance, __instance.Value, OptionItem.AllOptions[index]); } return false; } @@ -537,7 +536,6 @@ public static bool IncreasePrefix(NumberOption __instance) __instance.Value = __instance.ValidRange.min; __instance.UpdateValue(); __instance.OnValueChanged.Invoke(__instance); - __instance.AdjustButtonsActiveState(); return false; } @@ -547,7 +545,6 @@ public static bool IncreasePrefix(NumberOption __instance) __instance.Value += increment; __instance.UpdateValue(); __instance.OnValueChanged.Invoke(__instance); - __instance.AdjustButtonsActiveState(); return false; } @@ -561,7 +558,6 @@ public static bool DecreasePrefix(NumberOption __instance) __instance.Value = __instance.ValidRange.max; __instance.UpdateValue(); __instance.OnValueChanged.Invoke(__instance); - __instance.AdjustButtonsActiveState(); return false; } @@ -571,7 +567,6 @@ public static bool DecreasePrefix(NumberOption __instance) __instance.Value -= increment; __instance.UpdateValue(); __instance.OnValueChanged.Invoke(__instance); - __instance.AdjustButtonsActiveState(); return false; } @@ -605,7 +600,6 @@ private static bool InitializePrefix(StringOption __instance) SetupHelpIcon(role, __instance); } __instance.TitleText.text = name; - __instance.AdjustButtonsActiveState(); return false; } return true; @@ -682,6 +676,8 @@ private static bool FixedUpdatePrefix(StringOption __instance) if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index)) { var item = OptionItem.AllOptions[index]; + __instance.MinusBtn.SetInteractable(true); + __instance.PlusBtn.SetInteractable(true); if (item is StringOptionItem stringOptionItem) { @@ -711,7 +707,6 @@ public static bool IncreasePrefix(StringOption __instance) __instance.Value = 0; __instance.UpdateValue(); __instance.OnValueChanged?.Invoke(__instance); - __instance.AdjustButtonsActiveState(); return false; } return true; @@ -724,7 +719,6 @@ public static bool DecreasePrefix(StringOption __instance) __instance.Value = __instance.Values.Length - 1; __instance.UpdateValue(); __instance.OnValueChanged?.Invoke(__instance); - __instance.AdjustButtonsActiveState(); return false; } return true; From 5783369c32351ed0a8420dfcedcb95cc00eca9a0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 16 Aug 2024 23:04:13 +0800 Subject: [PATCH 253/778] Fix bug --- Patches/GameOptionsMenuPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 849d284417..c2d7f0bd94 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -629,10 +629,10 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) if (Enum.GetValues().Find(x => GetString($"{x}") == name.RemoveHtmlTags(), out var role)) { var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); - var str = Translator.GetString($"{roleName}InfoLong"); + var str = GetString($"{roleName}InfoLong"); int Lenght = str.Length > 360 ? 360 : str.Length; var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; - var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), role.ToString()); + var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), GetString(role.ToString())); var info = $"{ColorRole}: {infoLong}"; GameSettingMenu.Instance.MenuDescriptionText.text = info; } From b07e1bf0e3307ca4db0982163f80db9a55de7540 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:40:17 -0400 Subject: [PATCH 254/778] show na on eject --- Modules/OptionHolder.cs | 5 +++++ Patches/MeetingHudPatch.cs | 5 +++++ Resources/Lang/en_US.json | 2 ++ 3 files changed, 12 insertions(+) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index f054af2f88..025f3c76b3 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -239,6 +239,7 @@ private enum RatesZeroOne public static OptionItem CEMode; public static OptionItem ShowImpRemainOnEject; public static OptionItem ShowNKRemainOnEject; + public static OptionItem ShowNARemainOnEject; public static OptionItem ShowTeamNextToRoleNameOnEject; public static OptionItem ConfirmEgoistOnEject; public static OptionItem ConfirmLoversOnEject; @@ -1165,6 +1166,10 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(ShowImpRemainOnEject) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + ShowNARemainOnEject = BooleanOptionItem.Create(60446, "ShowNARemainOnEject", true, TabGroup.ModSettings, false) + .SetParent(ShowImpRemainOnEject) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); ShowTeamNextToRoleNameOnEject = BooleanOptionItem.Create(60443, "ShowTeamNextToRoleNameOnEject", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 238, 232, byte.MaxValue)); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 65c6027076..ed81ce3764 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -423,6 +423,7 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti var name = ""; int impnum = 0; int neutralnum = 0; + int apocnum = 0; if (CustomRoles.Bard.RoleExist()) { @@ -440,6 +441,8 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti impnum++; else if (pc_role.IsNK() && pc != exiledPlayer.Object) neutralnum++; + else if (pc_role.IsNA() && pc != exiledPlayer.Object) + apocnum++; } switch (Options.CEMode.GetInt()) { @@ -493,6 +496,8 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti name += string.Format(GetString("OneNeutralRemain"), neutralnum) + comma; else name += string.Format(GetString("NeutralRemain"), neutralnum) + comma; + if (Options.ShowNARemainOnEject.GetBool() && apocnum > 0) + name += string.Format(GetString("ApocRemain"), neutralnum) + comma; } EndOfSession: diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 18634476c1..5fa901ed1e 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1294,6 +1294,7 @@ "ConfirmEjections.Role": "Role", "ShowImpRemainOnEject": "Show remaining Impostors on ejects", "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", + "ShowNARemainOnEject": "Show remaining Neutral Apocalypse on ejects", "ConfirmEgoistOnEject": "Confirm Egoists on ejection", "ConfirmLoversOnEject": "Confirm Lovers on ejection", "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", @@ -2557,6 +2558,7 @@ "ImpRemain": "{0} Impostors remaining", "NeutralRemain": "\n{0} Neutral Killers remain", "OneNeutralRemain": "\n{0} Neutral Killer remains", + "ApocRemain": "\n{0} Neutral Apocalypse remains", "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", "GameOverReason.HumansByTask": "The Crewmates completed all tasks", "GameOverReason.HumansDisconnect": "Crewmates disconnected", From 68e4c8c6bd6aac9bae0492177b06533a548a90d1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:52:28 -0400 Subject: [PATCH 255/778] apoc guess eachother setting --- Modules/OptionHolder.cs | 4 ++++ Resources/Lang/en_US.json | 2 ++ Roles/Neutral/Baker.cs | 9 +++++++++ Roles/Neutral/Berserker.cs | 9 +++++++++ Roles/Neutral/PlagueBearer.cs | 9 +++++++++ Roles/Neutral/SoulCollector.cs | 9 +++++++++ 6 files changed, 42 insertions(+) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 025f3c76b3..9510d3a83c 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -476,6 +476,7 @@ private enum RatesZeroOne public static OptionItem CanGuessAddons; public static OptionItem ImpCanGuessImp; public static OptionItem CrewCanGuessCrew; + public static OptionItem ApocCanGuessApoc; public static OptionItem HideGuesserCommands; public static OptionItem ShowOnlyEnabledRolesInGuesserUI; @@ -1680,6 +1681,9 @@ private static System.Collections.IEnumerator CoLoadOptions() ImpCanGuessImp = BooleanOptionItem.Create(60687, "ImpCanGuessImp", true, TabGroup.ModifierSettings, false) .SetHidden(true) .SetParent(GuesserMode); + ApocCanGuessApoc = BooleanOptionItem.Create(60691, "ApocCanGuessApoc", false, TabGroup.ModifierSettings, false) + .SetHidden(true) + .SetParent(GuesserMode); HideGuesserCommands = BooleanOptionItem.Create(60688, "GuesserTryHideMsg", true, TabGroup.ModifierSettings, false) .SetParent(GuesserMode) .SetColor(Color.green); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 5fa901ed1e..6a3bfa9db5 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -69,6 +69,7 @@ "ShowOnlyEnabledRolesInGuesserUI": "Show Only Enabled Roles In Guesser UI", "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", + "ApocCanGuessApoc": "Neutral Apocalypse Can Guess Neutral Apocalypse Roles", "GuessImmune": "Sorry, but target is immune to being guessed!", @@ -2019,6 +2020,7 @@ "GuessAdtRole": "Unfortunately, the Host's settings do not allow you to guess add-ons", "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", + "GuessApocRole": "Fortunately, the Host's settings does not allow Apocalypse to guess Apocalypse roles.", "GuessKill": "{0} was guessed", "GuessNull": "Please select an ID of a living player to guess their role", "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 8678e6fa3a..dec4dc0102 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -294,6 +294,15 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para BreadList[baker.PlayerId].Clear(); StarvedNonBreaded = true; } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessApocRole")); + return true; + } + return false; + } } internal class Famine : RoleBase { diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index a8b966e9c1..72b093b8f9 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -167,6 +167,15 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return noScav; } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessApocRole")); + return true; + } + return false; + } } internal class War : RoleBase { diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 2c2554f751..86060d3c8e 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -217,6 +217,15 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.KillButton.OverrideText(GetString("InfectiousKillButtonText")); } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessApocRole")); + return true; + } + return false; + } } internal class Pestilence : RoleBase diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 1b0a658670..364f04caaa 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -203,6 +203,15 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [.. deathList]); } + public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) + { + if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessApocRole")); + return true; + } + return false; + } } internal class Death : RoleBase { From 0944441e87e2cb97671505eea570d9815b092762 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:59:35 +0200 Subject: [PATCH 256/778] complete --- Modules/GameState.cs | 1 + Resources/Lang/en_US.json | 5 ++ Roles/Impostor/YinYanger.cs | 99 +++++++++++++++++++++++++++++++++++++ main.cs | 1 + 4 files changed, 106 insertions(+) create mode 100644 Roles/Impostor/YinYanger.cs diff --git a/Modules/GameState.cs b/Modules/GameState.cs index deb65b33d5..2585734164 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -283,6 +283,7 @@ public enum DeathReason Trap, Targeted, Retribution, + Equilibrium, Slice, BloodLet, WrongAnswer, diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 18634476c1..e763381eeb 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -145,6 +145,7 @@ "Saboteur": "Saboteur", "Councillor": "Councillor", "Dazzler": "Dazzler", + "YinYanger": "YinYanger", "Deathpact": "Deathpact", "Devourer": "Devourer", "Consigliere": "Consigliere", @@ -489,6 +490,7 @@ "TimeManagerInfo": "Increase meeting time by doing tasks", "VeteranInfo": "Alert to kill anyone who interacts with you", "BastionInfo": "Bomb vents", + "YinYangerInfo": "Spontaneously combust 2 players", "BodyguardInfo": "Prevent nearby kills", "DeceiverInfo": "Try to fool the players", "GrenadierInfo": "Reduce Impostors' vision by venting", @@ -713,6 +715,7 @@ "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", + "YinYangerInfoLong": "(Impostors):\nAs the YinYanger, you can use your kill button once to pick your yin, and a secon time to choose a yang. When those 2 players meet, they'll kill each-other. When Yin & Yang have been chosen you can kill normally.'", "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", @@ -1907,6 +1910,7 @@ "DeathReason.BloodLet": "Bleed", "DeathReason.Armageddon": "Armageddon", "DeathReason.Starved": "Starved", + "DeathReason.Equilibrium": "Equilibrium", "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", "Alive": "Alive", "Disconnected": "Disconnected", @@ -2134,6 +2138,7 @@ "PacifistMaxUsage": "Ability use limit reached", "PacifistSkillNotify": "Pacifist reset your kill cooldown", "BeRecruitedByJackal": "The Jackal has recruited you", + "YinYangerAlreadyMarked": "{0} is already in a state of calm, endowed by a fellow YinYanger", "CoronerTrackRecorded": "Track recorded", "CoronerNoTrack": "Nothing to track", "CoronerIsTrackingYou": "The Coroner is tracking you!", diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs new file mode 100644 index 0000000000..8010cebb47 --- /dev/null +++ b/Roles/Impostor/YinYanger.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static TOHE.Utils; +using UnityEngine; +using static TOHE.Options; +using static TOHE.Translator; +using TOHE.Roles.Core; + +namespace TOHE.Roles.Impostor +{ + internal class YinYanger : RoleBase + { + //===========================SETUP================================\\ + const int Id = 29200; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.YinYanger); + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorKilling; + //==================================================================\\ + + + public static OptionItem KillCooldown; + + + public static Dictionary Yanged = []; + + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.YinYanger); + KillCooldown = FloatOptionItem.Create(Id + 2, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + Yanged.Clear(); + } + public override void Add(byte playerId) + { + Yanged[playerId] = new(); + } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + var (yin, yang) = Yanged[killer.PlayerId]; + if (yin && yang) return true; + if (Yanged.Where(x => x.Key != killer.PlayerId).Any(x => x.Value.yin == target || x.Value.yang == target)) + { + killer.Notify(string.Format(GetString("YinYangerAlreadyMarked"), target.GetRealName(clientData: true))); + return false; + } + + if (yin) + { + if (target.PlayerId == yin.PlayerId) + return false; + + Yanged[killer.PlayerId] = (yin, target); + + } + else + { + Yanged[killer.PlayerId] = (target, yang); + } + + killer.SetKillCooldown(); + return false; + } + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + Yanged[_state.PlayerId] = new(); + } + + public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) + { + var (yin, yang) = Yanged[seer.PlayerId]; + Color col = seen.PlayerId == yin?.PlayerId ? Color.white : new Color32(46, 46, 46, 255); + + return seen.PlayerId == yin?.PlayerId || seen.PlayerId == yang?.PlayerId ? ColorString(col, "☯") : string.Empty; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + var (yin, yang) = Yanged[pc.PlayerId]; + if (!yin || !yang) return; + + if (Vector2.Distance(yin.GetCustomPosition(), yang.GetCustomPosition()) < 1.5f) + { + yin.SetDeathReason(PlayerState.DeathReason.Equilibrium); + yin.RpcMurderPlayer(yang); + + yang.SetDeathReason(PlayerState.DeathReason.Equilibrium); + yang.RpcMurderPlayer(yin); + Yanged[pc.PlayerId] = new(); + } + } + + } +} diff --git a/main.cs b/main.cs index d209d1132d..2b945fe37e 100644 --- a/main.cs +++ b/main.cs @@ -641,6 +641,7 @@ public enum CustomRoles Sniper, SoulCatcher, Stealth, + YinYanger, Swooper, TimeThief, Trapster, From d6fee53f62f9b25f66edaf13ce9745d62e3f8576 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:02:08 +0200 Subject: [PATCH 257/778] fix grammar mistakes --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e763381eeb..1bae34168a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -715,7 +715,7 @@ "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", - "YinYangerInfoLong": "(Impostors):\nAs the YinYanger, you can use your kill button once to pick your yin, and a secon time to choose a yang. When those 2 players meet, they'll kill each-other. When Yin & Yang have been chosen you can kill normally.'", + "YinYangerInfoLong": "(Impostors):\nAs the YinYanger, you can use your kill button once to pick your Yin, and a second time to choose a Yang. When those 2 players meet, they'll kill each-other. When Yin & Yang have been chosen you can kill normally.", "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", From 42f09e5dcb3199432327b3b6c50883461f7ececc Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:03:10 +0200 Subject: [PATCH 258/778] fix --- Roles/Impostor/YinYanger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 8010cebb47..2045b397b5 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -63,7 +63,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Yanged[killer.PlayerId] = (target, yang); } - killer.SetKillCooldown(); + killer.ResetKillCooldown(); return false; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) From 9ec49161f2e4e724a20fb58d4066640e6abe7412 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:08:56 +0200 Subject: [PATCH 259/778] just incase --- Roles/Impostor/YinYanger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 2045b397b5..353c74ec44 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -43,7 +43,7 @@ public override void Add(byte playerId) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { var (yin, yang) = Yanged[killer.PlayerId]; - if (yin && yang) return true; + if (yin && yang || Main.AllAlivePlayerControls.Length <= 2) return true; if (Yanged.Where(x => x.Key != killer.PlayerId).Any(x => x.Value.yin == target || x.Value.yang == target)) { killer.Notify(string.Format(GetString("YinYangerAlreadyMarked"), target.GetRealName(clientData: true))); @@ -63,7 +63,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Yanged[killer.PlayerId] = (target, yang); } - killer.ResetKillCooldown(); + killer.SetKillCooldown(); return false; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) From d245a5c85406410c203681e730af01588ef12db0 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:34:49 +0200 Subject: [PATCH 260/778] fix some potential issue with multiple yinyangers --- Roles/Impostor/YinYanger.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 353c74ec44..2dd00201d0 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -18,10 +18,7 @@ internal class YinYanger : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorKilling; //==================================================================\\ - public static OptionItem KillCooldown; - - public static Dictionary Yanged = []; public override void SetupCustomOption() @@ -30,7 +27,6 @@ public override void SetupCustomOption() KillCooldown = FloatOptionItem.Create(Id + 2, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) .SetValueFormat(OptionFormat.Seconds); } - public override void Init() { Yanged.Clear(); @@ -40,10 +36,15 @@ public override void Add(byte playerId) Yanged[playerId] = new(); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + private static bool CheckAvailability() { + var tocheck = Main.AllAlivePlayerControls.Length - Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count(); + var result = 1 + (Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count() * 2); + return tocheck >= result; + } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { var (yin, yang) = Yanged[killer.PlayerId]; - if (yin && yang || Main.AllAlivePlayerControls.Length <= 2) return true; + if (yin && yang || Main.AllAlivePlayerControls.Length <= 2 || !CheckAvailability()) return true; if (Yanged.Where(x => x.Key != killer.PlayerId).Any(x => x.Value.yin == target || x.Value.yang == target)) { killer.Notify(string.Format(GetString("YinYangerAlreadyMarked"), target.GetRealName(clientData: true))); @@ -63,14 +64,18 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Yanged[killer.PlayerId] = (target, yang); } - killer.SetKillCooldown(); + killer.ResetKillCooldown(); return false; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { Yanged[_state.PlayerId] = new(); } - + public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + { + if (Yanged.TryGetValue(DeadPlayer.PlayerId, out _)) + Yanged[DeadPlayer.PlayerId] = new(); + } public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) { var (yin, yang) = Yanged[seer.PlayerId]; @@ -78,7 +83,6 @@ public override string GetMark(PlayerControl seer, PlayerControl seen, bool isFo return seen.PlayerId == yin?.PlayerId || seen.PlayerId == yang?.PlayerId ? ColorString(col, "☯") : string.Empty; } - public override void OnFixedUpdate(PlayerControl pc) { var (yin, yang) = Yanged[pc.PlayerId]; From d806a8b18757de639ae3e89477d5403306d8d002 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:35:53 +0200 Subject: [PATCH 261/778] bruh I already removed them --- Roles/Impostor/YinYanger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 2dd00201d0..eddb578f2d 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -38,7 +38,7 @@ public override void Add(byte playerId) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); private static bool CheckAvailability() { var tocheck = Main.AllAlivePlayerControls.Length - Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count(); - var result = 1 + (Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count() * 2); + var result = (Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count() * 2); return tocheck >= result; } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) From 69a382afb475f33caa852f6d367992a95e3b4579 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:39:02 +0200 Subject: [PATCH 262/778] bruh --- Roles/Impostor/YinYanger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index eddb578f2d..6f798a2eee 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -64,7 +64,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Yanged[killer.PlayerId] = (target, yang); } - killer.ResetKillCooldown(); + killer.SetKillCooldown(); return false; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) From 6d02b90464ac6fdb5a2f1b3a472b503804840f48 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:46:25 +0200 Subject: [PATCH 263/778] no need anymore --- Roles/Impostor/YinYanger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 6f798a2eee..e7fc076f9e 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -44,7 +44,7 @@ private static bool CheckAvailability() { public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { var (yin, yang) = Yanged[killer.PlayerId]; - if (yin && yang || Main.AllAlivePlayerControls.Length <= 2 || !CheckAvailability()) return true; + if (yin && yang || !CheckAvailability()) return true; if (Yanged.Where(x => x.Key != killer.PlayerId).Any(x => x.Value.yin == target || x.Value.yang == target)) { killer.Notify(string.Format(GetString("YinYangerAlreadyMarked"), target.GetRealName(clientData: true))); From c1b5e5000af7e2dd6ceb7e976308b1df77618209 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 05:27:12 +0800 Subject: [PATCH 264/778] IsDesyncRole --- Modules/Utils.cs | 2 +- Patches/ChatBubblePatch.cs | 2 +- Roles/Core/RoleBase.cs | 5 +++-- Roles/Neutral/Shroud.cs | 4 +--- Roles/Neutral/Sidekick.cs | 5 +---- Roles/Neutral/Spiritcaller.cs | 4 +--- Roles/Neutral/Werewolf.cs | 5 +---- Roles/Neutral/Wraith.cs | 6 +----- main.cs | 1 - 9 files changed, 10 insertions(+), 24 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index d302ff6c46..1f182b10e8 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1823,7 +1823,7 @@ static int GetInfoSize(string RoleInfo) var seerRoleClass = seer.GetRoleClass(); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && seerRoleClass.IsDesyncRole) { seer.RpcSetNamePrivate("", force: NoCache); } diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 8e0015cbad..1040e4909b 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -26,7 +26,7 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe var seerRoleClass = seer.GetRoleClass(); // if based role is Shapeshifter and is Desync Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seerRoleClass.IsDesyncRole) { // When target is impostor, set name color as white __instance.NameText.color = Color.white; diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 51e10c2be3..36529b1916 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -16,6 +16,9 @@ public abstract class RoleBase public float AbilityLimit { get; set; } = -100; public virtual bool IsEnable { get; set; } = false; + public virtual bool IsExperimental => false; + public virtual bool IsDesyncRole => false; + public void OnInit() // CustomRoleManager.RoleClass executes this { IsEnable = false; @@ -97,8 +100,6 @@ public virtual void Remove(byte playerId) public virtual void SetupCustomOption() { } - public virtual bool IsExperimental => false; - /// /// A generic method to send a CustomRole's Gameoptions. /// diff --git a/Roles/Neutral/Shroud.cs b/Roles/Neutral/Shroud.cs index f1cbecfe11..727fa07ee7 100644 --- a/Roles/Neutral/Shroud.cs +++ b/Roles/Neutral/Shroud.cs @@ -15,6 +15,7 @@ internal class Shroud : RoleBase //===========================SETUP================================\\ private const int Id = 18000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Shroud); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -40,9 +41,6 @@ public override void Init() public override void Add(byte playerId) { CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte shroudId, byte targetId, byte typeId) { diff --git a/Roles/Neutral/Sidekick.cs b/Roles/Neutral/Sidekick.cs index 672a9a710b..d20543bb0c 100644 --- a/Roles/Neutral/Sidekick.cs +++ b/Roles/Neutral/Sidekick.cs @@ -6,7 +6,7 @@ internal class Sidekick : RoleBase { private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; @@ -17,9 +17,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Jackal.KillCooldownSK.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte ico) => opt.SetVision(Jackal.HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Spiritcaller.cs b/Roles/Neutral/Spiritcaller.cs index cd5484fe49..122fc166e4 100644 --- a/Roles/Neutral/Spiritcaller.cs +++ b/Roles/Neutral/Spiritcaller.cs @@ -12,6 +12,7 @@ internal class Spiritcaller : RoleBase //===========================SETUP================================\\ private const int Id = 25200; public static bool HasEnabled = CustomRoleManager.HasEnabled(CustomRoles.Spiritcaller); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -60,9 +61,6 @@ public override void Add(byte playerId) AbilityLimit = SpiritMax.GetInt(); ProtectTimeStamp = 0; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); diff --git a/Roles/Neutral/Werewolf.cs b/Roles/Neutral/Werewolf.cs index b0909118f6..9637b7c07a 100644 --- a/Roles/Neutral/Werewolf.cs +++ b/Roles/Neutral/Werewolf.cs @@ -11,7 +11,7 @@ internal class Werewolf : RoleBase private const int Id = 18400; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -39,9 +39,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Wraith.cs b/Roles/Neutral/Wraith.cs index 62b6211d21..415295c7be 100644 --- a/Roles/Neutral/Wraith.cs +++ b/Roles/Neutral/Wraith.cs @@ -14,6 +14,7 @@ internal class Wraith : RoleBase //===========================SETUP================================\\ private const int Id = 18500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Wraith); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -45,11 +46,6 @@ public override void Init() lastTime.Clear(); ventedId.Clear(); } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } private void SendRPC(PlayerControl pc) { if (pc.AmOwner) return; diff --git a/main.cs b/main.cs index 70e748465b..d380fe12fe 100644 --- a/main.cs +++ b/main.cs @@ -132,7 +132,6 @@ public class Main : BasePlugin public static float RefixCooldownDelay = 0f; public static NetworkedPlayerInfo LastVotedPlayerInfo; public static string LastVotedPlayer; - public static readonly HashSet ResetCamPlayerList = []; public static readonly HashSet winnerList = []; public static readonly HashSet winnerNameList = []; public static readonly HashSet clientIdList = []; From d5c0f6d6b9249d41973cb129970a41b79f480c4d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 13:35:42 +0800 Subject: [PATCH 265/778] Has Desync Role --- Modules/Utils.cs | 6 +++--- Patches/ChatBubblePatch.cs | 2 +- Patches/ExilePatch.cs | 4 ++-- Patches/IntroPatch.cs | 2 +- Patches/MeetingHudPatch.cs | 2 +- Patches/OneWayShadowsPatch.cs | 6 ++++-- Patches/PlayerControlPatch.cs | 6 +++--- Patches/SabotageSystemPatch.cs | 18 ++++++++++++------ Patches/onGameStartedPatch.cs | 11 ++--------- Roles/Core/CustomRoleManager.cs | 2 ++ Roles/Core/RoleBase.cs | 6 ++++++ Roles/Crewmate/Admirer.cs | 4 +--- Roles/Crewmate/CopyCat.cs | 5 +---- Roles/Crewmate/Crusader.cs | 4 +--- Roles/Crewmate/Deceiver.cs | 4 +--- Roles/Crewmate/Deputy.cs | 6 ++---- Roles/Crewmate/Investigator.cs | 5 +---- Roles/Crewmate/Jailer.cs | 5 +---- Roles/Crewmate/Knight.cs | 4 +--- Roles/Crewmate/Medic.cs | 4 +--- Roles/Crewmate/Monarch.cs | 4 +--- Roles/Crewmate/Overseer.cs | 5 +---- Roles/Crewmate/Reverie.cs | 5 +---- Roles/Crewmate/Sheriff.cs | 4 +--- Roles/Crewmate/Vigilante.cs | 5 +---- Roles/Crewmate/Witness.cs | 5 +---- Roles/Neutral/Agitater.cs | 5 +---- Roles/Neutral/Amnesiac.cs | 5 +---- Roles/Neutral/Arsonist.cs | 5 +---- Roles/Neutral/Baker.cs | 9 ++------- Roles/Neutral/Bandit.cs | 4 +--- Roles/Neutral/Berserker.cs | 11 ++--------- Roles/Neutral/BloodKnight.cs | 4 +--- Roles/Neutral/Cultist.cs | 4 +--- Roles/Neutral/CursedSoul.cs | 4 +--- Roles/Neutral/Demon.cs | 4 +--- Roles/Neutral/Doppelganger.cs | 5 +---- Roles/Neutral/Follower.cs | 5 +---- Roles/Neutral/Glitch.cs | 6 ++---- Roles/Neutral/Hater.cs | 4 +--- Roles/Neutral/HexMaster.cs | 5 +---- Roles/Neutral/Huntsman.cs | 7 ++----- Roles/Neutral/Imitator.cs | 4 +--- Roles/Neutral/Infectious.cs | 5 +---- Roles/Neutral/Innocent.cs | 5 +---- Roles/Neutral/Jackal.cs | 7 +------ Roles/Neutral/Jinx.cs | 4 +--- Roles/Neutral/Juggernaut.cs | 5 +---- Roles/Neutral/Maverick.cs | 5 +---- Roles/Neutral/Medusa.cs | 5 +---- Roles/Neutral/Necromancer.cs | 5 +---- Roles/Neutral/Pelican.cs | 6 +----- Roles/Neutral/Pickpocket.cs | 7 +------ Roles/Neutral/Pirate.cs | 4 +--- Roles/Neutral/Pixie.cs | 4 +--- Roles/Neutral/PlagueBearer.cs | 12 +++--------- Roles/Neutral/PlagueDoctor.cs | 4 +--- Roles/Neutral/Poisoner.cs | 6 ++---- Roles/Neutral/PotionMaster.cs | 4 +--- Roles/Neutral/Pursuer.cs | 4 +--- Roles/Neutral/Pyromaniac.cs | 5 +---- Roles/Neutral/Revolutionist.cs | 6 ++---- Roles/Neutral/Romantic.cs | 15 ++++----------- Roles/Neutral/Seeker.cs | 4 +--- Roles/Neutral/SerialKiller.cs | 6 ++---- Roles/Neutral/Shaman.cs | 6 +----- Roles/Neutral/SoulCollector.cs | 11 ++--------- Roles/Neutral/Stalker.cs | 5 +---- Roles/Neutral/Traitor.cs | 6 ++---- Roles/Neutral/Virus.cs | 4 +--- main.cs | 1 + 71 files changed, 112 insertions(+), 274 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 01fdc6c96b..19cc9bedb1 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1784,7 +1784,7 @@ static int GetInfoSize(string RoleInfo) string RoleInfo = $"\n{Font}{ColorString(seer.GetRoleColor(), seer.GetRoleInfo())}"; string RoleNameUp = "\n\n"; - if (!seer.GetCustomRole().IsDesyncRole()) + if (!seer.HasDesyncRole()) { SelfTeamName = string.Empty; RoleNameUp = "\n"; @@ -1822,7 +1822,7 @@ static int GetInfoSize(string RoleInfo) var seerRoleClass = seer.GetRoleClass(); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && seerRoleClass.IsDesyncRole) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && seer.HasDesyncRole()) { seer.RpcSetNamePrivate("", force: NoCache); } @@ -1973,7 +1973,7 @@ static int GetInfoSize(string RoleInfo) //logger.Info("NotifyRoles-Loop2-" + target.GetNameWithRole() + ":START"); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && target.IsAlive() && !seer.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && target.IsAlive() && !seer.Is(Custom_Team.Impostor) && seer.HasDesyncRole()) { realTarget.RpcSetNamePrivate("", seer, force: NoCache); } diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 2023b4f553..d3bc056f4e 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -27,7 +27,7 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe var seerRoleClass = seer.GetRoleClass(); // if based role is Shapeshifter and is Desync Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seerRoleClass.IsDesyncRole) + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seer.HasDesyncRole()) { // When target is impostor, set name color as white __instance.NameText.color = Color.white; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index dff8b1ad59..d9093dc6df 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -73,7 +73,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (CLThingy && exiled != null) { // Reset player cam for exiled desync impostor - if (Main.ResetCamPlayerList.Contains(exiled.PlayerId)) + if (exiled.Object.HasDesyncRole()) { exiled.Object?.ResetPlayerCam(1f); } @@ -181,7 +181,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) player?.SetRealKiller(player, true); // Reset player cam for dead desync impostor - if (Main.ResetCamPlayerList.Contains(x.Key)) + if (player.HasDesyncRole()) { player?.ResetPlayerCam(1f); } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d1749c421d..4c7fe4d0e5 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -607,7 +607,7 @@ public static void Postfix() if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); } - var amDesyncImpostor = Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId); + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); if (amDesyncImpostor) { PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9795ab0eea..764f2f5b0e 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1083,7 +1083,7 @@ public static void Postfix(MeetingHud __instance) if (target == null) continue; // if based role is Shapeshifter and is Desync Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seer.HasDesyncRole()) { // When target is impostor, set name color as white target.cosmetics.SetNameColor(Color.white); diff --git a/Patches/OneWayShadowsPatch.cs b/Patches/OneWayShadowsPatch.cs index a07ecc03b0..fe23a78b6c 100644 --- a/Patches/OneWayShadowsPatch.cs +++ b/Patches/OneWayShadowsPatch.cs @@ -1,11 +1,13 @@ -namespace TOHE; +using TOHE.Roles.Core; + +namespace TOHE; [HarmonyPatch(typeof(OneWayShadows), nameof(OneWayShadows.IsIgnored))] public static class OneWayShadowsIsIgnoredPatch { public static bool Prefix(OneWayShadows __instance, ref bool __result) { - var amDesyncImpostor = Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId); + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); if (__instance.IgnoreImpostor && amDesyncImpostor) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 23a13f137a..6cf8f9395d 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1778,7 +1778,7 @@ public static void Postfix(PlayerControl __instance) // if player is Desync Impostor and the vanilla sees player as Imposter, the vanilla process does not hide your name, so the other person's name is hidden if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && // Impostor with vanilla !PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) && // Not an Impostor - Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId)) // Desync Impostor + PlayerControl.LocalPlayer.HasDesyncRole()) // Desync Impostor { // Hide names __instance.cosmetics.ToggleNameVisible(false); @@ -1894,13 +1894,13 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol Logger.Warn($"Error After RpcSetRole: {error}", "RpcSetRole.Prefix.GhostAssignPatch"); } - var targetIsKiller = target.Is(Custom_Team.Impostor) || Main.ResetCamPlayerList.Contains(target.PlayerId); + var targetIsKiller = target.Is(Custom_Team.Impostor) || target.HasDesyncRole(); ghostRoles.Clear(); foreach (var seer in Main.AllPlayerControls) { var self = seer.PlayerId == target.PlayerId; - var seerIsKiller = seer.Is(Custom_Team.Impostor) || Main.ResetCamPlayerList.Contains(seer.PlayerId); + var seerIsKiller = seer.Is(Custom_Team.Impostor) || seer.HasDesyncRole(); if (target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole())) { diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 6fa0f39404..2552d7f462 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -127,10 +127,13 @@ public static void Postfix() { Logger.Info($" IsActive", "MushroomMixupSabotageSystem.UpdateSystem.Postfix"); - foreach (var pc in Main.AllAlivePlayerControls.Where(player => !player.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(player.PlayerId)).ToArray()) + foreach (var pc in Main.AllAlivePlayerControls) { - // Need for hiding player names if player is desync Impostor - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixupIsActive: true); + if (!pc.Is(Custom_Team.Impostor) && pc.HasDesyncRole()) + { + // Need for hiding player names if player is desync Impostor + Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixupIsActive: true); + } } } } @@ -182,10 +185,13 @@ public static void Postfix(MushroomMixupSabotageSystem __instance, bool __state) } }, 1.2f, "Reset Ability Cooldown Arter Mushroom Mixup"); - foreach (var pc in Main.AllAlivePlayerControls.Where(player => !player.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(player.PlayerId)).ToArray()) + foreach (var pc in Main.AllAlivePlayerControls) { - // Need for display player names if player is desync Impostor - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true); + if (!pc.Is(Custom_Team.Impostor) && pc.HasDesyncRole()) + { + // Need for display player names if player is desync Impostor + Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true); + } } } } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index a5e4859593..43af24cb92 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -59,10 +59,10 @@ public static void Postfix(AmongUsClient __instance) Main.LastEnteredVent.Clear(); Main.LastEnteredVentLocation.Clear(); + Main.DesyncPlayerList.Clear(); Main.PlayersDiedInMeeting.Clear(); GuessManager.GuesserGuessed.Clear(); Main.AfterMeetingDeathPlayers.Clear(); - Main.ResetCamPlayerList.Clear(); Main.clientIdList.Clear(); PlayerControlSetRolePatch.DidSetGhost.Clear(); @@ -503,15 +503,13 @@ private static void SetRolesAfterSelect() var roleClass = pc.GetRoleClass(); - if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - roleClass?.OnAdd(pc.PlayerId); // if based role is Shapeshifter if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) { // Is Desync Shapeshifter - if (Main.ResetCamPlayerList.Contains(pc.PlayerId)) + if (pc.HasDesyncRole()) { foreach (var target in Main.AllPlayerControls) { @@ -615,11 +613,6 @@ private static void SetRolesAfterSelect() break; case CustomGameMode.FFA: GameEndCheckerForNormal.SetPredicateToFFA(); - - // Added players in reset cam - Main.ResetCamPlayerList.UnionWith(Main.AllPlayerControls - .Where(pc => pc.GetCustomRole() is CustomRoles.Killer) - .Select(pc => pc.PlayerId)); break; } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 34d6448981..1ec0670f3c 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -67,6 +67,8 @@ public static RoleBase CreateRoleClass(this CustomRoles role) return (RoleBase)Activator.CreateInstance(role.GetStaticRoleClass().GetType()); // Converts this.RoleBase back to its type and creates an unique one. } + public static bool HasDesyncRole(this PlayerControl player) => player != null && (player.GetRoleClass().IsDesyncRole || Main.DesyncPlayerList.Contains(player.Data.PlayerId)); + /// /// If the role protect others players /// diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 5adbb11173..89d0cb8396 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -39,6 +39,12 @@ public void OnAdd(byte playerid) // The player with the class executes this { CustomRoleManager.Add(); } + + // Remember desync player so that when changing role he will still be as desync + if (IsDesyncRole) + { + Main.DesyncPlayerList.Add(playerid); + } } public void OnRemove(byte playerId) { diff --git a/Roles/Crewmate/Admirer.cs b/Roles/Crewmate/Admirer.cs index d02dffa305..a8419d6049 100644 --- a/Roles/Crewmate/Admirer.cs +++ b/Roles/Crewmate/Admirer.cs @@ -15,6 +15,7 @@ internal class Admirer : RoleBase //===========================SETUP================================\\ private const int Id = 24800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Admired); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -42,9 +43,6 @@ public override void Add(byte playerId) { AbilityLimit = SkillLimit.GetInt(); AdmiredList.Add(playerId, []); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index abcf0f27bc..912c53a008 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -11,6 +11,7 @@ internal class CopyCat : RoleBase private const int Id = 11500; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -40,9 +41,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); CurrentKillCooldown = KillCooldown.GetFloat(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) //only to be used when copycat's role is going to be changed permanently { @@ -171,7 +169,6 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } if (role.IsCrewmate()) { - if (role != CustomRoles.CopyCat) { killer.RpcSetCustomRole(role); diff --git a/Roles/Crewmate/Crusader.cs b/Roles/Crewmate/Crusader.cs index 7ed986c2be..67e0d0eb0b 100644 --- a/Roles/Crewmate/Crusader.cs +++ b/Roles/Crewmate/Crusader.cs @@ -10,6 +10,7 @@ internal class Crusader : RoleBase //===========================SETUP================================\\ private const int Id = 10400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Crusader); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -32,9 +33,6 @@ public override void Add(byte playerId) { AbilityLimit = SkillLimitOpt.GetInt(); CurrentKillCooldown = SkillCooldown.GetFloat(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? CurrentKillCooldown : 300f; diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index 743b164f44..a7948ae48d 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -11,6 +11,7 @@ internal class Deceiver : RoleBase //===========================SETUP================================\\ private const int Id = 10500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Deceiver); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -34,9 +35,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = DeceiverSkillLimitTimes.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); public override bool CanUseKillButton(PlayerControl pc) diff --git a/Roles/Crewmate/Deputy.cs b/Roles/Crewmate/Deputy.cs index 82a50a769f..2e9e0b2b57 100644 --- a/Roles/Crewmate/Deputy.cs +++ b/Roles/Crewmate/Deputy.cs @@ -9,6 +9,7 @@ internal class Deputy : RoleBase { //===========================SETUP================================\\ private const int Id = 7800; + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -19,7 +20,7 @@ internal class Deputy : RoleBase public override void SetupCustomOption() { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deputy); + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deputy); HandcuffCooldown = FloatOptionItem.Create(Id + 10, "DeputyHandcuffCooldown", new(0f, 180f, 2.5f), 10f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) .SetValueFormat(OptionFormat.Seconds); DeputyHandcuffCDForTarget = FloatOptionItem.Create(Id + 14, "DeputyHandcuffCDForTarget", new(0f, 180f, 2.5f), 45f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) @@ -30,9 +31,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = HandcuffMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = HandcuffCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && AbilityLimit >= 1; diff --git a/Roles/Crewmate/Investigator.cs b/Roles/Crewmate/Investigator.cs index e484519573..b87674b9d4 100644 --- a/Roles/Crewmate/Investigator.cs +++ b/Roles/Crewmate/Investigator.cs @@ -11,7 +11,7 @@ internal class Investigator : RoleBase private const int Id = 24900; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -48,9 +48,6 @@ public override void Add(byte playerId) MaxInvestigateLimit[playerId] = InvestigateMax.GetInt(); RoundInvestigateLimit[playerId] = InvestigateRoundMax.GetInt(); InvestigatedList[playerId] = []; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index d5bdf7ef57..809a5e1bd9 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -12,7 +12,7 @@ internal class Jailer : RoleBase private const int Id = 10600; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -63,9 +63,6 @@ public override void Add(byte playerId) JailerTarget.Add(playerId, byte.MaxValue); JailerHasExe.Add(playerId, false); JailerDidVote.Add(playerId, false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Knight.cs b/Roles/Crewmate/Knight.cs index c7fb740f95..b9f5726651 100644 --- a/Roles/Crewmate/Knight.cs +++ b/Roles/Crewmate/Knight.cs @@ -9,6 +9,7 @@ internal class Knight : RoleBase //===========================SETUP================================\\ private const int Id = 10800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Knight); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -26,9 +27,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = 1; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); diff --git a/Roles/Crewmate/Medic.cs b/Roles/Crewmate/Medic.cs index c5c94c43a1..8aa9e48fa3 100644 --- a/Roles/Crewmate/Medic.cs +++ b/Roles/Crewmate/Medic.cs @@ -14,6 +14,7 @@ internal class Medic : RoleBase //===========================SETUP================================\\ private const int Id = 8600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Medic); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -69,9 +70,6 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = 1; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SendRPCForProtectList() { diff --git a/Roles/Crewmate/Monarch.cs b/Roles/Crewmate/Monarch.cs index 7967d0be01..4bc0d8e989 100644 --- a/Roles/Crewmate/Monarch.cs +++ b/Roles/Crewmate/Monarch.cs @@ -12,6 +12,7 @@ internal class Monarch : RoleBase //===========================SETUP================================\\ private const int Id = 12100; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Monarch); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -35,9 +36,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = KnightMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = AbilityLimit > 0 ? KnightCooldown.GetFloat() : 300f; diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 6d92b5879c..0ce4a96d33 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -16,7 +16,7 @@ internal class Overseer : RoleBase private const int Id = 12200; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -102,9 +102,6 @@ public override void Add(byte playerId) } RandomRole.Add(playerId, GetRandomCrewRoleString()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Reverie.cs b/Roles/Crewmate/Reverie.cs index 5a95e5086a..ad8b5c428c 100644 --- a/Roles/Crewmate/Reverie.cs +++ b/Roles/Crewmate/Reverie.cs @@ -10,7 +10,7 @@ internal class Reverie : RoleBase private const int Id = 11100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -52,9 +52,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); NowCooldown.TryAdd(playerId, DefaultKillCooldown.GetFloat()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index bceb42d702..f826955729 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -10,6 +10,7 @@ internal class Sheriff : RoleBase //===========================SETUP================================\\ private const int Id = 11200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sheriff); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -76,9 +77,6 @@ public override void Add(byte playerId) AbilityLimit = ShotLimitOpt.GetInt(); Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole()} : limit: {AbilityLimit}", "Sheriff"); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SetUpNeutralOptions(int Id) { diff --git a/Roles/Crewmate/Vigilante.cs b/Roles/Crewmate/Vigilante.cs index 6adf329fea..f36691c3bd 100644 --- a/Roles/Crewmate/Vigilante.cs +++ b/Roles/Crewmate/Vigilante.cs @@ -9,7 +9,7 @@ internal class Vigilante : RoleBase private const int Id = 11400; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -30,9 +30,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = VigilanteKillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index 69a648f69c..63d898eb88 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -12,7 +12,7 @@ internal class Witness : RoleBase private const int Id = 10100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -36,9 +36,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateLowLoadOthers); diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index 138226afbb..a0182edbab 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -12,7 +12,7 @@ internal class Agitater : RoleBase private const int Id = 15800; private static readonly List playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -57,9 +57,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public void ResetBomb() diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index bcc42e9f9a..9c1fb850e6 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -13,7 +13,7 @@ internal class Amnesiac : RoleBase private const int Id = 12700; private static readonly HashSet playerIdList = []; public static bool HasEnabled = playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -47,9 +47,6 @@ public override void Add(byte playerId) playerIdList.Add(playerId); CanUseVent[playerId] = true; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (!AmongUsClient.Instance.AmHost) return; if (ShowArrows.GetBool()) { diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index 07829ec6c6..d55f486e98 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -15,7 +15,7 @@ internal class Arsonist : RoleBase private const int id = 15900; private static readonly HashSet PlayerIds = []; public static bool HasEnabled = PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => CanIgniteAnytime() ? Custom_RoleType.NeutralKilling : Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -57,9 +57,6 @@ public override void Add(byte playerId) foreach (var ar in Main.AllPlayerControls) IsDoused.Add((playerId, ar.PlayerId), false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SendCurrentDousingTargetRPC(byte arsonistId, byte targetId) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index dec4dc0102..2709e69bed 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -15,7 +15,7 @@ internal class Baker : RoleBase public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -61,9 +61,6 @@ public override void Add(byte playerId) CanUseAbility = true; StarvedNonBreaded = false; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static (int, int) BreadedPlayerCount(byte playerId) @@ -308,14 +305,12 @@ internal class Famine : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Baker); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ public override void Add(byte playerId) { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs index d8c3ecbf6b..2347536bd1 100644 --- a/Roles/Neutral/Bandit.cs +++ b/Roles/Neutral/Bandit.cs @@ -13,6 +13,7 @@ internal class Bandit : RoleBase //===========================SETUP================================\\ private const int Id = 16000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Bandit); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -56,9 +57,6 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) { diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 72b093b8f9..77c07bcef4 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -15,7 +15,7 @@ internal class Berserker : RoleBase private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -81,9 +81,6 @@ public override void Add(byte playerId) { BerserkerKillMax[playerId] = 0; PlayerIds.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -181,15 +178,11 @@ internal class War : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Berserker); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) diff --git a/Roles/Neutral/BloodKnight.cs b/Roles/Neutral/BloodKnight.cs index 70fa3d7b97..004df0774f 100644 --- a/Roles/Neutral/BloodKnight.cs +++ b/Roles/Neutral/BloodKnight.cs @@ -13,6 +13,7 @@ internal class BloodKnight : RoleBase //===========================SETUP================================\\ private const int Id = 16100; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.BloodKnight); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -37,9 +38,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { TimeStamp = 0; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte playerId) { diff --git a/Roles/Neutral/Cultist.cs b/Roles/Neutral/Cultist.cs index dc27816403..c3c42027f0 100644 --- a/Roles/Neutral/Cultist.cs +++ b/Roles/Neutral/Cultist.cs @@ -12,6 +12,7 @@ internal class Cultist : RoleBase //===========================SETUP================================\\ private const int Id = 14800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Cultist); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -48,9 +49,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = CharmMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = AbilityLimit >= 1 ? CharmCooldown.GetFloat() + (CharmMax.GetInt() - AbilityLimit) * CharmCooldownIncrese.GetFloat() : 300f; public override bool CanUseKillButton(PlayerControl player) => AbilityLimit >= 1; diff --git a/Roles/Neutral/CursedSoul.cs b/Roles/Neutral/CursedSoul.cs index 84031aab58..d2f0bf09db 100644 --- a/Roles/Neutral/CursedSoul.cs +++ b/Roles/Neutral/CursedSoul.cs @@ -12,6 +12,7 @@ internal class CursedSoul : RoleBase private const int Id = 14000; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -45,9 +46,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); CurseLimit = CurseMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC() diff --git a/Roles/Neutral/Demon.cs b/Roles/Neutral/Demon.cs index 67b8cac98f..4cc16258e1 100644 --- a/Roles/Neutral/Demon.cs +++ b/Roles/Neutral/Demon.cs @@ -13,6 +13,7 @@ internal class Demon : RoleBase //===========================SETUP================================\\ private const int Id = 16200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Demon); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -55,9 +56,6 @@ public override void Add(byte playerId) foreach (var pc in Main.AllAlivePlayerControls) PlayerHealth[pc.PlayerId] = HealthMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index 747d718cc8..be713820a1 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -12,6 +12,7 @@ internal class Doppelganger : RoleBase private const int Id = 25000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Doppelganger); public override bool IsExperimental => true; + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -39,10 +40,6 @@ public override void Add(byte playerId) { AbilityLimit = MaxSteals.GetInt(); DoppelVictim[playerId] = Utils.GetPlayerById(playerId).GetRealName() ?? "Invalid"; - - if (!AmongUsClient.Instance.AmHost) return; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/Follower.cs b/Roles/Neutral/Follower.cs index 7ad48c1bf3..fe9a3ab0fc 100644 --- a/Roles/Neutral/Follower.cs +++ b/Roles/Neutral/Follower.cs @@ -15,7 +15,7 @@ internal class Follower : RoleBase private const int Id = 12800; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -54,9 +54,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); BetTimes.Add(playerId, MaxBetTimes.GetInt()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte playerId) { diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index 33c046b767..259ec63e55 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -13,7 +13,8 @@ internal class Glitch : RoleBase //===========================SETUP================================\\ private const int Id = 16300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Glitch); - public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -73,9 +74,6 @@ public override void Add(byte playerId) LastMimic = ts; lastRpcSend = ts; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - // Double Trigger var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index 0e0d4c1fc4..679e64bea6 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -10,6 +10,7 @@ internal class Hater : RoleBase private const int Id = 12900; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -51,9 +52,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index e90b63a8ef..ed105bb1c9 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -15,7 +15,7 @@ internal class HexMaster : RoleBase private const int Id = 16400; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -60,9 +60,6 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SendRPC(bool doHex, byte hexId, byte target = 255) diff --git a/Roles/Neutral/Huntsman.cs b/Roles/Neutral/Huntsman.cs index 6aac71eea8..eeffede1a2 100644 --- a/Roles/Neutral/Huntsman.cs +++ b/Roles/Neutral/Huntsman.cs @@ -13,6 +13,7 @@ internal class Huntsman : RoleBase //===========================SETUP================================\\ private const int Id = 16500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Huntsman); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -50,16 +51,12 @@ public override void SetupCustomOption() } public override void Add(byte playerId) { + KCD = KillCooldown.GetFloat(); _ = new LateTask(() => { ResetTargets(isStartedGame: true); }, 8f, "Huntsman Reset Targets"); - - KCD = KillCooldown.GetFloat(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public void SendRPC(bool isSetTarget, byte targetId = byte.MaxValue) diff --git a/Roles/Neutral/Imitator.cs b/Roles/Neutral/Imitator.cs index 735aad943f..2ff2325b35 100644 --- a/Roles/Neutral/Imitator.cs +++ b/Roles/Neutral/Imitator.cs @@ -10,6 +10,7 @@ internal class Imitator : RoleBase private const int Id = 13000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Imitator); public override bool IsExperimental => true; + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -36,9 +37,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = 1; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = RememberCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl player) => AbilityLimit > 0; diff --git a/Roles/Neutral/Infectious.cs b/Roles/Neutral/Infectious.cs index 70f78354d2..b0100fbd10 100644 --- a/Roles/Neutral/Infectious.cs +++ b/Roles/Neutral/Infectious.cs @@ -13,7 +13,7 @@ internal class Infectious : RoleBase private const int Id = 16600; private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -52,9 +52,6 @@ public override void Add(byte playerId) PlayerIds.Add(playerId); var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Innocent.cs b/Roles/Neutral/Innocent.cs index 51e3493192..a0a2058110 100644 --- a/Roles/Neutral/Innocent.cs +++ b/Roles/Neutral/Innocent.cs @@ -10,7 +10,7 @@ internal class Innocent : RoleBase private const int Id = 14300; private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -30,9 +30,6 @@ public override void Init() public override void Add(byte playerId) { PlayerIds.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index b468ca603c..12eed319ea 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -12,6 +12,7 @@ internal class Jackal : RoleBase //===========================SETUP================================\\ private const int Id = 16700; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jailer); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -83,9 +84,6 @@ public override void Add(byte playerId) { AbilityLimit = CanRecruitSidekick.GetBool() ? SidekickRecruitLimitOpt.GetInt() : 0; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.CheckDeadBodyOthers.Add(OthersPlayersDead); @@ -143,9 +141,6 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t target.RpcSetCustomRole(CustomRoles.Sidekick); target.GetRoleClass()?.OnAdd(target.PlayerId); - if (!Main.ResetCamPlayerList.Contains(target.PlayerId)) - Main.ResetCamPlayerList.Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); diff --git a/Roles/Neutral/Jinx.cs b/Roles/Neutral/Jinx.cs index 1c6903443b..2696e68278 100644 --- a/Roles/Neutral/Jinx.cs +++ b/Roles/Neutral/Jinx.cs @@ -10,6 +10,7 @@ internal class Jinx : RoleBase //===========================SETUP================================\\ private const int Id = 16800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jinx); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -36,9 +37,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = JinxSpellTimes.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { diff --git a/Roles/Neutral/Juggernaut.cs b/Roles/Neutral/Juggernaut.cs index 475c14e2a7..169c22fc50 100644 --- a/Roles/Neutral/Juggernaut.cs +++ b/Roles/Neutral/Juggernaut.cs @@ -10,7 +10,7 @@ internal class Juggernaut : RoleBase private const int Id = 16900; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -44,9 +44,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); NowCooldown.TryAdd(playerId, DefaultKillCooldown.GetFloat()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = NowCooldown[id]; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Neutral/Maverick.cs b/Roles/Neutral/Maverick.cs index 563164469b..ce2b86fc66 100644 --- a/Roles/Neutral/Maverick.cs +++ b/Roles/Neutral/Maverick.cs @@ -10,7 +10,7 @@ internal class Maverick : RoleBase //===========================SETUP================================\\ private const int Id = 13200; public static bool HasEnabled = CustomRoleManager.HasEnabled(CustomRoles.Maverick); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -35,9 +35,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { NumKills = 0; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); diff --git a/Roles/Neutral/Medusa.cs b/Roles/Neutral/Medusa.cs index 1afa9c2302..2770da576b 100644 --- a/Roles/Neutral/Medusa.cs +++ b/Roles/Neutral/Medusa.cs @@ -10,7 +10,7 @@ internal class Medusa : RoleBase private const int Id = 17000; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -37,9 +37,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Necromancer.cs b/Roles/Neutral/Necromancer.cs index 72eb78c392..9cd89ea423 100644 --- a/Roles/Neutral/Necromancer.cs +++ b/Roles/Neutral/Necromancer.cs @@ -10,7 +10,7 @@ internal class Necromancer : RoleBase private const int Id = 17100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -48,9 +48,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); Timer = RevengeTime.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index bc08202a2a..113dbad67c 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -16,6 +16,7 @@ internal class Pelican : RoleBase //===========================SETUP================================\\ private const int Id = 17300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pelican); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -46,11 +47,6 @@ public override void Init() Count = 0; } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override void Remove(byte playerId) { ReturnEatenPlayerBack(Utils.GetPlayerById(playerId)); diff --git a/Roles/Neutral/Pickpocket.cs b/Roles/Neutral/Pickpocket.cs index c02e13491a..494d905afd 100644 --- a/Roles/Neutral/Pickpocket.cs +++ b/Roles/Neutral/Pickpocket.cs @@ -10,7 +10,7 @@ internal class Pickpocket : RoleBase //===========================SETUP================================\\ private const int Id = 17400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pickpocket); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -35,11 +35,6 @@ public override void SetupCustomOption() HideAdditionalVotes = BooleanOptionItem.Create(Id + 14, GeneralOption.HideAdditionalVotes, false, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/Pirate.cs b/Roles/Neutral/Pirate.cs index ceb59b42aa..eccd1cf483 100644 --- a/Roles/Neutral/Pirate.cs +++ b/Roles/Neutral/Pirate.cs @@ -15,6 +15,7 @@ internal class Pirate : RoleBase //===========================SETUP================================\\ private const int Id = 15000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pirate); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -51,9 +52,6 @@ public override void Init() public override void Add(byte playerId) { DuelDone.Add(playerId, false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void MeetingHudClear() { diff --git a/Roles/Neutral/Pixie.cs b/Roles/Neutral/Pixie.cs index 01fceddb96..dd6afdaa0f 100644 --- a/Roles/Neutral/Pixie.cs +++ b/Roles/Neutral/Pixie.cs @@ -10,6 +10,7 @@ internal class Pixie : RoleBase //===========================SETUP================================\\ private const int Id = 25900; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pirate); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -43,9 +44,6 @@ public override void Add(byte playerId) { PixieTargets[playerId] = []; PixiePoints.Add(playerId, 0); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 86060d3c8e..41e6d50835 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -14,7 +14,8 @@ internal class PlagueBearer : RoleBase private const int Id = 17600; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -52,9 +53,6 @@ public override void Add(byte playerId) PlaguedList[playerId] = []; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -232,15 +230,11 @@ internal class Pestilence : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PlagueBearer); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index 675b6ac78e..e5b4ba9c74 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -15,6 +15,7 @@ internal class PlagueDoctor : RoleBase //===========================SETUP================================\\ private const int Id = 27600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PlagueDoctor); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -84,9 +85,6 @@ public override void Add(byte playerId) if (Main.NormalOptions.MapId == 4) InfectInactiveTime += 5f; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.OnFixedUpdateOthers.Add(OnCheckPlayerPosition); diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Neutral/Poisoner.cs index c244a7b8a8..8be8e2c5cf 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Neutral/Poisoner.cs @@ -16,7 +16,8 @@ private class PoisonedInfo(byte poisonerId, float killTimer) private const int Id = 17500; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -51,9 +52,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Neutral/PotionMaster.cs b/Roles/Neutral/PotionMaster.cs index c82e434d11..db55f619af 100644 --- a/Roles/Neutral/PotionMaster.cs +++ b/Roles/Neutral/PotionMaster.cs @@ -12,6 +12,7 @@ internal class PotionMaster : RoleBase //===========================SETUP================================\\ private const int Id = 17700; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PotionMaster); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -44,9 +45,6 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte playerId, byte targetId) diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index 9a77c49c05..7390a1b9b7 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -11,6 +11,7 @@ internal class Pursuer : RoleBase //===========================SETUP================================\\ private const int Id = 13400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pursuer); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -36,9 +37,6 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = PursuerSkillLimitTimes.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => CanUseKillButton(pc.PlayerId); diff --git a/Roles/Neutral/Pyromaniac.cs b/Roles/Neutral/Pyromaniac.cs index 6f3889636e..2cb963458c 100644 --- a/Roles/Neutral/Pyromaniac.cs +++ b/Roles/Neutral/Pyromaniac.cs @@ -10,7 +10,7 @@ internal class Pyromaniac : RoleBase private const int Id = 17800; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -47,9 +47,6 @@ public override void Add(byte playerId) // Double Trigger var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs index 4d7d2bd09c..fd5082514f 100644 --- a/Roles/Neutral/Revolutionist.cs +++ b/Roles/Neutral/Revolutionist.cs @@ -15,7 +15,8 @@ internal class Revolutionist : RoleBase private const int Id = 15200; private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -73,9 +74,6 @@ public override void Add(byte playerId) foreach (var ar in Main.AllPlayerControls) IsDraw.Add((playerId, ar.PlayerId), false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = RevolutionistCooldown.GetFloat(); diff --git a/Roles/Neutral/Romantic.cs b/Roles/Neutral/Romantic.cs index 1ec10d46f7..360c75d568 100644 --- a/Roles/Neutral/Romantic.cs +++ b/Roles/Neutral/Romantic.cs @@ -15,6 +15,7 @@ internal class Romantic : RoleBase //===========================SETUP================================\\ private const int Id = 13500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Romantic); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -68,9 +69,6 @@ public override void Add(byte playerId) BetTimes.Add(playerId, 1); CustomRoleManager.CheckDeadBodyOthers.Add(OthersAfterPlayerDeathTask); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -314,6 +312,7 @@ internal class VengefulRomantic : RoleBase //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Romantic); + public override bool IsDesyncRole => new Romantic().IsDesyncRole; public override CustomRoles ThisRoleBase => new Romantic().ThisRoleBase; public override Custom_RoleType ThisRoleType => new Romantic().ThisRoleType; //==================================================================\\ @@ -329,9 +328,6 @@ public override void Init() public override void Add(byte playerId) { VengefulTarget.Add(playerId, Romantic.VengefulTargetId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && !hasKilledKiller; @@ -381,11 +377,11 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) internal class RuthlessRomantic : RoleBase { - //===========================SETUP================================\\ private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => new Romantic().IsDesyncRole; public override CustomRoles ThisRoleBase => new Romantic().ThisRoleBase; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -396,9 +392,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Romantic.RuthlessKCD.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/Seeker.cs b/Roles/Neutral/Seeker.cs index 91570b5ffb..7095d3eee7 100644 --- a/Roles/Neutral/Seeker.cs +++ b/Roles/Neutral/Seeker.cs @@ -10,6 +10,7 @@ internal class Seeker : RoleBase //===========================SETUP================================\\ private const int Id = 14600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Seeker); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -43,9 +44,6 @@ public override void Add(byte playerId) DefaultSpeed[playerId] = Main.AllPlayerSpeed[playerId]; PointsToWinOpt = PointsToWin.GetInt(); - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) _ = new LateTask(() => { diff --git a/Roles/Neutral/SerialKiller.cs b/Roles/Neutral/SerialKiller.cs index 2ed045dc24..1ed578f989 100644 --- a/Roles/Neutral/SerialKiller.cs +++ b/Roles/Neutral/SerialKiller.cs @@ -9,7 +9,8 @@ internal class SerialKiller : RoleBase private const int Id = 17900; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -39,9 +40,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Shaman.cs b/Roles/Neutral/Shaman.cs index 7c575a1af2..e161e5737a 100644 --- a/Roles/Neutral/Shaman.cs +++ b/Roles/Neutral/Shaman.cs @@ -9,6 +9,7 @@ internal class Shaman : RoleBase //===========================SETUP================================\\ private const int Id = 13600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Shaman); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -30,11 +31,6 @@ public override void Init() ShamanTarget = byte.MaxValue; ShamanTargetChoosen = false; } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool CanUseKillButton(PlayerControl pc) => true; public override void AfterMeetingTasks() { diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 364f04caaa..c49085a090 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -12,7 +12,7 @@ internal class SoulCollector : RoleBase private const int Id = 15300; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -49,9 +49,6 @@ public override void Add(byte playerId) SoulCollectorPoints.TryAdd(playerId, 0); CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid"); @@ -217,15 +214,11 @@ internal class Death : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.SoulCollector); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); diff --git a/Roles/Neutral/Stalker.cs b/Roles/Neutral/Stalker.cs index 5d0faf555a..4318fbadbf 100644 --- a/Roles/Neutral/Stalker.cs +++ b/Roles/Neutral/Stalker.cs @@ -11,7 +11,7 @@ internal class Stalker : RoleBase private const int Id = 18100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -49,9 +49,6 @@ public override void Add(byte playerId) IsWinKill[playerId] = false; DRpcSetKillCount(Utils.GetPlayerById(playerId)); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public static void ReceiveRPC(MessageReader msg) diff --git a/Roles/Neutral/Traitor.cs b/Roles/Neutral/Traitor.cs index 705abb11c8..22a14605df 100644 --- a/Roles/Neutral/Traitor.cs +++ b/Roles/Neutral/Traitor.cs @@ -9,7 +9,8 @@ internal class Traitor : RoleBase private const int Id = 18200; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -37,9 +38,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs index 0f13aeb3fc..f2bed789d2 100644 --- a/Roles/Neutral/Virus.cs +++ b/Roles/Neutral/Virus.cs @@ -14,6 +14,7 @@ internal class Virus : RoleBase //===========================SETUP================================\\ private const int Id = 18300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Virus); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -61,9 +62,6 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = InfectMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void OnOthersMeetingHudStart(PlayerControl pc) diff --git a/main.cs b/main.cs index 899f7f3999..d6cc46965f 100644 --- a/main.cs +++ b/main.cs @@ -142,6 +142,7 @@ public class Main : BasePlugin public static bool isChatCommand = false; public static bool MeetingIsStarted = false; + public static readonly HashSet DesyncPlayerList = []; public static readonly HashSet TasklessCrewmate = []; public static readonly HashSet OverDeadPlayerList = []; public static readonly HashSet UnreportableBodies = []; From 5fdad05d3a46dc5aaf51107b6d489d2709445974 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 13:35:42 +0800 Subject: [PATCH 266/778] Has Desync Role --- Modules/Utils.cs | 6 +++--- Patches/ChatBubblePatch.cs | 2 +- Patches/ExilePatch.cs | 4 ++-- Patches/IntroPatch.cs | 2 +- Patches/MeetingHudPatch.cs | 2 +- Patches/OneWayShadowsPatch.cs | 6 ++++-- Patches/PlayerControlPatch.cs | 8 ++++---- Patches/SabotageSystemPatch.cs | 18 ++++++++++++------ Patches/onGameStartedPatch.cs | 11 ++--------- Roles/Core/AssignManager/GhostRoleAssign.cs | 2 +- Roles/Core/CustomRoleManager.cs | 2 ++ Roles/Core/RoleBase.cs | 6 ++++++ Roles/Crewmate/Admirer.cs | 4 +--- Roles/Crewmate/CopyCat.cs | 5 +---- Roles/Crewmate/Crusader.cs | 4 +--- Roles/Crewmate/Deceiver.cs | 4 +--- Roles/Crewmate/Deputy.cs | 6 ++---- Roles/Crewmate/Investigator.cs | 5 +---- Roles/Crewmate/Jailer.cs | 5 +---- Roles/Crewmate/Knight.cs | 4 +--- Roles/Crewmate/Medic.cs | 4 +--- Roles/Crewmate/Monarch.cs | 4 +--- Roles/Crewmate/Overseer.cs | 5 +---- Roles/Crewmate/Reverie.cs | 5 +---- Roles/Crewmate/Sheriff.cs | 4 +--- Roles/Crewmate/Vigilante.cs | 5 +---- Roles/Crewmate/Witness.cs | 5 +---- Roles/Neutral/Agitater.cs | 5 +---- Roles/Neutral/Amnesiac.cs | 5 +---- Roles/Neutral/Arsonist.cs | 5 +---- Roles/Neutral/Baker.cs | 9 ++------- Roles/Neutral/Bandit.cs | 4 +--- Roles/Neutral/Berserker.cs | 11 ++--------- Roles/Neutral/BloodKnight.cs | 4 +--- Roles/Neutral/Cultist.cs | 4 +--- Roles/Neutral/CursedSoul.cs | 4 +--- Roles/Neutral/Demon.cs | 4 +--- Roles/Neutral/Doppelganger.cs | 5 +---- Roles/Neutral/Follower.cs | 5 +---- Roles/Neutral/Glitch.cs | 6 ++---- Roles/Neutral/Hater.cs | 4 +--- Roles/Neutral/HexMaster.cs | 5 +---- Roles/Neutral/Huntsman.cs | 7 ++----- Roles/Neutral/Imitator.cs | 4 +--- Roles/Neutral/Infectious.cs | 5 +---- Roles/Neutral/Innocent.cs | 5 +---- Roles/Neutral/Jackal.cs | 7 +------ Roles/Neutral/Jinx.cs | 4 +--- Roles/Neutral/Juggernaut.cs | 5 +---- Roles/Neutral/Maverick.cs | 5 +---- Roles/Neutral/Medusa.cs | 5 +---- Roles/Neutral/Necromancer.cs | 5 +---- Roles/Neutral/Pelican.cs | 6 +----- Roles/Neutral/Pickpocket.cs | 7 +------ Roles/Neutral/Pirate.cs | 4 +--- Roles/Neutral/Pixie.cs | 4 +--- Roles/Neutral/PlagueBearer.cs | 12 +++--------- Roles/Neutral/PlagueDoctor.cs | 4 +--- Roles/Neutral/Poisoner.cs | 6 ++---- Roles/Neutral/PotionMaster.cs | 4 +--- Roles/Neutral/Pursuer.cs | 4 +--- Roles/Neutral/Pyromaniac.cs | 5 +---- Roles/Neutral/Revolutionist.cs | 6 ++---- Roles/Neutral/Romantic.cs | 15 ++++----------- Roles/Neutral/Seeker.cs | 4 +--- Roles/Neutral/SerialKiller.cs | 6 ++---- Roles/Neutral/Shaman.cs | 6 +----- Roles/Neutral/SoulCollector.cs | 11 ++--------- Roles/Neutral/Stalker.cs | 5 +---- Roles/Neutral/Traitor.cs | 6 ++---- Roles/Neutral/Virus.cs | 4 +--- Roles/Vanilla/NoisemakerTOHE.cs | 4 +++- main.cs | 1 + 73 files changed, 117 insertions(+), 277 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 01fdc6c96b..19cc9bedb1 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1784,7 +1784,7 @@ static int GetInfoSize(string RoleInfo) string RoleInfo = $"\n{Font}{ColorString(seer.GetRoleColor(), seer.GetRoleInfo())}"; string RoleNameUp = "\n\n"; - if (!seer.GetCustomRole().IsDesyncRole()) + if (!seer.HasDesyncRole()) { SelfTeamName = string.Empty; RoleNameUp = "\n"; @@ -1822,7 +1822,7 @@ static int GetInfoSize(string RoleInfo) var seerRoleClass = seer.GetRoleClass(); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && seerRoleClass.IsDesyncRole) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && seer.HasDesyncRole()) { seer.RpcSetNamePrivate("", force: NoCache); } @@ -1973,7 +1973,7 @@ static int GetInfoSize(string RoleInfo) //logger.Info("NotifyRoles-Loop2-" + target.GetNameWithRole() + ":START"); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && target.IsAlive() && !seer.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && target.IsAlive() && !seer.Is(Custom_Team.Impostor) && seer.HasDesyncRole()) { realTarget.RpcSetNamePrivate("", seer, force: NoCache); } diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 2023b4f553..d3bc056f4e 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -27,7 +27,7 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe var seerRoleClass = seer.GetRoleClass(); // if based role is Shapeshifter and is Desync Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seerRoleClass.IsDesyncRole) + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seer.HasDesyncRole()) { // When target is impostor, set name color as white __instance.NameText.color = Color.white; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index dff8b1ad59..d9093dc6df 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -73,7 +73,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (CLThingy && exiled != null) { // Reset player cam for exiled desync impostor - if (Main.ResetCamPlayerList.Contains(exiled.PlayerId)) + if (exiled.Object.HasDesyncRole()) { exiled.Object?.ResetPlayerCam(1f); } @@ -181,7 +181,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) player?.SetRealKiller(player, true); // Reset player cam for dead desync impostor - if (Main.ResetCamPlayerList.Contains(x.Key)) + if (player.HasDesyncRole()) { player?.ResetPlayerCam(1f); } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d1749c421d..4c7fe4d0e5 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -607,7 +607,7 @@ public static void Postfix() if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); } - var amDesyncImpostor = Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId); + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); if (amDesyncImpostor) { PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9795ab0eea..764f2f5b0e 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1083,7 +1083,7 @@ public static void Postfix(MeetingHud __instance) if (target == null) continue; // if based role is Shapeshifter and is Desync Shapeshifter - if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seer.HasDesyncRole()) { // When target is impostor, set name color as white target.cosmetics.SetNameColor(Color.white); diff --git a/Patches/OneWayShadowsPatch.cs b/Patches/OneWayShadowsPatch.cs index a07ecc03b0..fe23a78b6c 100644 --- a/Patches/OneWayShadowsPatch.cs +++ b/Patches/OneWayShadowsPatch.cs @@ -1,11 +1,13 @@ -namespace TOHE; +using TOHE.Roles.Core; + +namespace TOHE; [HarmonyPatch(typeof(OneWayShadows), nameof(OneWayShadows.IsIgnored))] public static class OneWayShadowsIsIgnoredPatch { public static bool Prefix(OneWayShadows __instance, ref bool __result) { - var amDesyncImpostor = Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId); + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); if (__instance.IgnoreImpostor && amDesyncImpostor) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 23a13f137a..49b30c753b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -756,7 +756,7 @@ public static void Prefix(PlayerControl __instance, bool isActive, bool shouldAn foreach (var target in Main.AllAlivePlayerControls) { - if (phantom == target || target.AmOwner || !target.GetCustomRole().IsDesyncRole()) continue; + if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; if (isActive) { @@ -1778,7 +1778,7 @@ public static void Postfix(PlayerControl __instance) // if player is Desync Impostor and the vanilla sees player as Imposter, the vanilla process does not hide your name, so the other person's name is hidden if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && // Impostor with vanilla !PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) && // Not an Impostor - Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId)) // Desync Impostor + PlayerControl.LocalPlayer.HasDesyncRole()) // Desync Impostor { // Hide names __instance.cosmetics.ToggleNameVisible(false); @@ -1894,13 +1894,13 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol Logger.Warn($"Error After RpcSetRole: {error}", "RpcSetRole.Prefix.GhostAssignPatch"); } - var targetIsKiller = target.Is(Custom_Team.Impostor) || Main.ResetCamPlayerList.Contains(target.PlayerId); + var targetIsKiller = target.Is(Custom_Team.Impostor) || target.HasDesyncRole(); ghostRoles.Clear(); foreach (var seer in Main.AllPlayerControls) { var self = seer.PlayerId == target.PlayerId; - var seerIsKiller = seer.Is(Custom_Team.Impostor) || Main.ResetCamPlayerList.Contains(seer.PlayerId); + var seerIsKiller = seer.Is(Custom_Team.Impostor) || seer.HasDesyncRole(); if (target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole())) { diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 6fa0f39404..2552d7f462 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -127,10 +127,13 @@ public static void Postfix() { Logger.Info($" IsActive", "MushroomMixupSabotageSystem.UpdateSystem.Postfix"); - foreach (var pc in Main.AllAlivePlayerControls.Where(player => !player.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(player.PlayerId)).ToArray()) + foreach (var pc in Main.AllAlivePlayerControls) { - // Need for hiding player names if player is desync Impostor - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixupIsActive: true); + if (!pc.Is(Custom_Team.Impostor) && pc.HasDesyncRole()) + { + // Need for hiding player names if player is desync Impostor + Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixupIsActive: true); + } } } } @@ -182,10 +185,13 @@ public static void Postfix(MushroomMixupSabotageSystem __instance, bool __state) } }, 1.2f, "Reset Ability Cooldown Arter Mushroom Mixup"); - foreach (var pc in Main.AllAlivePlayerControls.Where(player => !player.Is(Custom_Team.Impostor) && Main.ResetCamPlayerList.Contains(player.PlayerId)).ToArray()) + foreach (var pc in Main.AllAlivePlayerControls) { - // Need for display player names if player is desync Impostor - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true); + if (!pc.Is(Custom_Team.Impostor) && pc.HasDesyncRole()) + { + // Need for display player names if player is desync Impostor + Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true); + } } } } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index a5e4859593..43af24cb92 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -59,10 +59,10 @@ public static void Postfix(AmongUsClient __instance) Main.LastEnteredVent.Clear(); Main.LastEnteredVentLocation.Clear(); + Main.DesyncPlayerList.Clear(); Main.PlayersDiedInMeeting.Clear(); GuessManager.GuesserGuessed.Clear(); Main.AfterMeetingDeathPlayers.Clear(); - Main.ResetCamPlayerList.Clear(); Main.clientIdList.Clear(); PlayerControlSetRolePatch.DidSetGhost.Clear(); @@ -503,15 +503,13 @@ private static void SetRolesAfterSelect() var roleClass = pc.GetRoleClass(); - if (pc.GetRoleClass()?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - roleClass?.OnAdd(pc.PlayerId); // if based role is Shapeshifter if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) { // Is Desync Shapeshifter - if (Main.ResetCamPlayerList.Contains(pc.PlayerId)) + if (pc.HasDesyncRole()) { foreach (var target in Main.AllPlayerControls) { @@ -615,11 +613,6 @@ private static void SetRolesAfterSelect() break; case CustomGameMode.FFA: GameEndCheckerForNormal.SetPredicateToFFA(); - - // Added players in reset cam - Main.ResetCamPlayerList.UnionWith(Main.AllPlayerControls - .Where(pc => pc.GetCustomRole() is CustomRoles.Killer) - .Select(pc => pc.PlayerId)); break; } diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index a9157ea584..4532bc36ec 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -21,7 +21,7 @@ public static void GhostAssignPatch(PlayerControl player) || player == null || player.Data.Disconnected || GhostGetPreviousRole.ContainsKey(player.PlayerId) - || player.GetCustomRole().IsDesyncRole()) return; + || player.HasDesyncRole()) return; if (forceRole.TryGetValue(player.PlayerId, out CustomRoles forcerole)) { Logger.Info($" Debug set {player.GetRealName()}'s role to {forcerole}", "GhostAssignPatch"); player.GetRoleClass()?.OnRemove(player.PlayerId); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 34d6448981..1ec0670f3c 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -67,6 +67,8 @@ public static RoleBase CreateRoleClass(this CustomRoles role) return (RoleBase)Activator.CreateInstance(role.GetStaticRoleClass().GetType()); // Converts this.RoleBase back to its type and creates an unique one. } + public static bool HasDesyncRole(this PlayerControl player) => player != null && (player.GetRoleClass().IsDesyncRole || Main.DesyncPlayerList.Contains(player.Data.PlayerId)); + /// /// If the role protect others players /// diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 5adbb11173..89d0cb8396 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -39,6 +39,12 @@ public void OnAdd(byte playerid) // The player with the class executes this { CustomRoleManager.Add(); } + + // Remember desync player so that when changing role he will still be as desync + if (IsDesyncRole) + { + Main.DesyncPlayerList.Add(playerid); + } } public void OnRemove(byte playerId) { diff --git a/Roles/Crewmate/Admirer.cs b/Roles/Crewmate/Admirer.cs index d02dffa305..a8419d6049 100644 --- a/Roles/Crewmate/Admirer.cs +++ b/Roles/Crewmate/Admirer.cs @@ -15,6 +15,7 @@ internal class Admirer : RoleBase //===========================SETUP================================\\ private const int Id = 24800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Admired); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -42,9 +43,6 @@ public override void Add(byte playerId) { AbilityLimit = SkillLimit.GetInt(); AdmiredList.Add(playerId, []); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index abcf0f27bc..912c53a008 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -11,6 +11,7 @@ internal class CopyCat : RoleBase private const int Id = 11500; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -40,9 +41,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); CurrentKillCooldown = KillCooldown.GetFloat(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) //only to be used when copycat's role is going to be changed permanently { @@ -171,7 +169,6 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } if (role.IsCrewmate()) { - if (role != CustomRoles.CopyCat) { killer.RpcSetCustomRole(role); diff --git a/Roles/Crewmate/Crusader.cs b/Roles/Crewmate/Crusader.cs index 7ed986c2be..67e0d0eb0b 100644 --- a/Roles/Crewmate/Crusader.cs +++ b/Roles/Crewmate/Crusader.cs @@ -10,6 +10,7 @@ internal class Crusader : RoleBase //===========================SETUP================================\\ private const int Id = 10400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Crusader); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -32,9 +33,6 @@ public override void Add(byte playerId) { AbilityLimit = SkillLimitOpt.GetInt(); CurrentKillCooldown = SkillCooldown.GetFloat(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? CurrentKillCooldown : 300f; diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index 743b164f44..a7948ae48d 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -11,6 +11,7 @@ internal class Deceiver : RoleBase //===========================SETUP================================\\ private const int Id = 10500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Deceiver); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -34,9 +35,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = DeceiverSkillLimitTimes.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); public override bool CanUseKillButton(PlayerControl pc) diff --git a/Roles/Crewmate/Deputy.cs b/Roles/Crewmate/Deputy.cs index 82a50a769f..2e9e0b2b57 100644 --- a/Roles/Crewmate/Deputy.cs +++ b/Roles/Crewmate/Deputy.cs @@ -9,6 +9,7 @@ internal class Deputy : RoleBase { //===========================SETUP================================\\ private const int Id = 7800; + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -19,7 +20,7 @@ internal class Deputy : RoleBase public override void SetupCustomOption() { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deputy); + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deputy); HandcuffCooldown = FloatOptionItem.Create(Id + 10, "DeputyHandcuffCooldown", new(0f, 180f, 2.5f), 10f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) .SetValueFormat(OptionFormat.Seconds); DeputyHandcuffCDForTarget = FloatOptionItem.Create(Id + 14, "DeputyHandcuffCDForTarget", new(0f, 180f, 2.5f), 45f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) @@ -30,9 +31,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = HandcuffMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = HandcuffCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && AbilityLimit >= 1; diff --git a/Roles/Crewmate/Investigator.cs b/Roles/Crewmate/Investigator.cs index e484519573..b87674b9d4 100644 --- a/Roles/Crewmate/Investigator.cs +++ b/Roles/Crewmate/Investigator.cs @@ -11,7 +11,7 @@ internal class Investigator : RoleBase private const int Id = 24900; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -48,9 +48,6 @@ public override void Add(byte playerId) MaxInvestigateLimit[playerId] = InvestigateMax.GetInt(); RoundInvestigateLimit[playerId] = InvestigateRoundMax.GetInt(); InvestigatedList[playerId] = []; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index d5bdf7ef57..809a5e1bd9 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -12,7 +12,7 @@ internal class Jailer : RoleBase private const int Id = 10600; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -63,9 +63,6 @@ public override void Add(byte playerId) JailerTarget.Add(playerId, byte.MaxValue); JailerHasExe.Add(playerId, false); JailerDidVote.Add(playerId, false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Knight.cs b/Roles/Crewmate/Knight.cs index c7fb740f95..b9f5726651 100644 --- a/Roles/Crewmate/Knight.cs +++ b/Roles/Crewmate/Knight.cs @@ -9,6 +9,7 @@ internal class Knight : RoleBase //===========================SETUP================================\\ private const int Id = 10800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Knight); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -26,9 +27,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = 1; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); diff --git a/Roles/Crewmate/Medic.cs b/Roles/Crewmate/Medic.cs index c5c94c43a1..8aa9e48fa3 100644 --- a/Roles/Crewmate/Medic.cs +++ b/Roles/Crewmate/Medic.cs @@ -14,6 +14,7 @@ internal class Medic : RoleBase //===========================SETUP================================\\ private const int Id = 8600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Medic); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -69,9 +70,6 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = 1; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SendRPCForProtectList() { diff --git a/Roles/Crewmate/Monarch.cs b/Roles/Crewmate/Monarch.cs index 7967d0be01..4bc0d8e989 100644 --- a/Roles/Crewmate/Monarch.cs +++ b/Roles/Crewmate/Monarch.cs @@ -12,6 +12,7 @@ internal class Monarch : RoleBase //===========================SETUP================================\\ private const int Id = 12100; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Monarch); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -35,9 +36,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = KnightMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = AbilityLimit > 0 ? KnightCooldown.GetFloat() : 300f; diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 6d92b5879c..0ce4a96d33 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -16,7 +16,7 @@ internal class Overseer : RoleBase private const int Id = 12200; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmatePower; //==================================================================\\ @@ -102,9 +102,6 @@ public override void Add(byte playerId) } RandomRole.Add(playerId, GetRandomCrewRoleString()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Reverie.cs b/Roles/Crewmate/Reverie.cs index 5a95e5086a..ad8b5c428c 100644 --- a/Roles/Crewmate/Reverie.cs +++ b/Roles/Crewmate/Reverie.cs @@ -10,7 +10,7 @@ internal class Reverie : RoleBase private const int Id = 11100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -52,9 +52,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); NowCooldown.TryAdd(playerId, DefaultKillCooldown.GetFloat()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index bceb42d702..f826955729 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -10,6 +10,7 @@ internal class Sheriff : RoleBase //===========================SETUP================================\\ private const int Id = 11200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sheriff); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -76,9 +77,6 @@ public override void Add(byte playerId) AbilityLimit = ShotLimitOpt.GetInt(); Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole()} : limit: {AbilityLimit}", "Sheriff"); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SetUpNeutralOptions(int Id) { diff --git a/Roles/Crewmate/Vigilante.cs b/Roles/Crewmate/Vigilante.cs index 6adf329fea..f36691c3bd 100644 --- a/Roles/Crewmate/Vigilante.cs +++ b/Roles/Crewmate/Vigilante.cs @@ -9,7 +9,7 @@ internal class Vigilante : RoleBase private const int Id = 11400; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ @@ -30,9 +30,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = VigilanteKillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index 69a648f69c..63d898eb88 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -12,7 +12,7 @@ internal class Witness : RoleBase private const int Id = 10100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -36,9 +36,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateLowLoadOthers); diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index 138226afbb..a0182edbab 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -12,7 +12,7 @@ internal class Agitater : RoleBase private const int Id = 15800; private static readonly List playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -57,9 +57,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public void ResetBomb() diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index bcc42e9f9a..9c1fb850e6 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -13,7 +13,7 @@ internal class Amnesiac : RoleBase private const int Id = 12700; private static readonly HashSet playerIdList = []; public static bool HasEnabled = playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -47,9 +47,6 @@ public override void Add(byte playerId) playerIdList.Add(playerId); CanUseVent[playerId] = true; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (!AmongUsClient.Instance.AmHost) return; if (ShowArrows.GetBool()) { diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index 07829ec6c6..d55f486e98 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -15,7 +15,7 @@ internal class Arsonist : RoleBase private const int id = 15900; private static readonly HashSet PlayerIds = []; public static bool HasEnabled = PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => CanIgniteAnytime() ? Custom_RoleType.NeutralKilling : Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -57,9 +57,6 @@ public override void Add(byte playerId) foreach (var ar in Main.AllPlayerControls) IsDoused.Add((playerId, ar.PlayerId), false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SendCurrentDousingTargetRPC(byte arsonistId, byte targetId) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index dec4dc0102..2709e69bed 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -15,7 +15,7 @@ internal class Baker : RoleBase public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -61,9 +61,6 @@ public override void Add(byte playerId) CanUseAbility = true; StarvedNonBreaded = false; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static (int, int) BreadedPlayerCount(byte playerId) @@ -308,14 +305,12 @@ internal class Famine : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Baker); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ public override void Add(byte playerId) { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs index d8c3ecbf6b..2347536bd1 100644 --- a/Roles/Neutral/Bandit.cs +++ b/Roles/Neutral/Bandit.cs @@ -13,6 +13,7 @@ internal class Bandit : RoleBase //===========================SETUP================================\\ private const int Id = 16000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Bandit); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -56,9 +57,6 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) { diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 72b093b8f9..77c07bcef4 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -15,7 +15,7 @@ internal class Berserker : RoleBase private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -81,9 +81,6 @@ public override void Add(byte playerId) { BerserkerKillMax[playerId] = 0; PlayerIds.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -181,15 +178,11 @@ internal class War : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Berserker); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) diff --git a/Roles/Neutral/BloodKnight.cs b/Roles/Neutral/BloodKnight.cs index 70fa3d7b97..004df0774f 100644 --- a/Roles/Neutral/BloodKnight.cs +++ b/Roles/Neutral/BloodKnight.cs @@ -13,6 +13,7 @@ internal class BloodKnight : RoleBase //===========================SETUP================================\\ private const int Id = 16100; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.BloodKnight); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -37,9 +38,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { TimeStamp = 0; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte playerId) { diff --git a/Roles/Neutral/Cultist.cs b/Roles/Neutral/Cultist.cs index dc27816403..c3c42027f0 100644 --- a/Roles/Neutral/Cultist.cs +++ b/Roles/Neutral/Cultist.cs @@ -12,6 +12,7 @@ internal class Cultist : RoleBase //===========================SETUP================================\\ private const int Id = 14800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Cultist); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -48,9 +49,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = CharmMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = AbilityLimit >= 1 ? CharmCooldown.GetFloat() + (CharmMax.GetInt() - AbilityLimit) * CharmCooldownIncrese.GetFloat() : 300f; public override bool CanUseKillButton(PlayerControl player) => AbilityLimit >= 1; diff --git a/Roles/Neutral/CursedSoul.cs b/Roles/Neutral/CursedSoul.cs index 84031aab58..d2f0bf09db 100644 --- a/Roles/Neutral/CursedSoul.cs +++ b/Roles/Neutral/CursedSoul.cs @@ -12,6 +12,7 @@ internal class CursedSoul : RoleBase private const int Id = 14000; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -45,9 +46,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); CurseLimit = CurseMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC() diff --git a/Roles/Neutral/Demon.cs b/Roles/Neutral/Demon.cs index 67b8cac98f..4cc16258e1 100644 --- a/Roles/Neutral/Demon.cs +++ b/Roles/Neutral/Demon.cs @@ -13,6 +13,7 @@ internal class Demon : RoleBase //===========================SETUP================================\\ private const int Id = 16200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Demon); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -55,9 +56,6 @@ public override void Add(byte playerId) foreach (var pc in Main.AllAlivePlayerControls) PlayerHealth[pc.PlayerId] = HealthMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index 747d718cc8..be713820a1 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -12,6 +12,7 @@ internal class Doppelganger : RoleBase private const int Id = 25000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Doppelganger); public override bool IsExperimental => true; + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -39,10 +40,6 @@ public override void Add(byte playerId) { AbilityLimit = MaxSteals.GetInt(); DoppelVictim[playerId] = Utils.GetPlayerById(playerId).GetRealName() ?? "Invalid"; - - if (!AmongUsClient.Instance.AmHost) return; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/Follower.cs b/Roles/Neutral/Follower.cs index 7ad48c1bf3..fe9a3ab0fc 100644 --- a/Roles/Neutral/Follower.cs +++ b/Roles/Neutral/Follower.cs @@ -15,7 +15,7 @@ internal class Follower : RoleBase private const int Id = 12800; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -54,9 +54,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); BetTimes.Add(playerId, MaxBetTimes.GetInt()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte playerId) { diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index 33c046b767..259ec63e55 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -13,7 +13,8 @@ internal class Glitch : RoleBase //===========================SETUP================================\\ private const int Id = 16300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Glitch); - public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -73,9 +74,6 @@ public override void Add(byte playerId) LastMimic = ts; lastRpcSend = ts; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - // Double Trigger var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index 0e0d4c1fc4..679e64bea6 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -10,6 +10,7 @@ internal class Hater : RoleBase private const int Id = 12900; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -51,9 +52,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index e90b63a8ef..ed105bb1c9 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -15,7 +15,7 @@ internal class HexMaster : RoleBase private const int Id = 16400; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -60,9 +60,6 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private static void SendRPC(bool doHex, byte hexId, byte target = 255) diff --git a/Roles/Neutral/Huntsman.cs b/Roles/Neutral/Huntsman.cs index 6aac71eea8..eeffede1a2 100644 --- a/Roles/Neutral/Huntsman.cs +++ b/Roles/Neutral/Huntsman.cs @@ -13,6 +13,7 @@ internal class Huntsman : RoleBase //===========================SETUP================================\\ private const int Id = 16500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Huntsman); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -50,16 +51,12 @@ public override void SetupCustomOption() } public override void Add(byte playerId) { + KCD = KillCooldown.GetFloat(); _ = new LateTask(() => { ResetTargets(isStartedGame: true); }, 8f, "Huntsman Reset Targets"); - - KCD = KillCooldown.GetFloat(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public void SendRPC(bool isSetTarget, byte targetId = byte.MaxValue) diff --git a/Roles/Neutral/Imitator.cs b/Roles/Neutral/Imitator.cs index 735aad943f..2ff2325b35 100644 --- a/Roles/Neutral/Imitator.cs +++ b/Roles/Neutral/Imitator.cs @@ -10,6 +10,7 @@ internal class Imitator : RoleBase private const int Id = 13000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Imitator); public override bool IsExperimental => true; + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -36,9 +37,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = 1; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = RememberCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl player) => AbilityLimit > 0; diff --git a/Roles/Neutral/Infectious.cs b/Roles/Neutral/Infectious.cs index 70f78354d2..b0100fbd10 100644 --- a/Roles/Neutral/Infectious.cs +++ b/Roles/Neutral/Infectious.cs @@ -13,7 +13,7 @@ internal class Infectious : RoleBase private const int Id = 16600; private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -52,9 +52,6 @@ public override void Add(byte playerId) PlayerIds.Add(playerId); var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Innocent.cs b/Roles/Neutral/Innocent.cs index 51e3493192..a0a2058110 100644 --- a/Roles/Neutral/Innocent.cs +++ b/Roles/Neutral/Innocent.cs @@ -10,7 +10,7 @@ internal class Innocent : RoleBase private const int Id = 14300; private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -30,9 +30,6 @@ public override void Init() public override void Add(byte playerId) { PlayerIds.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index b468ca603c..12eed319ea 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -12,6 +12,7 @@ internal class Jackal : RoleBase //===========================SETUP================================\\ private const int Id = 16700; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jailer); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -83,9 +84,6 @@ public override void Add(byte playerId) { AbilityLimit = CanRecruitSidekick.GetBool() ? SidekickRecruitLimitOpt.GetInt() : 0; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.CheckDeadBodyOthers.Add(OthersPlayersDead); @@ -143,9 +141,6 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t target.RpcSetCustomRole(CustomRoles.Sidekick); target.GetRoleClass()?.OnAdd(target.PlayerId); - if (!Main.ResetCamPlayerList.Contains(target.PlayerId)) - Main.ResetCamPlayerList.Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); diff --git a/Roles/Neutral/Jinx.cs b/Roles/Neutral/Jinx.cs index 1c6903443b..2696e68278 100644 --- a/Roles/Neutral/Jinx.cs +++ b/Roles/Neutral/Jinx.cs @@ -10,6 +10,7 @@ internal class Jinx : RoleBase //===========================SETUP================================\\ private const int Id = 16800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jinx); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -36,9 +37,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = JinxSpellTimes.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { diff --git a/Roles/Neutral/Juggernaut.cs b/Roles/Neutral/Juggernaut.cs index 475c14e2a7..169c22fc50 100644 --- a/Roles/Neutral/Juggernaut.cs +++ b/Roles/Neutral/Juggernaut.cs @@ -10,7 +10,7 @@ internal class Juggernaut : RoleBase private const int Id = 16900; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -44,9 +44,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); NowCooldown.TryAdd(playerId, DefaultKillCooldown.GetFloat()); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = NowCooldown[id]; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Neutral/Maverick.cs b/Roles/Neutral/Maverick.cs index 563164469b..ce2b86fc66 100644 --- a/Roles/Neutral/Maverick.cs +++ b/Roles/Neutral/Maverick.cs @@ -10,7 +10,7 @@ internal class Maverick : RoleBase //===========================SETUP================================\\ private const int Id = 13200; public static bool HasEnabled = CustomRoleManager.HasEnabled(CustomRoles.Maverick); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -35,9 +35,6 @@ public override void SetupCustomOption() public override void Add(byte playerId) { NumKills = 0; - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); diff --git a/Roles/Neutral/Medusa.cs b/Roles/Neutral/Medusa.cs index 1afa9c2302..2770da576b 100644 --- a/Roles/Neutral/Medusa.cs +++ b/Roles/Neutral/Medusa.cs @@ -10,7 +10,7 @@ internal class Medusa : RoleBase private const int Id = 17000; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -37,9 +37,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Necromancer.cs b/Roles/Neutral/Necromancer.cs index 72eb78c392..9cd89ea423 100644 --- a/Roles/Neutral/Necromancer.cs +++ b/Roles/Neutral/Necromancer.cs @@ -10,7 +10,7 @@ internal class Necromancer : RoleBase private const int Id = 17100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -48,9 +48,6 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); Timer = RevengeTime.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index bc08202a2a..113dbad67c 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -16,6 +16,7 @@ internal class Pelican : RoleBase //===========================SETUP================================\\ private const int Id = 17300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pelican); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -46,11 +47,6 @@ public override void Init() Count = 0; } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override void Remove(byte playerId) { ReturnEatenPlayerBack(Utils.GetPlayerById(playerId)); diff --git a/Roles/Neutral/Pickpocket.cs b/Roles/Neutral/Pickpocket.cs index c02e13491a..494d905afd 100644 --- a/Roles/Neutral/Pickpocket.cs +++ b/Roles/Neutral/Pickpocket.cs @@ -10,7 +10,7 @@ internal class Pickpocket : RoleBase //===========================SETUP================================\\ private const int Id = 17400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pickpocket); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -35,11 +35,6 @@ public override void SetupCustomOption() HideAdditionalVotes = BooleanOptionItem.Create(Id + 14, GeneralOption.HideAdditionalVotes, false, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/Pirate.cs b/Roles/Neutral/Pirate.cs index ceb59b42aa..eccd1cf483 100644 --- a/Roles/Neutral/Pirate.cs +++ b/Roles/Neutral/Pirate.cs @@ -15,6 +15,7 @@ internal class Pirate : RoleBase //===========================SETUP================================\\ private const int Id = 15000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pirate); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -51,9 +52,6 @@ public override void Init() public override void Add(byte playerId) { DuelDone.Add(playerId, false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void MeetingHudClear() { diff --git a/Roles/Neutral/Pixie.cs b/Roles/Neutral/Pixie.cs index 01fceddb96..dd6afdaa0f 100644 --- a/Roles/Neutral/Pixie.cs +++ b/Roles/Neutral/Pixie.cs @@ -10,6 +10,7 @@ internal class Pixie : RoleBase //===========================SETUP================================\\ private const int Id = 25900; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pirate); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -43,9 +44,6 @@ public override void Add(byte playerId) { PixieTargets[playerId] = []; PixiePoints.Add(playerId, 0); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 86060d3c8e..41e6d50835 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -14,7 +14,8 @@ internal class PlagueBearer : RoleBase private const int Id = 17600; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -52,9 +53,6 @@ public override void Add(byte playerId) PlaguedList[playerId] = []; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -232,15 +230,11 @@ internal class Pestilence : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PlagueBearer); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index 675b6ac78e..e5b4ba9c74 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -15,6 +15,7 @@ internal class PlagueDoctor : RoleBase //===========================SETUP================================\\ private const int Id = 27600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PlagueDoctor); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -84,9 +85,6 @@ public override void Add(byte playerId) if (Main.NormalOptions.MapId == 4) InfectInactiveTime += 5f; - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) { CustomRoleManager.OnFixedUpdateOthers.Add(OnCheckPlayerPosition); diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Neutral/Poisoner.cs index c244a7b8a8..8be8e2c5cf 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Neutral/Poisoner.cs @@ -16,7 +16,8 @@ private class PoisonedInfo(byte poisonerId, float killTimer) private const int Id = 17500; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -51,9 +52,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Neutral/PotionMaster.cs b/Roles/Neutral/PotionMaster.cs index c82e434d11..db55f619af 100644 --- a/Roles/Neutral/PotionMaster.cs +++ b/Roles/Neutral/PotionMaster.cs @@ -12,6 +12,7 @@ internal class PotionMaster : RoleBase //===========================SETUP================================\\ private const int Id = 17700; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PotionMaster); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -44,9 +45,6 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } private void SendRPC(byte playerId, byte targetId) diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index 9a77c49c05..7390a1b9b7 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -11,6 +11,7 @@ internal class Pursuer : RoleBase //===========================SETUP================================\\ private const int Id = 13400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pursuer); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -36,9 +37,6 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = PursuerSkillLimitTimes.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl pc) => CanUseKillButton(pc.PlayerId); diff --git a/Roles/Neutral/Pyromaniac.cs b/Roles/Neutral/Pyromaniac.cs index 6f3889636e..2cb963458c 100644 --- a/Roles/Neutral/Pyromaniac.cs +++ b/Roles/Neutral/Pyromaniac.cs @@ -10,7 +10,7 @@ internal class Pyromaniac : RoleBase private const int Id = 17800; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -47,9 +47,6 @@ public override void Add(byte playerId) // Double Trigger var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs index 4d7d2bd09c..fd5082514f 100644 --- a/Roles/Neutral/Revolutionist.cs +++ b/Roles/Neutral/Revolutionist.cs @@ -15,7 +15,8 @@ internal class Revolutionist : RoleBase private const int Id = 15200; private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -73,9 +74,6 @@ public override void Add(byte playerId) foreach (var ar in Main.AllPlayerControls) IsDraw.Add((playerId, ar.PlayerId), false); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = RevolutionistCooldown.GetFloat(); diff --git a/Roles/Neutral/Romantic.cs b/Roles/Neutral/Romantic.cs index 1ec10d46f7..360c75d568 100644 --- a/Roles/Neutral/Romantic.cs +++ b/Roles/Neutral/Romantic.cs @@ -15,6 +15,7 @@ internal class Romantic : RoleBase //===========================SETUP================================\\ private const int Id = 13500; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Romantic); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -68,9 +69,6 @@ public override void Add(byte playerId) BetTimes.Add(playerId, 1); CustomRoleManager.CheckDeadBodyOthers.Add(OthersAfterPlayerDeathTask); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void Remove(byte playerId) { @@ -314,6 +312,7 @@ internal class VengefulRomantic : RoleBase //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Romantic); + public override bool IsDesyncRole => new Romantic().IsDesyncRole; public override CustomRoles ThisRoleBase => new Romantic().ThisRoleBase; public override Custom_RoleType ThisRoleType => new Romantic().ThisRoleType; //==================================================================\\ @@ -329,9 +328,6 @@ public override void Init() public override void Add(byte playerId) { VengefulTarget.Add(playerId, Romantic.VengefulTargetId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && !hasKilledKiller; @@ -381,11 +377,11 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) internal class RuthlessRomantic : RoleBase { - //===========================SETUP================================\\ private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => new Romantic().IsDesyncRole; public override CustomRoles ThisRoleBase => new Romantic().ThisRoleBase; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -396,9 +392,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Romantic.RuthlessKCD.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Neutral/Seeker.cs b/Roles/Neutral/Seeker.cs index 91570b5ffb..7095d3eee7 100644 --- a/Roles/Neutral/Seeker.cs +++ b/Roles/Neutral/Seeker.cs @@ -10,6 +10,7 @@ internal class Seeker : RoleBase //===========================SETUP================================\\ private const int Id = 14600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Seeker); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -43,9 +44,6 @@ public override void Add(byte playerId) DefaultSpeed[playerId] = Main.AllPlayerSpeed[playerId]; PointsToWinOpt = PointsToWin.GetInt(); - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - if (AmongUsClient.Instance.AmHost) _ = new LateTask(() => { diff --git a/Roles/Neutral/SerialKiller.cs b/Roles/Neutral/SerialKiller.cs index 2ed045dc24..1ed578f989 100644 --- a/Roles/Neutral/SerialKiller.cs +++ b/Roles/Neutral/SerialKiller.cs @@ -9,7 +9,8 @@ internal class SerialKiller : RoleBase private const int Id = 17900; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -39,9 +40,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); diff --git a/Roles/Neutral/Shaman.cs b/Roles/Neutral/Shaman.cs index 7c575a1af2..e161e5737a 100644 --- a/Roles/Neutral/Shaman.cs +++ b/Roles/Neutral/Shaman.cs @@ -9,6 +9,7 @@ internal class Shaman : RoleBase //===========================SETUP================================\\ private const int Id = 13600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Shaman); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ @@ -30,11 +31,6 @@ public override void Init() ShamanTarget = byte.MaxValue; ShamanTargetChoosen = false; } - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool CanUseKillButton(PlayerControl pc) => true; public override void AfterMeetingTasks() { diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 364f04caaa..c49085a090 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -12,7 +12,7 @@ internal class SoulCollector : RoleBase private const int Id = 15300; public static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ @@ -49,9 +49,6 @@ public override void Add(byte playerId) SoulCollectorPoints.TryAdd(playerId, 0); CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid"); @@ -217,15 +214,11 @@ internal class Death : RoleBase { //===========================SETUP================================\\ public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.SoulCollector); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ - public override void Add(byte playerId) - { - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); - } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); diff --git a/Roles/Neutral/Stalker.cs b/Roles/Neutral/Stalker.cs index 5d0faf555a..4318fbadbf 100644 --- a/Roles/Neutral/Stalker.cs +++ b/Roles/Neutral/Stalker.cs @@ -11,7 +11,7 @@ internal class Stalker : RoleBase private const int Id = 18100; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -49,9 +49,6 @@ public override void Add(byte playerId) IsWinKill[playerId] = false; DRpcSetKillCount(Utils.GetPlayerById(playerId)); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public static void ReceiveRPC(MessageReader msg) diff --git a/Roles/Neutral/Traitor.cs b/Roles/Neutral/Traitor.cs index 705abb11c8..22a14605df 100644 --- a/Roles/Neutral/Traitor.cs +++ b/Roles/Neutral/Traitor.cs @@ -9,7 +9,8 @@ internal class Traitor : RoleBase private const int Id = 18200; private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); - + + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -37,9 +38,6 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs index 0f13aeb3fc..f2bed789d2 100644 --- a/Roles/Neutral/Virus.cs +++ b/Roles/Neutral/Virus.cs @@ -14,6 +14,7 @@ internal class Virus : RoleBase //===========================SETUP================================\\ private const int Id = 18300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Virus); + public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; //==================================================================\\ @@ -61,9 +62,6 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = InfectMax.GetInt(); - - if (!Main.ResetCamPlayerList.Contains(playerId)) - Main.ResetCamPlayerList.Add(playerId); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void OnOthersMeetingHudStart(PlayerControl pc) diff --git a/Roles/Vanilla/NoisemakerTOHE.cs b/Roles/Vanilla/NoisemakerTOHE.cs index 2aaa93e6a3..34b1b7acdc 100644 --- a/Roles/Vanilla/NoisemakerTOHE.cs +++ b/Roles/Vanilla/NoisemakerTOHE.cs @@ -1,4 +1,6 @@  +using TOHE.Roles.Core; + namespace TOHE.Roles.Vanilla; internal class NoisemakerTOHE : RoleBase @@ -40,7 +42,7 @@ public static void ApplyGameOptionsForOthers(PlayerControl player) var playerRole = player.GetCustomRole(); // When impostor alert is off, and player is desync crewamte, make impostor alert as true - if (playerRole.IsDesyncRole() && playerRole.IsCrewmate() && !ImpostorAlert.GetBool()) + if (player.HasDesyncRole() && !playerRole.IsImpostorTeamV3() && !ImpostorAlert.GetBool()) { AURoleOptions.NoisemakerImpostorAlert = true; } diff --git a/main.cs b/main.cs index 899f7f3999..d6cc46965f 100644 --- a/main.cs +++ b/main.cs @@ -142,6 +142,7 @@ public class Main : BasePlugin public static bool isChatCommand = false; public static bool MeetingIsStarted = false; + public static readonly HashSet DesyncPlayerList = []; public static readonly HashSet TasklessCrewmate = []; public static readonly HashSet OverDeadPlayerList = []; public static readonly HashSet UnreportableBodies = []; From 3777c0f3e758ae76d691c632ed00c024824c785a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 13:51:12 +0800 Subject: [PATCH 267/778] Some changes --- GameModes/FFAManager.cs | 1 - Modules/ExtendedPlayerControl.cs | 2 +- Modules/Utils.cs | 9 ++++++--- Patches/ChatBubblePatch.cs | 1 - Patches/GameSettingMenuPatch.cs | 1 - Patches/HudPatch.cs | 1 - Patches/TaskAssignPatch.cs | 6 +++--- Patches/TextBoxPatch.cs | 2 -- Roles/AddOns/Common/Spurt.cs | 7 +------ Roles/AddOns/IAddon.cs | 8 +------- 10 files changed, 12 insertions(+), 26 deletions(-) diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index a75e113503..56d1b3e02e 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -1,5 +1,4 @@ using Hazel; -using System; using TOHE.Modules; using UnityEngine; using static TOHE.Translator; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 1292dbf1aa..ca4981b60d 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -869,7 +869,7 @@ public static List GetPlayersInAbilityRangeSorted(this PlayerCont var rangePlayersIL = RoleBehaviour.GetTempPlayerList(); List rangePlayers = []; player.Data.Role.GetPlayersInAbilityRangeSorted(rangePlayersIL, ignoreColliders); - foreach (var pc in rangePlayersIL.ToArray()) + foreach (var pc in rangePlayersIL.GetFastEnumerator()) { if (predicate(pc)) rangePlayers.Add(pc); } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 19cc9bedb1..03acfca797 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -381,9 +381,12 @@ public static string GetDeathReason(PlayerState.DeathReason status) } public static Color GetRoleColor(CustomRoles role) { - if (!Main.roleColors.TryGetValue(role, out var hexColor)) hexColor = "#ffffff"; - _ = ColorUtility.TryParseHtmlString(hexColor, out Color c); - return c; + if (Main.roleColors.TryGetValue(role, out var hexColor)) + { + _ = ColorUtility.TryParseHtmlString(hexColor, out var color); + return color; + } + return Color.white; } public static Color GetTeamColor(PlayerControl player) { diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index d3bc056f4e..0fd648b3bf 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -1,6 +1,5 @@ using AmongUs.GameOptions; using TOHE.Roles.Core; -using TMPro; using UnityEngine; namespace TOHE.Patches; diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 8542cf180c..cb7dce2666 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -1,7 +1,6 @@ using System; using TMPro; using UnityEngine; -using UnityEngine.UI; using static TOHE.Translator; using Object = UnityEngine.Object; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 4c25e787d8..62b85d144c 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -3,7 +3,6 @@ using TMPro; using TOHE.Roles.Core; using UnityEngine; -using AmongUs.GameOptions; using static TOHE.Translator; using TOHE.Roles.AddOns.Common; diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs index 3004ceb35f..bd04033c49 100644 --- a/Patches/TaskAssignPatch.cs +++ b/Patches/TaskAssignPatch.cs @@ -19,7 +19,7 @@ public static void Prefix(ShipStatus __instance, List disabledTasks = []; - foreach (var task in unusedTasks) + foreach (var task in unusedTasks.GetFastEnumerator()) { switch (task.TaskType) { @@ -202,13 +202,13 @@ public static void Prefix(NetworkedPlayerInfo __instance, [HarmonyArgument(0)] r // List of long tasks that can be assigned Il2CppSystem.Collections.Generic.List LongTasks = new(); - foreach (var task in ShipStatus.Instance.LongTasks.ToArray()) + foreach (var task in ShipStatus.Instance.LongTasks) LongTasks.Add(task); Shuffle(LongTasks); // List of short tasks that can be assigned Il2CppSystem.Collections.Generic.List ShortTasks = new(); - foreach (var task in ShipStatus.Instance.ShortTasks.ToArray()) + foreach (var task in ShipStatus.Instance.ShortTasks) ShortTasks.Add(task); Shuffle(ShortTasks); diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index a028faee9e..bad74e70a2 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -1,6 +1,4 @@ using System; -using TMPro; -using UnityEngine; namespace TOHE.Patches; diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index f3c7ef027f..a763d1f0a9 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static TOHE.Options; +using static TOHE.Options; using UnityEngine; namespace TOHE.Roles.AddOns.Common diff --git a/Roles/AddOns/IAddon.cs b/Roles/AddOns/IAddon.cs index ff53ecf272..0305acaac0 100644 --- a/Roles/AddOns/IAddon.cs +++ b/Roles/AddOns/IAddon.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -//Thanks EHR for https://github.com/Gurge44/EndlessHostRoles/blob/main/Roles/AddOns/IAddon.cs and everything related ;) +//Thanks EHR for https://github.com/Gurge44/EndlessHostRoles/blob/main/Roles/AddOns/IAddon.cs and everything related ;) namespace TOHE.Roles.AddOns { From 0e6f6ee679d4977253d2a1f37f7a9478cf14c2aa Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 14:13:48 +0800 Subject: [PATCH 268/778] Fix Fortune Teller --- Roles/Crewmate/FortuneTeller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index cca1645c1d..9dfaa5ddee 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -158,7 +158,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } else { - List completeRoleList = EnumHelper.Achunk(chunkSize: 6, shuffle: true, exclude: (x) => !x.IsGhostRole() && !x.IsAdditionRole() && x != CustomRoles.NotAssigned && x != CustomRoles.ChiefOfPolice); + List completeRoleList = EnumHelper.Achunk(chunkSize: 6, shuffle: true, exclude: (x) => !x.IsGhostRole() && !x.IsAdditionRole() && !x.IsVanilla() && x is not CustomRoles.NotAssigned and not CustomRoles.ChiefOfPolice and not CustomRoles.Killer and not CustomRoles.GM); var targetRole = target.GetCustomRole(); string text = string.Empty; From 1221e705dc7d36d1ec7fcefbb614d9649383dd86 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:13:25 +0200 Subject: [PATCH 269/778] almsot finish --- Modules/OutfitManager.cs | 10 ++++++- Patches/MeetingHudPatch.cs | 4 +++ Patches/onGameStartedPatch.cs | 4 ++- Resources/Lang/en_US.json | 5 ++++ Resources/roleColor.json | 3 +- Roles/AddOns/Common/Rebirth.cs | 50 ++++++++++++++++++++++++++++++++++ main.cs | 1 + 7 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 Roles/AddOns/Common/Rebirth.cs diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index 998cfd5ef5..2a39e8e550 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -2,7 +2,7 @@ public static class OutfitManager { - public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, bool force = false) + public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, uint newLevel = 500, bool force = false) { Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; @@ -54,6 +54,14 @@ void Setoutfit() .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) .EndRpc(); + if (newLevel != 500) + { + player.SetLevel(newLevel); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetLevel) + .WritePacked(newLevel) + .EndRpc(); + } + sender.SendMessage(); //cannot use currentoutfit type because of mushroom mixup . . diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 764f2f5b0e..c1d0d26d2d 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -351,6 +351,10 @@ public static bool Prefix(MeetingHud __instance) exileId = 0xff; exiledPlayer = GetPlayerInfoById(exileId); } + else if (exiledPlayer.Object?.Is(CustomRoles.Rebirth) == true && Rebirth.SwapSkins(exiledPlayer.Object, out var NewExiled)) + { + exiledPlayer = NewExiled; + } exiledPlayer?.Object.SetRealKiller(null); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 43af24cb92..a3ba566f89 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -576,7 +576,9 @@ private static void SetRolesAfterSelect() case CustomRoles.Bloodthirst: Bloodthirst.Add(); break; - + case CustomRoles.Rebirth: + Rebirth.Add(pc.PlayerId); + break; default: break; } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 203f1723f2..a230670e46 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -324,6 +324,7 @@ "Seer": "Seer", "Tiebreaker": "Tiebreaker", "Oblivious": "Oblivious", + "Rebirth": "Rebirth", "Bewilder": "Bewilder", "Workhorse": "Workhorse", "Fool": "Fool", @@ -502,6 +503,7 @@ "MediumInfo": "Talk with ghosts", "ObserverInfo": "You can see all shield-animations", "PacifistInfo": "Vent to reset kill cooldowns", + "RebirthInfo": "Arise Again", "MonarchInfo": "Give your crew extra voting power!", "SpurtInfo": "Spring Like A rabbit!", "StealthInfo": "Killing Blinds Everyone in the Room", @@ -953,6 +955,7 @@ "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", + "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with someone and thrive once more.", "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", @@ -1479,6 +1482,8 @@ "SheriffMadCanKillNeutral": "Can kill Neutrals", "SheriffMadCanKillCrew": "Can kill Crewmates", + "RebirthFailed": "Ahh, how unfortunate, you did not find any viable souls to swap bodies with", + "ReverieIncreaseKillCooldown": "Increase kill cooldown", "ReverieMaxKillCooldown": "Max kill cooldown", "ReverieMisfireSuicide": "Misfire on reaching max kill cooldown", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 0f2023f6e3..6c242adbc4 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -240,5 +240,6 @@ "Spurt": "#c9e8f5", "Ghastly": "#9ad6d4", "Glow": "#E2F147", - "Radar": "#1eff1e" + "Radar": "#1eff1e", + "Rebirth": "#f08c22" } diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs new file mode 100644 index 0000000000..91d11ed09e --- /dev/null +++ b/Roles/AddOns/Common/Rebirth.cs @@ -0,0 +1,50 @@ +using static TOHE.Options; +using static TOHE.Utils; +using static TOHE.Translator; +using TOHE.Modules; +using static UnityEngine.GraphicsBuffer; + +namespace TOHE.Roles.AddOns.Common; + +public class Rebirth : IAddon +{ + private const int Id = 29300; + public AddonTypes Type => AddonTypes.Helpful; + public static OptionItem RebirthUses; + public static Dictionary Rebirths = []; + public void SetupCustomOption() + { + SetupAdtRoleOptions(Id, CustomRoles.Rebirth, canSetNum: true, teamSpawnOptions: true); + RebirthUses = IntegerOptionItem.Create(Id + 11, "RebirthUses", new(1, 14, 1), 1, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rebirth]) + .SetValueFormat(OptionFormat.Times); + } + public static void Add(byte Playerid) + { + Rebirths[Playerid] = RebirthUses.GetInt(); + } + public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiledPlayer) + { + NewExiledPlayer = default; + if (!pc.Is(CustomRoles.Rebirth)) return false; + var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc) + .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && +/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal))); + + if (ViablePlayer == null) + { + var tytyl = ColorString(GetRoleColor(CustomRoles.Rebirth), GetString("Rebirth").ToUpper()); + Utils.SendMessage(GetString("RebirthFailed"), pc.PlayerId, title: tytyl); + return false; + } + Rebirths[pc.PlayerId]--; + pc.ResetPlayerOutfit(Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, ViablePlayer.Data.PlayerLevel); + ViablePlayer.ResetPlayerOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit, pc.Data.PlayerLevel); + NewExiledPlayer = ViablePlayer.Data; + if (Rebirths[pc.PlayerId] <= 0) + { + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Rebirth); + } + return true; + + } +} \ No newline at end of file diff --git a/main.cs b/main.cs index 4435341a93..40c7c62e35 100644 --- a/main.cs +++ b/main.cs @@ -861,6 +861,7 @@ public enum CustomRoles Lucky, Madmate, Mare, + Rebirth, Mimic, Mundane, Necroview, From 7e9f590a320f6df1e0b0ae82ba3632397caa9584 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:17:46 +0200 Subject: [PATCH 270/778] fix --- Patches/MeetingHudPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index c1d0d26d2d..c2dc3f5ca8 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -351,7 +351,7 @@ public static bool Prefix(MeetingHud __instance) exileId = 0xff; exiledPlayer = GetPlayerInfoById(exileId); } - else if (exiledPlayer.Object?.Is(CustomRoles.Rebirth) == true && Rebirth.SwapSkins(exiledPlayer.Object, out var NewExiled)) + else if (exiledPlayer?.Object.Is(CustomRoles.Rebirth) == true && Rebirth.SwapSkins(exiledPlayer.Object, out var NewExiled)) { exiledPlayer = NewExiled; } From 29f9064855246b95127131d804b93cc136c64afe Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 15:20:47 +0800 Subject: [PATCH 271/778] Utils.GetDistance() --- Modules/DisableDevice.cs | 33 ++++++++++++++-------------- Modules/Utils.cs | 1 + Patches/LadderPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 2 +- Patches/SabotageSystemPatch.cs | 6 ++--- Patches/UsablesPatch.cs | 2 +- Roles/(Ghosts)/Impostor/Possessor.cs | 23 +++++++++---------- Roles/AddOns/Common/Glow.cs | 2 +- Roles/AddOns/Common/Radar.cs | 4 ++-- Roles/AddOns/Common/Spurt.cs | 2 +- Roles/AddOns/Common/Statue.cs | 4 ++-- Roles/Crewmate/Alchemist.cs | 2 +- Roles/Crewmate/Bodyguard.cs | 2 +- Roles/Crewmate/Mortician.cs | 2 +- Roles/Crewmate/Overseer.cs | 2 +- Roles/Crewmate/SuperStar.cs | 2 +- Roles/Crewmate/Telecommunication.cs | 32 +++++++++++++-------------- Roles/Impostor/AntiAdminer.cs | 32 +++++++++++++-------------- Roles/Impostor/Bomber.cs | 2 +- Roles/Impostor/Crewpostor.cs | 2 +- Roles/Impostor/Deathpact.cs | 2 +- Roles/Impostor/DoubleAgent.cs | 6 ++--- Roles/Impostor/Fireworker.cs | 2 +- Roles/Impostor/Lightning.cs | 2 +- Roles/Impostor/Pitfall.cs | 2 +- Roles/Impostor/Puppeteer.cs | 2 +- Roles/Impostor/RiftMaker.cs | 8 +++---- Roles/Impostor/Warlock.cs | 2 +- Roles/Neutral/Agitater.cs | 2 +- Roles/Neutral/Arsonist.cs | 2 +- Roles/Neutral/Berserker.cs | 2 +- Roles/Neutral/Pelican.cs | 2 +- Roles/Neutral/Revolutionist.cs | 2 +- Roles/Neutral/Shroud.cs | 2 +- Roles/Neutral/Solsticer.cs | 2 +- Roles/Neutral/Vulture.cs | 2 +- Roles/Neutral/Werewolf.cs | 2 +- 37 files changed, 100 insertions(+), 103 deletions(-) diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index 817a4565b0..20f9f0a84f 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -1,5 +1,6 @@ using System; using UnityEngine; +using static TOHE.Utils; namespace TOHE; @@ -70,48 +71,48 @@ public static void FixedUpdate() { case 0: if (Options.DisableSkeldAdmin.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["SkeldAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["SkeldAdmin"]) <= UsableDistance(mapName); if (Options.DisableSkeldCamera.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["SkeldCamera"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["SkeldCamera"]) <= UsableDistance(mapName); break; case 1: if (Options.DisableMiraHQAdmin.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["MiraHQAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["MiraHQAdmin"]) <= UsableDistance(mapName); if (Options.DisableMiraHQDoorLog.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["MiraHQDoorLog"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["MiraHQDoorLog"]) <= UsableDistance(mapName); break; case 2: if (Options.DisablePolusAdmin.GetBool()) { - doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusLeftAdmin"]) <= UsableDistance(mapName); - doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusRightAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["PolusLeftAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["PolusRightAdmin"]) <= UsableDistance(mapName); } if (Options.DisablePolusCamera.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusCamera"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["PolusCamera"]) <= UsableDistance(mapName); if (Options.DisablePolusVital.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusVital"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["PolusVital"]) <= UsableDistance(mapName); break; case 3: if (Options.DisableSkeldAdmin.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["DleksAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["DleksAdmin"]) <= UsableDistance(mapName); if (Options.DisableSkeldCamera.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["DleksCamera"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["DleksCamera"]) <= UsableDistance(mapName); break; case 4: if (Options.DisableAirshipCockpitAdmin.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipCockpitAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["AirshipCockpitAdmin"]) <= UsableDistance(mapName); if (Options.DisableAirshipRecordsAdmin.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipRecordsAdmin"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["AirshipRecordsAdmin"]) <= UsableDistance(mapName); if (Options.DisableAirshipCamera.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipCamera"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["AirshipCamera"]) <= UsableDistance(mapName); if (Options.DisableAirshipVital.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipVital"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["AirshipVital"]) <= UsableDistance(mapName); break; case 5: if (Options.DisableFungleBinoculars.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["FungleCamera"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["FungleCamera"]) <= UsableDistance(mapName); if (Options.DisableFungleVital.GetBool()) - doComms |= Vector2.Distance(PlayerPos, DevicePos["FungleVital"]) <= UsableDistance(mapName); + doComms |= GetDistance(PlayerPos, DevicePos["FungleVital"]) <= UsableDistance(mapName); break; } } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 03acfca797..528a8202a0 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -379,6 +379,7 @@ public static string GetDeathReason(PlayerState.DeathReason status) { return GetString("DeathReason." + Enum.GetName(typeof(PlayerState.DeathReason), status)); } + public static float GetDistance(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); public static Color GetRoleColor(CustomRoles role) { if (Main.roleColors.TryGetValue(role, out var hexColor)) diff --git a/Patches/LadderPatch.cs b/Patches/LadderPatch.cs index 1a3a4438c8..69e60aed00 100644 --- a/Patches/LadderPatch.cs +++ b/Patches/LadderPatch.cs @@ -30,7 +30,7 @@ public static void FixedUpdate(PlayerControl player) if (player.Data.Disconnected) return; if (TargetLadderData.ContainsKey(player.PlayerId)) { - if (Vector2.Distance(TargetLadderData[player.PlayerId], player.transform.position) < 0.5f) + if (Utils.GetDistance(TargetLadderData[player.PlayerId], player.transform.position) < 0.5f) { if (player.Data.IsDead) return; // To put in LateTask, put in a death decision first diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 49b30c753b..de07f51b6f 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -315,7 +315,7 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, if (Main.AllAlivePlayerControls.Any(x => x.PlayerId != killer.PlayerId && x.PlayerId != target.PlayerId && - Vector2.Distance(x.transform.position, target.transform.position) < 2f)) + Utils.GetDistance(x.transform.position, target.transform.position) < 2f)) return false; } } diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 2552d7f462..97004d2afb 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -226,9 +226,9 @@ private static bool Prefix(SwitchSystem __instance, [HarmonyArgument(0)] PlayerC if (GameStates.AirshipIsActive) { var truePosition = player.GetCustomPosition(); - if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Vector2.Distance(truePosition, new(-12.93f, -11.28f)) <= 2f) return false; - if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Vector2.Distance(truePosition, new(13.92f, 6.43f)) <= 2f) return false; - if (Options.DisableAirshipCargoLightsPanel.GetBool() && Vector2.Distance(truePosition, new(30.56f, 2.12f)) <= 2f) return false; + if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Utils.GetDistance(truePosition, new(-12.93f, -11.28f)) <= 2f) return false; + if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Utils.GetDistance(truePosition, new(13.92f, 6.43f)) <= 2f) return false; + if (Options.DisableAirshipCargoLightsPanel.GetBool() && Utils.GetDistance(truePosition, new(30.56f, 2.12f)) <= 2f) return false; } if (Fool.IsEnable && player.Is(CustomRoles.Fool)) diff --git a/Patches/UsablesPatch.cs b/Patches/UsablesPatch.cs index 4eda1a58b6..0d2630c656 100644 --- a/Patches/UsablesPatch.cs +++ b/Patches/UsablesPatch.cs @@ -80,7 +80,7 @@ public static bool Prefix(Vent __instance, [HarmonyArgument(0)] NetworkedPlayerI { Vector3 center = playerControl.Collider.bounds.center; Vector3 ventPosition = __instance.transform.position; - actualDistance = Vector2.Distance(center, ventPosition); + actualDistance = Utils.GetDistance(center, ventPosition); canUse &= actualDistance <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(playerControl.Collider, center, ventPosition, Constants.ShipOnlyMask, false); } __result = actualDistance; diff --git a/Roles/(Ghosts)/Impostor/Possessor.cs b/Roles/(Ghosts)/Impostor/Possessor.cs index 5c9425d81a..5a6176706d 100644 --- a/Roles/(Ghosts)/Impostor/Possessor.cs +++ b/Roles/(Ghosts)/Impostor/Possessor.cs @@ -36,9 +36,6 @@ public override void SetupCustomOption() FocusRange = FloatOptionItem.Create(Id + 13, "PossessorFocusRange", new(5f, 25f, 2.5f), 10f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Possessor]) .SetValueFormat(OptionFormat.Multiplier); } - public override void Init() - { - } public override void Add(byte PlayerId) { CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOther); @@ -53,24 +50,26 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) private void OnFixedUpdateOther(PlayerControl target) { + if (_Player == null) return; + if (target.PlayerId == controllingTargetId) { if (controllingPlayer && possessTime >= 0) { - if (CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) > 5f) + if (Utils.GetDistance(_Player.GetCustomPosition(), target.GetCustomPosition()) > 5f) { _Player.RpcTeleport((_Player.GetCustomPosition() + target.GetCustomPosition()) / 2); } foreach (var allPlayers in Main.AllAlivePlayerControls.Where(pc => pc != target)) { - if (CheckRange(target.GetCustomPosition(), allPlayers.GetCustomPosition()) < AlertRange.GetFloat()) + if (Utils.GetDistance(target.GetCustomPosition(), allPlayers.GetCustomPosition()) < AlertRange.GetFloat()) { controllingPlayer = false; } } - if (CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) < 1f) + if (Utils.GetDistance(_Player.GetCustomPosition(), target.GetCustomPosition()) < 1f) { if (target.MyPhysics.Animations.IsPlayingRunAnimation()) { @@ -78,9 +77,9 @@ private void OnFixedUpdateOther(PlayerControl target) target.MyPhysics.RpcCancelPet(); } } - else if (CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) < 3.5f) + else if (Utils.GetDistance(_Player.GetCustomPosition(), target.GetCustomPosition()) < 3.5f) { - if (!target.petting && CheckRange(_Player.GetCustomPosition(), target.GetCustomPosition()) > 1f) + if (!target.petting && Utils.GetDistance(_Player.GetCustomPosition(), target.GetCustomPosition()) > 1f) { target.MyPhysics.RpcPet(_Player.GetCustomPosition(), new Vector2(500f, 500f)); } @@ -108,9 +107,9 @@ private void OnFixedUpdateOther(PlayerControl target) float checkPos = float.MaxValue; foreach (var allPlayers in Main.AllAlivePlayerControls.Where(pc => pc != target)) { - if (CheckRange(_Player.GetCustomPosition(), allPlayers.GetCustomPosition()) < checkPos) + if (Utils.GetDistance(_Player.GetCustomPosition(), allPlayers.GetCustomPosition()) < checkPos) { - checkPos = CheckRange(_Player.GetCustomPosition(), allPlayers.GetCustomPosition()); + checkPos = Utils.GetDistance(_Player.GetCustomPosition(), allPlayers.GetCustomPosition()); } } if (checkPos >= FocusRange.GetFloat()) @@ -127,8 +126,6 @@ private void OnFixedUpdateOther(PlayerControl target) } } - private static float CheckRange(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); - public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) { if (target.GetCustomRole().IsImpostorTeam()) @@ -142,7 +139,7 @@ public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) // Cancel if Target is around other players foreach (var allPlayers in Main.AllAlivePlayerControls.Where(pc => pc != target)) { - if (CheckRange(target.GetCustomPosition(), allPlayers.GetCustomPosition()) < AlertRange.GetFloat()) + if (Utils.GetDistance(target.GetCustomPosition(), allPlayers.GetCustomPosition()) < AlertRange.GetFloat()) { _Player.RpcResetAbilityCooldown(); return false; diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index 7d5cf59db2..9a94932da1 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -86,7 +86,7 @@ public void OnFixedUpdateLowLoad(PlayerControl player) InRadius[player.PlayerId] = Main.AllAlivePlayerControls .Where(target => target != null && !target.Is(CustomRoles.Glow) - && Vector2.Distance(player.GetCustomPosition(), target.GetCustomPosition()) <= GlowRadius.GetFloat()) + && Utils.GetDistance(player.GetCustomPosition(), target.GetCustomPosition()) <= GlowRadius.GetFloat()) .Select(target => target.PlayerId) .ToHashSet(); diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index 1b55836f80..1f9b36335b 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -54,7 +54,7 @@ public static void ReceiveRPC(MessageReader reader) public void OnFixedUpdateLowLoad(PlayerControl radarPC) { - if (!IsEnable || radarPC == null || !radarPC.Is(CustomRoles.Radar) || !GameStates.IsInTask) return; + if (!IsEnable || !radarPC.IsAlive() || !radarPC.Is(CustomRoles.Radar) || !GameStates.IsInTask) return; if (Main.AllAlivePlayerControls.Length <= 1) return; if (!ClosestPlayer.ContainsKey(radarPC.PlayerId)) ClosestPlayer[radarPC.PlayerId] = byte.MaxValue; @@ -68,7 +68,7 @@ public void OnFixedUpdateLowLoad(PlayerControl radarPC) if (pc == radarPC) continue; - float distance = Vector2.Distance(radarPC.GetCustomPosition(), pc.GetCustomPosition()); + float distance = Utils.GetDistance(radarPC.GetCustomPosition(), pc.GetCustomPosition()); if (distance < closestDistance) { diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index a763d1f0a9..00caebdbb0 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -81,7 +81,7 @@ public void OnFixedUpdate(PlayerControl player) if (!player.Is(CustomRoles.Spurt) || !player.IsAlive()) return; var pos = player.GetCustomPosition(); - bool moving = Vector2.Distance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); + bool moving = Utils.GetDistance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); LastPos[player.PlayerId] = pos; float modulator = Modulator.GetFloat(); diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index 3509b26ced..c5ef9891c3 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -67,7 +67,7 @@ public void OnFixedUpdate(PlayerControl victim) foreach (var PVC in Main.AllAlivePlayerControls) { - if (CountNearplr.Contains(PVC.PlayerId) && Vector2.Distance(PVC.transform.position, victim.transform.position) > 2f) + if (CountNearplr.Contains(PVC.PlayerId) && Utils.GetDistance(PVC.transform.position, victim.transform.position) > 2f) { CountNearplr.Remove(PVC.PlayerId); } @@ -77,7 +77,7 @@ public void OnFixedUpdate(PlayerControl victim) { foreach (var plr in Main.AllAlivePlayerControls) { - if (Vector2.Distance(plr.transform.position, victim.transform.position) < 2f && plr != victim) + if (Utils.GetDistance(plr.transform.position, victim.transform.position) < 2f && plr != victim) { if (!CountNearplr.Contains(plr.PlayerId)) CountNearplr.Add(plr.PlayerId); } diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index 44705a628d..680c77c7ad 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -190,7 +190,7 @@ private static void OnFixedUpdatesBloodlus(PlayerControl player) { if (target.PlayerId != player.PlayerId && !target.IsTransformedNeutralApocalypse()) { - dis = Vector2.Distance(bloodthirstPos, target.transform.position); + dis = Utils.GetDistance(bloodthirstPos, target.transform.position); targetDistance.Add(target.PlayerId, dis); } } diff --git a/Roles/Crewmate/Bodyguard.cs b/Roles/Crewmate/Bodyguard.cs index 61a88b9531..6b5bf90d4e 100644 --- a/Roles/Crewmate/Bodyguard.cs +++ b/Roles/Crewmate/Bodyguard.cs @@ -37,7 +37,7 @@ or CustomRoles.Veteran return false; var pos = target.transform.position; - var dis = Vector2.Distance(pos, bodyguard.transform.position); + var dis = Utils.GetDistance(pos, bodyguard.transform.position); if (dis > ProtectRadiusOpt.GetFloat()) return false; if (bodyguard.Is(CustomRoles.Madmate) && killer.GetCustomRole().IsImpostorTeam()) diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index ddc8c25157..d11913ecb1 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -79,7 +79,7 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe foreach (var pc in Main.AllAlivePlayerControls) { if (pc.PlayerId == target.PlayerId) continue; - var dis = Vector2.Distance(pc.transform.position, pos); + var dis = Utils.GetDistance(pc.transform.position, pos); if (dis < minDis && dis < 1.5f) { minDis = dis; diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 0ce4a96d33..48aea22301 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -229,7 +229,7 @@ public override void OnFixedUpdate(PlayerControl player) { float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(Reach.IsReach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - float dis = Vector2.Distance(player.GetCustomPosition(), farTarget.GetCustomPosition()); + float dis = GetDistance(player.GetCustomPosition(), farTarget.GetCustomPosition()); if (dis <= range) { OverseerTimer[playerId] = (farTarget, farTime + Time.fixedDeltaTime); diff --git a/Roles/Crewmate/SuperStar.cs b/Roles/Crewmate/SuperStar.cs index 5c0a0a9cf9..6f5b6fcefa 100644 --- a/Roles/Crewmate/SuperStar.cs +++ b/Roles/Crewmate/SuperStar.cs @@ -41,7 +41,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t return !Main.AllAlivePlayerControls.Any(x => x.PlayerId != killer.PlayerId && x.PlayerId != target.PlayerId && - Vector2.Distance(x.transform.position, target.transform.position) < 2f); + GetDistance(x.transform.position, target.transform.position) < 2f); } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl pc, CustomRoles role, ref bool guesserSuicide) { diff --git a/Roles/Crewmate/Telecommunication.cs b/Roles/Crewmate/Telecommunication.cs index d9816a92f9..a9b4e8b981 100644 --- a/Roles/Crewmate/Telecommunication.cs +++ b/Roles/Crewmate/Telecommunication.cs @@ -76,48 +76,48 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) { case 0: if (!Options.DisableSkeldAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["SkeldAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableSkeldCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["SkeldCamera"]) <= DisableDevice.UsableDistance(mapName); break; case 1: if (!Options.DisableMiraHQAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["MiraHQAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableMiraHQDoorLog.GetBool()) - DoorLog |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQDoorLog"]) <= DisableDevice.UsableDistance(mapName); + DoorLog |= GetDistance(PlayerPos, DisableDevice.DevicePos["MiraHQDoorLog"]) <= DisableDevice.UsableDistance(mapName); break; case 2: if (!Options.DisablePolusAdmin.GetBool()) { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusLeftAdmin"]) <= DisableDevice.UsableDistance(mapName); - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusRightAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusLeftAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusRightAdmin"]) <= DisableDevice.UsableDistance(mapName); } if (!Options.DisablePolusCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusCamera"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisablePolusVital.GetBool()) - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusVital"]) <= DisableDevice.UsableDistance(mapName); + Vital |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusVital"]) <= DisableDevice.UsableDistance(mapName); break; case 3: if (!Options.DisableSkeldAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["DleksAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableSkeldCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["DleksCamera"]) <= DisableDevice.UsableDistance(mapName); break; case 4: if (!Options.DisableAirshipCockpitAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCockpitAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipCockpitAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableAirshipRecordsAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipRecordsAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipRecordsAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableAirshipCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipCamera"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableAirshipVital.GetBool()) - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipVital"]) <= DisableDevice.UsableDistance(mapName); + Vital |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipVital"]) <= DisableDevice.UsableDistance(mapName); break; case 5: if (!Options.DisableFungleBinoculars.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["FungleCamera"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableFungleVital.GetBool()) - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleVital"]) <= DisableDevice.UsableDistance(mapName); + Vital |= GetDistance(PlayerPos, DisableDevice.DevicePos["FungleVital"]) <= DisableDevice.UsableDistance(mapName); break; } } diff --git a/Roles/Impostor/AntiAdminer.cs b/Roles/Impostor/AntiAdminer.cs index 3569eed02f..42b7591383 100644 --- a/Roles/Impostor/AntiAdminer.cs +++ b/Roles/Impostor/AntiAdminer.cs @@ -69,48 +69,48 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) { case 0: if (!Options.DisableSkeldAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["SkeldAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableSkeldCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["SkeldCamera"]) <= DisableDevice.UsableDistance(mapName); break; case 1: if (!Options.DisableMiraHQAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["MiraHQAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableMiraHQDoorLog.GetBool()) - DoorLog |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQDoorLog"]) <= DisableDevice.UsableDistance(mapName); + DoorLog |= GetDistance(PlayerPos, DisableDevice.DevicePos["MiraHQDoorLog"]) <= DisableDevice.UsableDistance(mapName); break; case 2: if (!Options.DisablePolusAdmin.GetBool()) { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusLeftAdmin"]) <= DisableDevice.UsableDistance(mapName); - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusRightAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusLeftAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusRightAdmin"]) <= DisableDevice.UsableDistance(mapName); } if (!Options.DisablePolusCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusCamera"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisablePolusVital.GetBool()) - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusVital"]) <= DisableDevice.UsableDistance(mapName); + Vital |= GetDistance(PlayerPos, DisableDevice.DevicePos["PolusVital"]) <= DisableDevice.UsableDistance(mapName); break; case 3: if (!Options.DisableSkeldAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["DleksAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableSkeldCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["DleksCamera"]) <= DisableDevice.UsableDistance(mapName); break; case 4: if (!Options.DisableAirshipCockpitAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCockpitAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipCockpitAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableAirshipRecordsAdmin.GetBool()) - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipRecordsAdmin"]) <= DisableDevice.UsableDistance(mapName); + Admin |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipRecordsAdmin"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableAirshipCamera.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipCamera"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableAirshipVital.GetBool()) - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipVital"]) <= DisableDevice.UsableDistance(mapName); + Vital |= GetDistance(PlayerPos, DisableDevice.DevicePos["AirshipVital"]) <= DisableDevice.UsableDistance(mapName); break; case 5: if (!Options.DisableFungleBinoculars.GetBool()) - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleCamera"]) <= DisableDevice.UsableDistance(mapName); + Camera |= GetDistance(PlayerPos, DisableDevice.DevicePos["FungleCamera"]) <= DisableDevice.UsableDistance(mapName); if (!Options.DisableFungleVital.GetBool()) - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleVital"]) <= DisableDevice.UsableDistance(mapName); + Vital |= GetDistance(PlayerPos, DisableDevice.DevicePos["FungleVital"]) <= DisableDevice.UsableDistance(mapName); break; } } diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index 56e4a00508..5932c33540 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -79,7 +79,7 @@ public override void UnShapeShiftButton(PlayerControl shapeshifter) if (!target.IsAlive() || Medic.ProtectList.Contains(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; var pos = shapeshifter.transform.position; - var dis = Vector2.Distance(pos, target.transform.position); + var dis = Utils.GetDistance(pos, target.transform.position); if (dis > BomberRadius.GetFloat()) continue; target.SetDeathReason(PlayerState.DeathReason.Bombed); diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index 026dcff578..ff092e0748 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -122,7 +122,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } else { - list = [.. list.OrderBy(x => Vector2.Distance(player.transform.position, x.transform.position))]; + list = [.. list.OrderBy(x => Utils.GetDistance(player.transform.position, x.transform.position))]; var target = list[0]; if (!target.IsTransformedNeutralApocalypse()) diff --git a/Roles/Impostor/Deathpact.cs b/Roles/Impostor/Deathpact.cs index fb845fa089..d4527afe30 100644 --- a/Roles/Impostor/Deathpact.cs +++ b/Roles/Impostor/Deathpact.cs @@ -193,7 +193,7 @@ private static bool CheckCancelDeathpact(PlayerControl deathpact) float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(Reach.IsReach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; foreach (var otherPlayerInPact in PlayersInDeathpact[deathpact.PlayerId].Where(a => a.PlayerId != player.PlayerId).ToArray()) { - float dis = Vector2.Distance(player.transform.position, otherPlayerInPact.transform.position); + float dis = GetDistance(player.transform.position, otherPlayerInPact.transform.position); cancelDeathpact = cancelDeathpact && (dis <= range); } } diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index d803d0f548..772a0af522 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -99,7 +99,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) if (pc.PlayerId == Agitater.CurrentBombedPlayer) { Agitater.ResetBomb(); - PlaySoundForAll("Boom"); + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); _ = new LateTask(() => { if (pc.inVent) pc.MyPhysics.RpcBootFromVent(vent.Id); @@ -241,7 +241,7 @@ private void BoomBoom(PlayerControl player) foreach (PlayerControl target in Main.AllAlivePlayerControls) // Get players in radius of bomb that are not in a vent. { - if (CheckForPlayersInRadius(player, target) <= ExplosionRadius.GetFloat()) + if (Utils.GetDistance(player.GetCustomPosition(), target.GetCustomPosition()) <= ExplosionRadius.GetFloat()) { if (player.inVent) continue; Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Bombed; @@ -256,8 +256,6 @@ private void BoomBoom(PlayerControl player) _Player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); } - private static float CheckForPlayersInRadius(PlayerControl player, PlayerControl target) => Vector2.Distance(player.GetCustomPosition(), target.GetCustomPosition()); - // Set bomb mark on player. public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { diff --git a/Roles/Impostor/Fireworker.cs b/Roles/Impostor/Fireworker.cs index 615fd2daea..20351ef690 100644 --- a/Roles/Impostor/Fireworker.cs +++ b/Roles/Impostor/Fireworker.cs @@ -136,7 +136,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl { foreach (var pos in FireworkerPosition[shapeshifterId].ToArray()) { - var dis = Vector2.Distance(pos, player.transform.position); + var dis = Utils.GetDistance(pos, player.transform.position); if (dis > fireworkerRadius) continue; if (player == shapeshifter) diff --git a/Roles/Impostor/Lightning.cs b/Roles/Impostor/Lightning.cs index d10fca6545..8254c6b005 100644 --- a/Roles/Impostor/Lightning.cs +++ b/Roles/Impostor/Lightning.cs @@ -136,7 +136,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl lightning) foreach (var pc in allAlivePlayerControls) { var pos = gs.transform.position; - var dis = Vector2.Distance(pos, pc.transform.position); + var dis = Utils.GetDistance(pos, pc.transform.position); if (dis > 0.3f) continue; deList.Add(gs.PlayerId); diff --git a/Roles/Impostor/Pitfall.cs b/Roles/Impostor/Pitfall.cs index 9a548a7b9e..30ed0692bd 100644 --- a/Roles/Impostor/Pitfall.cs +++ b/Roles/Impostor/Pitfall.cs @@ -137,7 +137,7 @@ private void OnFixedUpdateOthers(PlayerControl player) continue; } - var dis = Vector2.Distance(trap.Location, position); + var dis = Utils.GetDistance(trap.Location, position); if (dis > TrapRadius.GetFloat()) continue; if (TrapFreezeTime.GetFloat() > 0) diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index ca3a7000d4..e15be939fb 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -118,7 +118,7 @@ private void OnFixedUpdateOthers(PlayerControl puppet) { if (target.PlayerId != puppet.PlayerId && !(target.Is(Custom_Team.Impostor) || target.Is(CustomRoles.Pestilence))) { - dis = Vector2.Distance(puppeteerPos, target.transform.position); + dis = Utils.GetDistance(puppeteerPos, target.transform.position); targetDistance.Add(target.PlayerId, dis); } } diff --git a/Roles/Impostor/RiftMaker.cs b/Roles/Impostor/RiftMaker.cs index 41f1d4e847..354988cca2 100644 --- a/Roles/Impostor/RiftMaker.cs +++ b/Roles/Impostor/RiftMaker.cs @@ -160,12 +160,12 @@ private static void DoRifts(PlayerControl shapeshifter, PlayerControl target) var currentPos = shapeshifter.GetCustomPosition(); var totalMarked = MarkedLocation[shapeshifterId].Count; - if (totalMarked == 1 && Vector2.Distance(currentPos, MarkedLocation[shapeshifterId][0]) <= 5f) + if (totalMarked == 1 && Utils.GetDistance(currentPos, MarkedLocation[shapeshifterId][0]) <= 5f) { shapeshifter.Notify(GetString("RiftsTooClose")); return; } - else if (totalMarked == 2 && Vector2.Distance(currentPos, MarkedLocation[shapeshifterId][1]) <= 5f) + else if (totalMarked == 2 && Utils.GetDistance(currentPos, MarkedLocation[shapeshifterId][1]) <= 5f) { shapeshifter.Notify(GetString("RiftsTooClose")); return; @@ -214,11 +214,11 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) Vector2 position = player.GetCustomPosition(); Vector2 TPto; - if (Vector2.Distance(position, MarkedLocation[playerId][0]) <= RiftRadius.GetFloat()) + if (Utils.GetDistance(position, MarkedLocation[playerId][0]) <= RiftRadius.GetFloat()) { TPto = MarkedLocation[playerId][1]; } - else if (Vector2.Distance(position, MarkedLocation[playerId][1]) <= RiftRadius.GetFloat()) + else if (Utils.GetDistance(position, MarkedLocation[playerId][1]) <= RiftRadius.GetFloat()) { TPto = MarkedLocation[playerId][0]; } diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index 225478368b..f84d601c08 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -108,7 +108,7 @@ public override void OnShapeshift(PlayerControl shapeshifter, PlayerControl targ if (Pelican.IsEaten(p.PlayerId) || Medic.ProtectList.Contains(p.PlayerId)) continue; if (p.Is(CustomRoles.Glitch) || p.Is(CustomRoles.Pestilence)) continue; - dis = Vector2.Distance(cppos, p.transform.position); + dis = Utils.GetDistance(cppos, p.transform.position); cpdistance.Add(p, dis); Logger.Info($"{p?.Data?.PlayerName} distance: {dis}", "Warlock"); } diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index b323f7e0d8..cc0d123d07 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -151,7 +151,7 @@ private void OnFixedUpdateOthers(PlayerControl player) { if (target.PlayerId != player.PlayerId && target.PlayerId != LastBombedPlayer) { - dis = Vector2.Distance(playerPos, target.transform.position); + dis = Utils.GetDistance(playerPos, target.transform.position); targetDistance.Add(target.PlayerId, dis); } } diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index d55f486e98..63fd0deef2 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -152,7 +152,7 @@ public override void OnFixedUpdate(PlayerControl player) else { float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(Reach.IsReach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - float distance = Vector2.Distance(player.GetCustomPosition(), arTarget.GetCustomPosition()); + float distance = GetDistance(player.GetCustomPosition(), arTarget.GetCustomPosition()); if (distance <= range) { diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 77c07bcef4..fd5db62e5a 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -147,7 +147,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (player == killer) continue; if (player == target) continue; - if (Vector2.Distance(killer.transform.position, player.transform.position) <= Bomber.BomberRadius.GetFloat()) + if (Utils.GetDistance(killer.transform.position, player.transform.position) <= Bomber.BomberRadius.GetFloat()) { Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Bombed; player.RpcMurderPlayer(player); diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index 113dbad67c..2d6c6f66ca 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -279,7 +279,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl pelican) if (target == null) continue; var pos = GetBlackRoomPSForPelican(); - var dis = Vector2.Distance(pos, target.GetCustomPosition()); + var dis = Utils.GetDistance(pos, target.GetCustomPosition()); if (dis < 1f) continue; target.RpcTeleport(pos, sendInfoInLogs: false); diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs index fd5082514f..f34c278997 100644 --- a/Roles/Neutral/Revolutionist.cs +++ b/Roles/Neutral/Revolutionist.cs @@ -238,7 +238,7 @@ private static void OnFixUpdateOthers(PlayerControl player) // jesus christ else { float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(Reach.IsReach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - float dis = Vector2.Distance(player.GetCustomPosition(), rv_target.GetCustomPosition()); + float dis = GetDistance(player.GetCustomPosition(), rv_target.GetCustomPosition()); if (dis <= range) { RevolutionistTimer[playerId] = (rv_target, rv_time + Time.fixedDeltaTime); diff --git a/Roles/Neutral/Shroud.cs b/Roles/Neutral/Shroud.cs index 150ccab7b0..025bbfadb9 100644 --- a/Roles/Neutral/Shroud.cs +++ b/Roles/Neutral/Shroud.cs @@ -115,7 +115,7 @@ private void OnFixedUpdateOthers(PlayerControl shroud) { if (target.PlayerId != shroud.PlayerId && !target.Is(CustomRoles.Shroud) && !target.IsTransformedNeutralApocalypse()) { - dis = Vector2.Distance(shroudPos, target.transform.position); + dis = Utils.GetDistance(shroudPos, target.transform.position); targetDistance.Add(target.PlayerId, dis); } } diff --git a/Roles/Neutral/Solsticer.cs b/Roles/Neutral/Solsticer.cs index d48c199d5b..101e8b6bab 100644 --- a/Roles/Neutral/Solsticer.cs +++ b/Roles/Neutral/Solsticer.cs @@ -180,7 +180,7 @@ public override void OnFixedUpdate(PlayerControl pc) Count = 15; var pos = ExtendedPlayerControl.GetBlackRoomPosition(); - var dis = Vector2.Distance(pos, pc.GetCustomPosition()); + var dis = Utils.GetDistance(pos, pc.GetCustomPosition()); if (dis < 1f) return; diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index d9fffe4354..3e94b38300 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -242,7 +242,7 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe foreach (var pc in Main.AllAlivePlayerControls) { if (pc.PlayerId == target.PlayerId) continue; - var dis = Vector2.Distance(pc.transform.position, pos); + var dis = GetDistance(pc.transform.position, pos); if (dis < minDis && dis < 1.5f) { minDis = dis; diff --git a/Roles/Neutral/Werewolf.cs b/Roles/Neutral/Werewolf.cs index 2e3d14fb62..b4233da647 100644 --- a/Roles/Neutral/Werewolf.cs +++ b/Roles/Neutral/Werewolf.cs @@ -60,7 +60,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (player.IsTransformedNeutralApocalypse()) continue; else if ((player.Is(CustomRoles.NiceMini) || player.Is(CustomRoles.EvilMini)) && Mini.Age < 18) continue; - if (Vector2.Distance(killer.transform.position, player.transform.position) <= MaulRadius.GetFloat()) + if (Utils.GetDistance(killer.transform.position, player.transform.position) <= MaulRadius.GetFloat()) { player.SetDeathReason(PlayerState.DeathReason.Mauled); player.RpcMurderPlayer(player); From ee0b2cfbc8b4ca2c92af5cfb72d116b9804b448b Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:22:05 +0200 Subject: [PATCH 272/778] stuff --- Modules/ExtendedPlayerControl.cs | 4 ++++ Resources/Lang/en_US.json | 3 ++- Roles/AddOns/Common/Rebirth.cs | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index ca4981b60d..5b8fc782a4 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -826,6 +826,10 @@ public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, Radar.Remove(Killed.PlayerId); Radar.Add(target.PlayerId); break; + case CustomRoles.Rebirth: + Rebirth.Remove(Killed.PlayerId); + Rebirth.Add(target.PlayerId); + break; } } public static bool RpcCheckAndMurder(this PlayerControl killer, PlayerControl target, bool check = false) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index a230670e46..b87c29d6c0 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -955,7 +955,7 @@ "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with someone and thrive once more.", + "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with someone and thrive once more. \n\nNotice: Rebirth will be removed from you wif you exhausted all your rebirths.", "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", @@ -1482,6 +1482,7 @@ "SheriffMadCanKillNeutral": "Can kill Neutrals", "SheriffMadCanKillCrew": "Can kill Crewmates", + "RebirthUses": "Amount of Rebirths", "RebirthFailed": "Ahh, how unfortunate, you did not find any viable souls to swap bodies with", "ReverieIncreaseKillCooldown": "Increase kill cooldown", diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 91d11ed09e..304a57e654 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -22,6 +22,11 @@ public static void Add(byte Playerid) { Rebirths[Playerid] = RebirthUses.GetInt(); } + public static void Remove(byte Playerid) + { + Rebirths.Remove(Playerid); + } + public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiledPlayer) { NewExiledPlayer = default; From 99acc5e8e85eccee113094908a2b3440addb37a6 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 15:33:35 +0800 Subject: [PATCH 273/778] Fix ID --- Modules/OptionHolder.cs | 2 +- Roles/(Ghosts)/Impostor/Possessor.cs | 2 +- Roles/Impostor/DoubleAgent.cs | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 9510d3a83c..d0781b3a1c 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -621,7 +621,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 28700 last id for roles/add-ons (Next use 28800) + // 29000 last id for roles/add-ons (Next use 29100) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Roles/(Ghosts)/Impostor/Possessor.cs b/Roles/(Ghosts)/Impostor/Possessor.cs index 5a6176706d..87f63c9a93 100644 --- a/Roles/(Ghosts)/Impostor/Possessor.cs +++ b/Roles/(Ghosts)/Impostor/Possessor.cs @@ -9,7 +9,7 @@ namespace TOHE.Roles._Ghosts_.Impostor; internal class Possessor : RoleBase { //===========================SETUP================================\\ - private const int Id = 29800; + private const int Id = 28900; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Possessor); public override CustomRoles ThisRoleBase => CustomRoles.GuardianAngel; public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorGhosts; diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 772a0af522..6f8f11f7b7 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -13,7 +13,7 @@ namespace TOHE.Roles.Impostor; internal class DoubleAgent : RoleBase { //===========================SETUP================================\\ - private const int Id = 28600; + private const int Id = 29000; private static readonly List playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; @@ -124,8 +124,11 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) public override bool CheckVote(PlayerControl voter, PlayerControl target) { - if (voter.IsModClient()) return true; - if (!CanBombInMeeting) return true; + if (voter.IsModClient() || !CanBombInMeeting) + { + voter.GetRoleClass().HasVoted = true; + return true; + } if (!BombIsActive) { From 2c563e7a1b4bbc5f70135a8418f75bc37ad9f1cd Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:39:05 +0200 Subject: [PATCH 274/778] force --- Roles/AddOns/Common/Rebirth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 304a57e654..9efb7d8456 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -42,8 +42,8 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled return false; } Rebirths[pc.PlayerId]--; - pc.ResetPlayerOutfit(Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, ViablePlayer.Data.PlayerLevel); - ViablePlayer.ResetPlayerOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit, pc.Data.PlayerLevel); + pc.ResetPlayerOutfit(Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, ViablePlayer.Data.PlayerLevel, true); + ViablePlayer.ResetPlayerOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit, pc.Data.PlayerLevel, true); NewExiledPlayer = ViablePlayer.Data; if (Rebirths[pc.PlayerId] <= 0) { From 5137704190ade4864c8f75a34016fb64b775dcce Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 15:49:45 +0800 Subject: [PATCH 275/778] Fix bug --- Roles/Impostor/DoubleAgent.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 6f8f11f7b7..b7d408c3ea 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -124,11 +124,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) public override bool CheckVote(PlayerControl voter, PlayerControl target) { - if (voter.IsModClient() || !CanBombInMeeting) - { - voter.GetRoleClass().HasVoted = true; - return true; - } + if (voter.IsModClient() || !CanBombInMeeting)return true; if (!BombIsActive) { @@ -146,7 +142,11 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) // Clear active bombed players on meeting call if ClearBombedOnMeetingCall is enabled. public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - + if (_Player != null && (_Player.AmOwner || _Player.IsModClient())) + { + HasVoted = true; + } + if (BombIsActive && ClearBombedOnMeetingCall.GetBool()) { ClearBomb(); From b3c46481d4a7231c1bbb1c3b9b29430fa2a97b50 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 15:50:23 +0800 Subject: [PATCH 276/778] hm --- Roles/Impostor/DoubleAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index b7d408c3ea..42c37ee7c7 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -124,7 +124,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) public override bool CheckVote(PlayerControl voter, PlayerControl target) { - if (voter.IsModClient() || !CanBombInMeeting)return true; + if (voter.IsModClient() || !CanBombInMeeting) return true; if (!BombIsActive) { From 273b0801cc749a3665d4a4ad4e27ca8224768681 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 10:05:02 +0200 Subject: [PATCH 277/778] fix id --- Roles/AddOns/Common/Rebirth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 9efb7d8456..5ab780b518 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -8,7 +8,7 @@ namespace TOHE.Roles.AddOns.Common; public class Rebirth : IAddon { - private const int Id = 29300; + private const int Id = 29500; public AddonTypes Type => AddonTypes.Helpful; public static OptionItem RebirthUses; public static Dictionary Rebirths = []; From cc146361e924ed8d0bd2e604a46d0657889b05ba Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 16:05:12 +0800 Subject: [PATCH 278/778] Change ID & Some changes --- Modules/OptionHolder.cs | 2 +- Roles/Impostor/YinYanger.cs | 161 ++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 83 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index d0781b3a1c..a78047b462 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -621,7 +621,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29000 last id for roles/add-ons (Next use 29100) + // 29500 last id for roles/add-ons (Next use 29600) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index e7fc076f9e..e71b76239b 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -1,103 +1,100 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using static TOHE.Utils; -using UnityEngine; +using UnityEngine; +using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; -using TOHE.Roles.Core; +using static TOHE.Utils; + +namespace TOHE.Roles.Impostor; -namespace TOHE.Roles.Impostor +internal class YinYanger : RoleBase { - internal class YinYanger : RoleBase - { - //===========================SETUP================================\\ - const int Id = 29200; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.YinYanger); - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorKilling; - //==================================================================\\ + //===========================SETUP================================\\ + const int Id = 29100; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.YinYanger); + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorKilling; + //==================================================================\\ - public static OptionItem KillCooldown; - public static Dictionary Yanged = []; + public static OptionItem KillCooldown; + public static Dictionary Yanged = []; - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.YinYanger); - KillCooldown = FloatOptionItem.Create(Id + 2, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) - .SetValueFormat(OptionFormat.Seconds); - } - public override void Init() - { - Yanged.Clear(); - } - public override void Add(byte playerId) + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.YinYanger); + KillCooldown = FloatOptionItem.Create(Id + 2, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) + .SetValueFormat(OptionFormat.Seconds); + } + public override void Init() + { + Yanged.Clear(); + } + public override void Add(byte playerId) + { + Yanged[playerId] = new(); + } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + private static bool CheckAvailability() + { + var tocheck = Main.AllAlivePlayerControls.Length - Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.YinYanger)); + var result = Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.YinYanger)) * 2; + return tocheck >= result; + } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + var (yin, yang) = Yanged[killer.PlayerId]; + if (yin && yang || !CheckAvailability()) return true; + if (Yanged.Where(x => x.Key != killer.PlayerId).Any(x => x.Value.yin == target || x.Value.yang == target)) { - Yanged[playerId] = new(); - } - public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - private static bool CheckAvailability() { - var tocheck = Main.AllAlivePlayerControls.Length - Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count(); - var result = (Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.YinYanger)).Count() * 2); - return tocheck >= result; + killer.Notify(string.Format(GetString("YinYangerAlreadyMarked"), target.GetRealName(clientData: true))); + return false; } - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + + if (yin) { - var (yin, yang) = Yanged[killer.PlayerId]; - if (yin && yang || !CheckAvailability()) return true; - if (Yanged.Where(x => x.Key != killer.PlayerId).Any(x => x.Value.yin == target || x.Value.yang == target)) - { - killer.Notify(string.Format(GetString("YinYangerAlreadyMarked"), target.GetRealName(clientData: true))); + if (target.PlayerId == yin.PlayerId) return false; - } - - if (yin) - { - if (target.PlayerId == yin.PlayerId) - return false; - - Yanged[killer.PlayerId] = (yin, target); - } - else - { - Yanged[killer.PlayerId] = (target, yang); - } + Yanged[killer.PlayerId] = (yin, target); - killer.SetKillCooldown(); - return false; - } - public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) - { - Yanged[_state.PlayerId] = new(); } - public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + else { - if (Yanged.TryGetValue(DeadPlayer.PlayerId, out _)) - Yanged[DeadPlayer.PlayerId] = new(); + Yanged[killer.PlayerId] = (target, yang); } - public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) - { - var (yin, yang) = Yanged[seer.PlayerId]; - Color col = seen.PlayerId == yin?.PlayerId ? Color.white : new Color32(46, 46, 46, 255); - return seen.PlayerId == yin?.PlayerId || seen.PlayerId == yang?.PlayerId ? ColorString(col, "☯") : string.Empty; - } - public override void OnFixedUpdate(PlayerControl pc) - { - var (yin, yang) = Yanged[pc.PlayerId]; - if (!yin || !yang) return; + killer.SetKillCooldown(); + return false; + } + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + Yanged[_state.PlayerId] = new(); + } + public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + { + if (Yanged.TryGetValue(DeadPlayer.PlayerId, out _)) + Yanged[DeadPlayer.PlayerId] = new(); + } + public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) + { + var (yin, yang) = Yanged[seer.PlayerId]; + Color col = seen.PlayerId == yin?.PlayerId ? Color.white : new Color32(46, 46, 46, 255); + + return seen.PlayerId == yin?.PlayerId || seen.PlayerId == yang?.PlayerId ? ColorString(col, "☯") : string.Empty; + } + public override void OnFixedUpdate(PlayerControl pc) + { + var (yin, yang) = Yanged[pc.PlayerId]; + if (!yin || !yang) return; - if (Vector2.Distance(yin.GetCustomPosition(), yang.GetCustomPosition()) < 1.5f) - { - yin.SetDeathReason(PlayerState.DeathReason.Equilibrium); - yin.RpcMurderPlayer(yang); + if (Utils.GetDistance(yin.GetCustomPosition(), yang.GetCustomPosition()) < 1.5f) + { + yin.SetDeathReason(PlayerState.DeathReason.Equilibrium); + yin.RpcMurderPlayer(yang); - yang.SetDeathReason(PlayerState.DeathReason.Equilibrium); - yang.RpcMurderPlayer(yin); - Yanged[pc.PlayerId] = new(); - } + yang.SetDeathReason(PlayerState.DeathReason.Equilibrium); + yang.RpcMurderPlayer(yin); + Yanged[pc.PlayerId] = new(); } - } + } From e2135085b82f7e0e1f41954c68cc174a4789e447 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 16:08:00 +0800 Subject: [PATCH 279/778] Change AllPlayerControls (From EHR) --- main.cs | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/main.cs b/main.cs index 1d54bcd980..4db600be01 100644 --- a/main.cs +++ b/main.cs @@ -184,8 +184,45 @@ public class Main : BasePlugin public static int BardCreations = 0; public static int MeetingsPassed = 0; - public static PlayerControl[] AllPlayerControls => PlayerControl.AllPlayerControls.ToArray().Where(p => p != null).ToArray(); - public static PlayerControl[] AllAlivePlayerControls => PlayerControl.AllPlayerControls.ToArray().Where(p => p != null && p.IsAlive() && !p.Data.Disconnected && !Pelican.IsEaten(p.PlayerId)).ToArray(); + public static PlayerControl[] AllPlayerControls + { + get + { + int count = PlayerControl.AllPlayerControls.Count; + var result = new PlayerControl[count]; + int i = 0; + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (pc == null || pc.PlayerId == 255) continue; + result[i++] = pc; + } + + if (i == 0) return []; + + Array.Resize(ref result, i); + return result; + } + } + + public static PlayerControl[] AllAlivePlayerControls + { + get + { + int count = PlayerControl.AllPlayerControls.Count; + var result = new PlayerControl[count]; + int i = 0; + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (pc == null || pc.PlayerId == 255 || !pc.IsAlive() || pc.Data.Disconnected || Pelican.IsEaten(pc.PlayerId)) continue; + result[i++] = pc; + } + + if (i == 0) return []; + + Array.Resize(ref result, i); + return result; + } + } public static Main Instance; From d8d5aef81abf256cac07366bb659b66ab8b97d83 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 17 Aug 2024 16:26:31 +0800 Subject: [PATCH 280/778] Change --- Patches/OutroPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 2c3ae590b5..97af79c4e1 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -240,9 +240,9 @@ public static void Postfix(EndGameManager __instance) break; case CustomWinner.NiceMini: // __instance.WinText.color = Utils.GetRoleColor(CustomRoles.Mini); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Mini); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.NiceMini); // WinnerText.text = GetString("NiceMiniDied"); - WinnerText.color = Utils.GetRoleColor(CustomRoles.Mini); + WinnerText.color = Utils.GetRoleColor(CustomRoles.NiceMini); break; case CustomWinner.Neutrals: __instance.WinText.text = GetString("DefeatText"); From 4010a95a82fae99006a940fa61ef7c7a2bb42371 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 19:54:39 +0200 Subject: [PATCH 281/778] finish? --- Modules/Camouflague.cs | 14 +++++++++----- Patches/OutroPatch.cs | 6 +++--- Patches/PlayerControlPatch.cs | 4 ++-- Patches/onGameStartedPatch.cs | 1 + Roles/AddOns/Common/Rebirth.cs | 5 +++++ Roles/Impostor/Blackmailer.cs | 2 +- Roles/Neutral/Doppelganger.cs | 11 +++-------- TOHE.csproj | 2 +- main.cs | 2 ++ 9 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index 04531981b2..c0e5f56e3b 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -188,17 +188,21 @@ public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bo id = Main.ShapeshiftTarget[id]; } - // if game not end and Doppelganger clone skins - if (!GameEnd && Doppelganger.HasEnabled && Doppelganger.DoppelPresentSkin.TryGetValue(id, out var playerOutfit)) + + bool Hasovveride = Main.OvverideOutfit.TryGetValue(id, out var RealOutfit); + + // if game not end and Something clone skins + if (!GameEnd && Hasovveride) { - newOutfit = playerOutfit; + Logger.Info($"{RealOutfit.outfit.SkinId}", "RealOutfit Check"); + newOutfit = RealOutfit.outfit; } else { // if game end, set normal name - if (GameEnd && Doppelganger.DoppelVictim.TryGetValue(id, out var playerName)) + if (GameEnd && Hasovveride) { - Utils.GetPlayerById(id)?.RpcSetName(playerName); + Utils.GetPlayerById(id)?.RpcSetName(RealOutfit.name); } // Set Outfit diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 2c3ae590b5..668a155aad 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -60,13 +60,13 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En foreach (var id in Main.PlayerStates.Keys.ToArray()) { - if (Doppelganger.HasEnabled && Doppelganger.DoppelVictim.TryGetValue(id, out var playerName)) + if (Main.OvverideOutfit.TryGetValue(id, out var RealOutfit)) { var dpc = Utils.GetPlayerById(id); if (dpc != null) { - dpc.RpcSetName(playerName); - Main.AllPlayerNames[id] = playerName; + dpc.RpcSetName(RealOutfit.name); + Main.AllPlayerNames[id] = RealOutfit.name; } } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index de07f51b6f..2cea04b8f4 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -393,7 +393,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC } } - if (!target.IsProtected() && !Doppelganger.CheckDoppelVictim(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId)) + if (!target.IsProtected() && !Doppelganger.CheckDoppelVictim(target.PlayerId) && !Main.OvverideOutfit.ContainsKey(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId)) { Camouflage.ResetSkinAfterDeathPlayers.Add(target.PlayerId); Camouflage.RpcSetSkin(target, ForceRevert: true, RevertToDefault: true); @@ -965,7 +965,7 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta foreach (var pc in Main.AllPlayerControls) { - if (!Doppelganger.CheckDoppelVictim(pc.PlayerId)) + if (!Main.OvverideOutfit.ContainsKey(pc.PlayerId)) { // Update skins again, since players have different skins // And can be easily distinguished from each other diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index a3ba566f89..604713d345 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -72,6 +72,7 @@ public static void Postfix(AmongUsClient __instance) Main.AllKillers.Clear(); Main.OverDeadPlayerList.Clear(); Main.UnShapeShifter.Clear(); + Main.OvverideOutfit.Clear(); Main.GameIsLoaded = false; Utils.LateExileTask.Clear(); diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 5ab780b518..d9657a9387 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -43,8 +43,13 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled } Rebirths[pc.PlayerId]--; pc.ResetPlayerOutfit(Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, ViablePlayer.Data.PlayerLevel, true); + Main.OvverideOutfit[pc.PlayerId] = (Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit.PlayerName); + ViablePlayer.ResetPlayerOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit, pc.Data.PlayerLevel, true); + Main.OvverideOutfit[ViablePlayer.PlayerId] = (Main.PlayerStates[pc.PlayerId].NormalOutfit, Main.PlayerStates[pc.PlayerId].NormalOutfit.PlayerName); + NewExiledPlayer = ViablePlayer.Data; + if (Rebirths[pc.PlayerId] <= 0) { Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Rebirth); diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 90723cc4a6..6f6f5d088b 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -84,7 +84,7 @@ public override void OnOthersMeetingHudStart(PlayerControl pc) if (CheckBlackmaile(pc)) { var playername = pc.GetRealName(isMeeting: true); - if (Doppelganger.DoppelVictim.TryGetValue(pc.PlayerId, out var doppelPlayerName)) playername = doppelPlayerName; + if (Main.OvverideOutfit.TryGetValue(pc.PlayerId, out var realfit)) playername = realfit.name; AddMsg(string.Format(string.Format(GetString("BlackmailerDead"), playername), byte.MaxValue, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Blackmailer), GetString("BlackmaileKillTitle")))); } } diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index be713820a1..4b02768ec8 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -22,9 +22,6 @@ internal class Doppelganger : RoleBase private static OptionItem HasImpostorVision; private static OptionItem MaxSteals; - public static readonly Dictionary DoppelVictim = []; - public static readonly Dictionary DoppelPresentSkin = []; - public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doppelganger, 1, zeroOne: false); @@ -39,13 +36,12 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = MaxSteals.GetInt(); - DoppelVictim[playerId] = Utils.GetPlayerById(playerId).GetRealName() ?? "Invalid"; } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - public static bool CheckDoppelVictim(byte playerId) => DoppelVictim.ContainsKey(playerId); + public static bool CheckDoppelVictim(byte playerId) => Main.OvverideOutfit.ContainsKey(playerId); public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { @@ -73,14 +69,13 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t .Set(tname, target.CurrentOutfit.ColorId, target.CurrentOutfit.HatId, target.CurrentOutfit.SkinId, target.CurrentOutfit.VisorId, target.CurrentOutfit.PetId, target.CurrentOutfit.NamePlateId); var targetLvl = target.Data.PlayerLevel; - DoppelVictim[target.PlayerId] = tname; target.SetNewOutfit(killerSkin, newLevel: killerLvl); - DoppelPresentSkin[target.PlayerId] = killerSkin; + Main.OvverideOutfit[target.PlayerId] = (killerSkin, Main.PlayerStates[killer.PlayerId].NormalOutfit.PlayerName); Logger.Info("Changed target skin", "Doppelganger"); killer.SetNewOutfit(targetSkin, newLevel: targetLvl); - DoppelPresentSkin[killer.PlayerId] = targetSkin; + Main.OvverideOutfit[killer.PlayerId] = (targetSkin, Main.PlayerStates[target.PlayerId].NormalOutfit.PlayerName); Logger.Info("Changed killer skin", "Doppelganger"); SendSkillRPC(); diff --git a/TOHE.csproj b/TOHE.csproj index ca5573ec1e..bc11608bc9 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -7,7 +7,7 @@ Town Of Host Enhanced Moe preview - + C:\Program Files\Epic Games\AmongUs Debug;Release;Canary true True diff --git a/main.cs b/main.cs index 59f4aa1ee6..44e35701f0 100644 --- a/main.cs +++ b/main.cs @@ -154,6 +154,7 @@ public class Main : BasePlugin public static readonly Dictionary AllPlayerSpeed = []; public static readonly HashSet PlayersDiedInMeeting = []; public static readonly Dictionary AllKillers = []; + public static readonly Dictionary OvverideOutfit = []; public static readonly Dictionary CheckShapeshift = []; public static readonly Dictionary ShapeshiftTarget = []; @@ -183,6 +184,7 @@ public class Main : BasePlugin public static int MadmateNum = 0; public static int BardCreations = 0; public static int MeetingsPassed = 0; + public static PlayerControl[] AllPlayerControls => PlayerControl.AllPlayerControls.ToArray().Where(p => p != null).ToArray(); public static PlayerControl[] AllAlivePlayerControls => PlayerControl.AllPlayerControls.ToArray().Where(p => p != null && p.IsAlive() && !p.Data.Disconnected && !Pelican.IsEaten(p.PlayerId)).ToArray(); From c4e538e04bf8bfbf63c0a00db5bd6b9aa1a0e496 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 19:57:55 +0200 Subject: [PATCH 282/778] fix --- Roles/AddOns/Common/Rebirth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index d9657a9387..2b6b816cde 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -31,9 +31,9 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled { NewExiledPlayer = default; if (!pc.Is(CustomRoles.Rebirth)) return false; - var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc) + var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && -/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal))); +/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer))); if (ViablePlayer == null) { From 1848490283b52160ff35b9e9a15729ef0b29e301 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:00:52 +0200 Subject: [PATCH 283/778] add more --- Roles/AddOns/Common/Rebirth.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 2b6b816cde..274ab026d2 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -3,6 +3,7 @@ using static TOHE.Translator; using TOHE.Modules; using static UnityEngine.GraphicsBuffer; +using TOHE.Roles.Neutral; namespace TOHE.Roles.AddOns.Common; @@ -33,7 +34,8 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled if (!pc.Is(CustomRoles.Rebirth)) return false; var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && -/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer))); +/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer)) && + !x.Is(CustomRoles.Lovers) && x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); if (ViablePlayer == null) { From 69227e498ca02a4148ddae85bace88edba9da5c9 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:01:10 +0200 Subject: [PATCH 284/778] fix --- Roles/AddOns/Common/Rebirth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 274ab026d2..6ceffd0a0c 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -35,7 +35,7 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && /*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer)) && - !x.Is(CustomRoles.Lovers) && x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); + !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); if (ViablePlayer == null) { From cee06b66ac320dba661cff29b665f52ab70073cc Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:01:32 +0200 Subject: [PATCH 285/778] more fix --- Roles/AddOns/Common/Rebirth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 6ceffd0a0c..a25fa9c5fe 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -34,7 +34,7 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled if (!pc.Is(CustomRoles.Rebirth)) return false; var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && -/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infected) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer)) && +/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer)) && !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); if (ViablePlayer == null) From 21ff69545c19eb566321c4aea332ca11300ac55d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:09:46 +0200 Subject: [PATCH 286/778] remove subrole RPC --- Modules/GameState.cs | 8 ++++++++ Modules/RPC.cs | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 2585734164..b53fe6d194 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -8,6 +8,7 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Impostor; using static TOHE.Utils; +using Hazel; namespace TOHE; @@ -217,6 +218,13 @@ public void RemoveSubRole(CustomRoles role) { if (SubRoles.Contains(role)) SubRoles.Remove(role); + + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveSubRole, SendOption.Reliable); + writer.Write(PlayerId); + writer.WritePacked((int)role); + writer.EndMessage(); } public void SetDead() diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 37297e60ec..41b05c4da6 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -50,6 +50,7 @@ enum CustomRPC : byte // 197/255 USED SyncPlayerSetting, ShowChat, SyncShieldPersonDiedFirst, + RemoveSubRole, //Roles SetBountyTarget, @@ -334,6 +335,12 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c } break; + case CustomRPC.RemoveSubRole: + byte targetId = reader.ReadByte(); + var Subrole = (CustomRoles)reader.ReadPackedInt32(); + Main.PlayerStates[targetId].RemoveSubRole(Subrole); + break; + case CustomRPC.SetDeathReason: RPC.GetDeathReason(reader); break; From 19d33c9cefac02de1f660a41e6abe9460a65a868 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:11:43 +0200 Subject: [PATCH 287/778] fix --- Patches/MeetingHudPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index c2dc3f5ca8..7aa3da6d4a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -353,6 +353,7 @@ public static bool Prefix(MeetingHud __instance) } else if (exiledPlayer?.Object.Is(CustomRoles.Rebirth) == true && Rebirth.SwapSkins(exiledPlayer.Object, out var NewExiled)) { + exileId = NewExiled.PlayerId; exiledPlayer = NewExiled; } From abacc50ac1cdc16479ff15d58b16779b364f01fd Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:14:09 +0200 Subject: [PATCH 288/778] fix error --- Modules/RPC.cs | 4 ++-- TOHE.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 41b05c4da6..a9dea737a3 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -466,9 +466,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c EvilTracker.ReceiveRPC(reader); break; case CustomRPC.SetRealKiller: - byte targetId = reader.ReadByte(); + byte tarid = reader.ReadByte(); byte killerId = reader.ReadByte(); - RPC.SetRealKiller(targetId, killerId); + RPC.SetRealKiller(tarid, killerId); break; //case CustomRPC.SetTrackerTarget: // Tracker.ReceiveRPC(reader); diff --git a/TOHE.csproj b/TOHE.csproj index bc11608bc9..ca5573ec1e 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -7,7 +7,7 @@ Town Of Host Enhanced Moe preview - C:\Program Files\Epic Games\AmongUs + Debug;Release;Canary true True From 4dfa525d5a14a4c46c2b0f6db466484365390c11 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:27:41 +0200 Subject: [PATCH 289/778] fix grammar --- Resources/Lang/en_US.json | 2 +- Roles/AddOns/Common/Rebirth.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 24d446a10b..6fd76973f5 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -964,7 +964,7 @@ "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with someone and thrive once more. \n\nNotice: Rebirth will be removed from you wif you exhausted all your rebirths.", + "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with someone and thrive once more. \n\nNotice: Rebirth will be removed from you if you exhausted all your rebirths.", "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index a25fa9c5fe..6414a096f8 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -51,7 +51,7 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled Main.OvverideOutfit[ViablePlayer.PlayerId] = (Main.PlayerStates[pc.PlayerId].NormalOutfit, Main.PlayerStates[pc.PlayerId].NormalOutfit.PlayerName); NewExiledPlayer = ViablePlayer.Data; - + _ = new LateTask(() => Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc), 3f); if (Rebirths[pc.PlayerId] <= 0) { Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Rebirth); From a8d89a84f7d3f67489f4976a63f269d0bcab3a2f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 08:31:13 +0200 Subject: [PATCH 290/778] lol forgot to remopve this --- Roles/AddOns/Common/Rebirth.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 6414a096f8..99c30f1f00 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -51,7 +51,6 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled Main.OvverideOutfit[ViablePlayer.PlayerId] = (Main.PlayerStates[pc.PlayerId].NormalOutfit, Main.PlayerStates[pc.PlayerId].NormalOutfit.PlayerName); NewExiledPlayer = ViablePlayer.Data; - _ = new LateTask(() => Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc), 3f); if (Rebirths[pc.PlayerId] <= 0) { Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Rebirth); From 2b1794e5150a3bf6cb0368cb738336603e066e24 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 19 Aug 2024 18:23:08 +0800 Subject: [PATCH 291/778] Haunt Menu in normal game --- Patches/HauntMenuMinigamePatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/HauntMenuMinigamePatch.cs b/Patches/HauntMenuMinigamePatch.cs index 96f0905323..e7307ac8b5 100644 --- a/Patches/HauntMenuMinigamePatch.cs +++ b/Patches/HauntMenuMinigamePatch.cs @@ -5,7 +5,7 @@ public static class HauntMenuMinigameSetFilterTextPatch { public static bool Prefix(HauntMenuMinigame __instance) { - if (__instance.HauntTarget != null && Options.GhostCanSeeOtherRoles.GetBool()) + if (__instance.HauntTarget != null && Options.GhostCanSeeOtherRoles.GetBool() && GameStates.IsNormalGame) { // Override job title display with custom role name __instance.FilterText.text = Utils.GetDisplayRoleAndSubName(PlayerControl.LocalPlayer.PlayerId, __instance.HauntTarget.PlayerId, false); From 4c84ea797c05e95d59e3373a9d0994f6904e941b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 19 Aug 2024 18:31:58 +0800 Subject: [PATCH 292/778] Fix Badit steals statue --- Roles/AddOns/Common/Statue.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index c5ef9891c3..f3a75e9349 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -32,15 +32,20 @@ public static void Init() IsEnable = false; } - public static void Add(byte player) + public static void Add(byte playerId) { - tempSpeed.Add(player, Main.AllPlayerSpeed[player]); + tempSpeed.Add(playerId, Main.AllPlayerSpeed[playerId]); IsEnable = true; } - public static void Remove(byte player) + public static void Remove(byte playerId) { - tempSpeed.Remove(player); + if (Main.AllPlayerSpeed[playerId] == SlowDown.GetFloat()) + { + Main.AllPlayerSpeed[playerId] = Main.AllPlayerSpeed[playerId] - SlowDown.GetFloat() + tempSpeed[playerId]; + Utils.GetPlayerById(playerId)?.MarkDirtySettings(); + } + tempSpeed.Remove(playerId); } public static void AfterMeetingTasks() From 0d42a60d0ca1a73d49af10c31da4333399a63503 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 19 Aug 2024 18:55:06 +0800 Subject: [PATCH 293/778] Fix Main.UnShapeShifter null --- Patches/PlayerControlPatch.cs | 38 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index de07f51b6f..0fc5b91eff 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1186,23 +1186,6 @@ public static Task DoPostfix(PlayerControl __instance) if (GameStates.IsInTask) { - if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) - && !player.IsMushroomMixupActive() && Main.GameIsLoaded) - { // using lowload because it is a pretty long domino of tasks 💀 - Main.UnShapeShifter.Where(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) - .Do(x => - { - var PC = Utils.GetPlayerById(x); - var randomplayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); - PC.RpcShapeshift(randomplayer, false); - PC.RpcRejectShapeshift(); - PC.ResetPlayerOutfit(); - - Logger.Info($"Revert to shapeshifting state for: {player.GetRealName()}", "UnShapeShifer_FixedUpdate"); - }); - } - - CustomRoleManager.OnFixedUpdate(player); if (Main.LateOutfits.TryGetValue(player.PlayerId, out var Method) && !player.CheckCamoflague()) @@ -1231,6 +1214,27 @@ public static Task DoPostfix(PlayerControl __instance) if (Rainbow.isEnabled) Rainbow.OnFixedUpdate(); + + if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + && !player.IsMushroomMixupActive() && Main.GameIsLoaded) + { + foreach (var UnShapeshifterId in Main.UnShapeShifter) + { + var UnShapeshifter = Utils.GetPlayerById(UnShapeshifterId); + if (UnShapeshifter == null) + { + Main.UnShapeShifter.Remove(UnShapeshifterId); + continue; + } + if (UnShapeshifter.CurrentOutfitType == PlayerOutfitType.Shapeshifted) continue; + + var randomPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != UnShapeshifter); + UnShapeshifter.RpcShapeshift(randomPlayer, false); + UnShapeshifter.RpcRejectShapeshift(); + UnShapeshifter.ResetPlayerOutfit(); + Logger.Info($"Revert to shapeshifting state for: {player.GetRealName()}", "UnShapeShifer_FixedUpdate"); + } + } } } } From f5e19cfd4f88474a1334054e5397f158618fe16c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 19 Aug 2024 19:03:13 +0800 Subject: [PATCH 294/778] Fix --- Roles/Impostor/Crewpostor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index ff092e0748..c52f86e458 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -110,7 +110,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount TasksDone[player.PlayerId] = 0; SendRPC(player.PlayerId, TasksDone[player.PlayerId]); - List list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && (CanKillAllies.GetBool() || !x.GetCustomRole().IsImpostorTeam())).ToList(); + List list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && !(x.GetCustomRole() is CustomRoles.NiceMini or CustomRoles.EvilMini) && (CanKillAllies.GetBool() || !x.GetCustomRole().IsImpostorTeam())).ToList(); if (!list.Any()) { From 5d9056c4e9dc21486da5bb6b4080b88c7e001bd2 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:26:40 +0200 Subject: [PATCH 295/778] only voted --- Patches/MeetingHudPatch.cs | 4 ++++ Patches/onGameStartedPatch.cs | 1 + Resources/Lang/en_US.json | 3 ++- Roles/AddOns/Common/Rebirth.cs | 24 +++++++++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 7aa3da6d4a..51211a33a2 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -133,6 +133,10 @@ public static bool Prefix(MeetingHud __instance) { Aware.OnVoted(pc, pva); } + else if (voteTarget.Is(CustomRoles.Rebirth)) + { + Rebirth.CountVotes(voteTarget.PlayerId, pva.TargetPlayerId); + } } } } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 604713d345..61ed7fcff6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -222,6 +222,7 @@ public static void Postfix(AmongUsClient __instance) Statue.Init(); Ghoul.Init(); Rainbow.Init(); + Rebirth.Init(); //FFA FFAManager.Init(); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6fd76973f5..6a1bef70b4 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1353,7 +1353,7 @@ "DollMasterPossessionDuration": "Possession Duration", "DollMasterCanKillAsMainBody": "Can kill as the main body", "DollMasterTargetDiesAfterPossession": "Target dies after possession", - + "DoubleAgentCanDiffuseBombs": "Double Agent can diffuse bombs from other roles", "DoubleAgentClearBombOnMeetingCall": "Diffuse active bomb on meeting call", "DoubleAgentCanUseAbilityInCalledMeeting": "If diffused can use ability in called meeting", @@ -1506,6 +1506,7 @@ "SheriffMadCanKillCrew": "Can kill Crewmates", "RebirthUses": "Amount of Rebirths", + "RebirthCountVotes": "Only rebirth to players who voted for them", "RebirthFailed": "Ahh, how unfortunate, you did not find any viable souls to swap bodies with", "ReverieIncreaseKillCooldown": "Increase kill cooldown", diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 99c30f1f00..2b78f0861e 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -12,27 +12,49 @@ public class Rebirth : IAddon private const int Id = 29500; public AddonTypes Type => AddonTypes.Helpful; public static OptionItem RebirthUses; + public static OptionItem OnlyVoted; + public static Dictionary Rebirths = []; + public static Dictionary> VotedCount = []; public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Rebirth, canSetNum: true, teamSpawnOptions: true); RebirthUses = IntegerOptionItem.Create(Id + 11, "RebirthUses", new(1, 14, 1), 1, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rebirth]) .SetValueFormat(OptionFormat.Times); + OnlyVoted = BooleanOptionItem.Create(Id + 12, "RebirthCountVotes", false, TabGroup.Addons, false); + } + public static void Init() + { + Rebirths.Clear(); + VotedCount.Clear(); } public static void Add(byte Playerid) { Rebirths[Playerid] = RebirthUses.GetInt(); + VotedCount[Playerid] = []; } public static void Remove(byte Playerid) { Rebirths.Remove(Playerid); } + public static void CountVotes(byte playerid, byte voter) + { + if (!VotedCount.ContainsKey(playerid)) return; + + VotedCount[playerid].Add(voter); + } public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiledPlayer) { NewExiledPlayer = default; if (!pc.Is(CustomRoles.Rebirth)) return false; - var ViablePlayer = Main.AllAlivePlayerControls.Where(x => x != pc).Shuffle(IRandom.Instance) + List list = [..Main.AllAlivePlayerControls]; + if (OnlyVoted.GetBool()) + { + list = [..VotedCount[pc.PlayerId].Select(x => GetPlayerById(x))]; + } + + var ViablePlayer = list.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && /*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer)) && !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); From 4fb4d288b22b1e62662705ba1ce21ec9527ba18a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:28:29 +0200 Subject: [PATCH 296/778] fix --- Patches/PlayerControlPatch.cs | 1 + Roles/AddOns/Common/Rebirth.cs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 2cea04b8f4..b2cddc97b4 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -947,6 +947,7 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta Logger.SendInGame($"Error: {error}"); } } + Rebirth.OnReportDeadBody(); // Alchemist & Bloodlust Alchemist.OnReportDeadBodyGlobal(); diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 2b78f0861e..7dd98a67d1 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -44,6 +44,13 @@ public static void CountVotes(byte playerid, byte voter) VotedCount[playerid].Add(voter); } + public static void OnReportDeadBody() + { + foreach(var KvP in VotedCount) + { + KvP.Value.Clear(); + } + } public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiledPlayer) { NewExiledPlayer = default; From af38c4b0d634766ecc7b0bf763e00b9b5c5a43bc Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:02:31 +0200 Subject: [PATCH 297/778] add rpc --- Modules/RPC.cs | 6 ++++++ Roles/AddOns/Common/Spurt.cs | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 37297e60ec..2a4e419f98 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -109,6 +109,7 @@ enum CustomRPC : byte // 197/255 USED SetOverseerRevealedPlayer, SetOverseerTimer, SetCoronerArrow, + SpurtSync, SetCoronerkKillerArrow, SetVultureArrow, SetRadarArrow, @@ -502,6 +503,8 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetMarkedPlayer: Ninja.ReceiveRPC(reader); break; + + case CustomRPC.SetMedicalerProtectList: Medic.ReceiveRPCForProtectList(reader); break; @@ -567,6 +570,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.NemesisRevenge: Nemesis.ReceiveRPC_Custom(reader, __instance); break; + case CustomRPC.SpurtSync: + Spurt.RecieveRPC(reader); + break; case CustomRPC.RetributionistRevenge: Retributionist.ReceiveRPC_Custom(reader, __instance); break; diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 00caebdbb0..55ce7ca600 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -1,5 +1,7 @@ using static TOHE.Options; using UnityEngine; +using Hazel; +using TOHE.Roles.Neutral; namespace TOHE.Roles.AddOns.Common { @@ -32,7 +34,19 @@ public void SetupCustomOption() DisplaysCharge = BooleanOptionItem.Create(id + 9, "EnableSpurtCharge", false, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); } - + private static void Sendrpc(byte playerId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SpurtSync, SendOption.Reliable, -1); + writer.Write(playerId); + writer.Write(Main.AllPlayerSpeed[playerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public static void RecieveRPC(MessageReader reader) + { + byte playerid = reader.ReadByte(); + float speed = reader.ReadSingle(); + Main.AllPlayerSpeed[playerid] = speed; + } public static void Add() { foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) @@ -97,6 +111,7 @@ public void OnFixedUpdate(PlayerControl player) { Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); LastUpdate[player.PlayerId] = now; + Sendrpc(player.PlayerId); } } From 858273e33beb27a9510d0a179a8a24520427ccca Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:03:51 +0200 Subject: [PATCH 298/778] bruh --- Modules/RPC.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 2a4e419f98..7ad30e35da 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -503,8 +503,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetMarkedPlayer: Ninja.ReceiveRPC(reader); break; - - case CustomRPC.SetMedicalerProtectList: Medic.ReceiveRPCForProtectList(reader); break; From bb69db42f2968b3de7627ff698f25bb255e35e27 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:05:37 +0200 Subject: [PATCH 299/778] remove --- Patches/PlayerControlPatch.cs | 2 +- Patches/ShowHostMeetingPatch.cs | 2 +- Roles/Neutral/Doppelganger.cs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b2cddc97b4..9ca9903c5e 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -393,7 +393,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC } } - if (!target.IsProtected() && !Doppelganger.CheckDoppelVictim(target.PlayerId) && !Main.OvverideOutfit.ContainsKey(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId)) + if (!target.IsProtected() && !Main.OvverideOutfit.ContainsKey(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId)) { Camouflage.ResetSkinAfterDeathPlayers.Add(target.PlayerId); Camouflage.RpcSetSkin(target, ForceRevert: true, RevertToDefault: true); diff --git a/Patches/ShowHostMeetingPatch.cs b/Patches/ShowHostMeetingPatch.cs index 3f78b411fd..14213dcbdd 100644 --- a/Patches/ShowHostMeetingPatch.cs +++ b/Patches/ShowHostMeetingPatch.cs @@ -25,7 +25,7 @@ public static void OnDestroy_Postfix() hostName = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.PlayerName; hostColor = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.ColorId; - if (Doppelganger.HasEnabled && Doppelganger.CheckDoppelVictim(AmongUsClient.Instance.GetHost().Character.PlayerId)) + if (Doppelganger.HasEnabled && Main.OvverideOutfit.ContainsKey(AmongUsClient.Instance.GetHost().Character.PlayerId)) { hostName = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.PlayerName; hostColor = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.ColorId; diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index 4b02768ec8..be9d3f5027 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -41,8 +41,6 @@ public override void Add(byte playerId) public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - public static bool CheckDoppelVictim(byte playerId) => Main.OvverideOutfit.ContainsKey(playerId); - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null || Camouflage.IsCamouflage || Camouflager.AbilityActivated || Utils.IsActive(SystemTypes.MushroomMixupSabotage)) return true; From b3aaa37dbcd4c9d232df453a248e52c8e1224e02 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:06:25 +0200 Subject: [PATCH 300/778] lol --- Patches/ShowHostMeetingPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/ShowHostMeetingPatch.cs b/Patches/ShowHostMeetingPatch.cs index 14213dcbdd..e7354026ef 100644 --- a/Patches/ShowHostMeetingPatch.cs +++ b/Patches/ShowHostMeetingPatch.cs @@ -25,7 +25,7 @@ public static void OnDestroy_Postfix() hostName = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.PlayerName; hostColor = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.ColorId; - if (Doppelganger.HasEnabled && Main.OvverideOutfit.ContainsKey(AmongUsClient.Instance.GetHost().Character.PlayerId)) + if (Main.OvverideOutfit.ContainsKey(AmongUsClient.Instance.GetHost().Character.PlayerId)) { hostName = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.PlayerName; hostColor = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.ColorId; From c1855d709c1a0f4553e0e425efbf5b40bb9b7ed1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 20 Aug 2024 00:17:02 +0800 Subject: [PATCH 301/778] Alpha 6 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 34704c0f88..d0d8e73a27 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0814.210.00050"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 5"; + public const string PluginVersion = "2024.0820.210.00060"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 6"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 5 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 6 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From bec885a12057a4c90355d00ab6844fc83aae86f2 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Mon, 19 Aug 2024 20:09:59 +0200 Subject: [PATCH 302/778] fix --- Roles/AddOns/Common/Rebirth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 7dd98a67d1..1e9d817d88 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -21,7 +21,7 @@ public void SetupCustomOption() SetupAdtRoleOptions(Id, CustomRoles.Rebirth, canSetNum: true, teamSpawnOptions: true); RebirthUses = IntegerOptionItem.Create(Id + 11, "RebirthUses", new(1, 14, 1), 1, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rebirth]) .SetValueFormat(OptionFormat.Times); - OnlyVoted = BooleanOptionItem.Create(Id + 12, "RebirthCountVotes", false, TabGroup.Addons, false); + OnlyVoted = BooleanOptionItem.Create(Id + 12, "RebirthCountVotes", false, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rebirth]); } public static void Init() { From e4643b3552e621a42224fbe0370e52ee4b0bd14d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 20 Aug 2024 20:45:03 +0200 Subject: [PATCH 303/778] fix --- Modules/CustomRolesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 75d87d5d4f..82c0cb6e10 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -847,7 +847,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Hangman) || pc.Is(CustomRoles.Stealer) || pc.Is(CustomRoles.Tricky) - || pc.Is(CustomRoles.DoubleAgent)) + || pc.Is(CustomRoles.DoubleAgent) + || pc.Is(CustomRoles.YinYanger)) return false; if (!pc.GetCustomRole().IsImpostor()) return false; From 3ba98400f781ba73b10245fe4d12dbaad8e520ee Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:22:59 +0200 Subject: [PATCH 304/778] stuff ig --- Roles/Impostor/DoubleAgent.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 42c37ee7c7..92ebbae6b5 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -8,6 +8,7 @@ using Hazel; using InnerNet; using System; +using static TOHE.Utils; namespace TOHE.Roles.Impostor; internal class DoubleAgent : RoleBase @@ -134,6 +135,7 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) CurrentBombedTime = 999f; CurrentBombedPlayers.Add(target.PlayerId); BombIsActive = true; + SendMessage(GetString("VoteHasReturned"), voter.PlayerId, title: ColorString(GetRoleColor(CustomRoles.DoubleAgent), string.Format(GetString("VoteAbilityUsed"), GetString("DoubleAgent")))); return false; } return true; @@ -271,7 +273,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo // Set timer for Double Agent Modded Clients. public override string GetLowerText(PlayerControl player, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - if (player == null) return string.Empty; + if (player == null || player != seen || player.IsModClient() && !isForHud) return string.Empty; if (CurrentBombedTime > 0 && CurrentBombedTime < BombExplosionTimer.GetFloat() + 1) return Utils.ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); return string.Empty; } @@ -346,3 +348,4 @@ private static void DestroyButtons(GameObject pressedButton) } // FieryFlower was here ඞ +// Drakos wasn't here, 100% not \ No newline at end of file From 762ef51e3b21003a7c633174fffcf3f3aa3780aa Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:24:34 +0200 Subject: [PATCH 305/778] bracie --- Roles/Impostor/DoubleAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 92ebbae6b5..0c14a0a98c 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -271,7 +271,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo // Set timer for Double Agent Modded Clients. - public override string GetLowerText(PlayerControl player, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + public override string GetLowerText(PlayerControl player, PlayerControl seen, bool isForMeeting = false, bool isForHud = false) { if (player == null || player != seen || player.IsModClient() && !isForHud) return string.Empty; if (CurrentBombedTime > 0 && CurrentBombedTime < BombExplosionTimer.GetFloat() + 1) return Utils.ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); From 9238b2ad52b96393d008338e1868c70c401ee9ca Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:44:11 +0200 Subject: [PATCH 306/778] fix grammar --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9dba0055e3..dffb6af086 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1362,7 +1362,7 @@ "DoubleAgent_DiffusedAgitaterBomb": "Agitator bomb successfully diffused", "DoubleAgent_DiffusedBastionBomb": "Bastion bomb successfully diffused", "DoubleAgent_BombExplodesIn": "Bomb Explodes In: {0}s", - "DoubleAgent_BombExploded": "Bomb has exploited!", + "DoubleAgent_BombExploded": "Bomb has exploded!", "DoubleAgentChangeRoleTo": "Change role on last Imposter", "DoubleAgentRoleChange": "You have become a: ", From f9e6260098ae040ab1a90836dca899b91bbfed6f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:55:16 +0200 Subject: [PATCH 307/778] rpcsetrole --- Modules/CustomRpcSender.cs | 7 ++++++- Patches/IntroPatch.cs | 1 + Patches/PlayerControlPatch.cs | 1 + Patches/onGameStartedPatch.cs | 22 +++++++++++++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 9b5220c656..1a88a2c1e0 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -3,6 +3,8 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; using System; +using TOHE.Roles.Core.AssignManager; +using static TOHE.SelectRolesPatch; namespace TOHE; @@ -233,10 +235,13 @@ public static class CustomRpcSenderExtensions { public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) { + + RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, true); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) .Write((ushort)role) - .Write(false) + .Write(true) .EndRpc(); + RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, false); } public static void RpcMurderPlayerV3(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 4c7fe4d0e5..7071f71520 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -593,6 +593,7 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } + _ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Phantom, true); }, 3f); if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index c12338d72d..ce9797c06d 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1877,6 +1877,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol // canOverrideRole = true; /* set this to true no matter the case */ if (GameStates.IsHideNSeek || __instance == null) return true; if (!ShipStatus.Instance.enabled || !AmongUsClient.Instance.AmHost) return true; + canOverrideRole = true; var target = __instance; var targetName = __instance.GetNameWithRole().RemoveHtmlTags(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 61ed7fcff6..a20570a341 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -7,9 +7,11 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; +using static TOHE.Roles.Core.AssignManager.RoleAssign; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; +using System.Linq; namespace TOHE; @@ -735,11 +737,16 @@ public static void Release() { try { + + SetDisconnectedMessage(sender.Value.stream, true); + seer.SetRole(roleType, false); sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) .Write((ushort)roleType) - .Write(false) + .Write(true) .EndRpc(); + + SetDisconnectedMessage(sender.Value.stream, false); } catch { } @@ -748,6 +755,19 @@ public static void Release() } doReplace = false; } + public static void SetDisconnectedMessage(MessageWriter stream, bool disconnected) + { + foreach (var pc in Main.AllPlayerControls) + { + //if (pc.PlayerId != target.PlayerId) continue; + pc.Data.Disconnected = disconnected; + + stream.StartMessage(1); + stream.WritePacked(pc.Data.NetId); + pc.Data.Serialize(stream, false); + stream.EndMessage(); + } + } public static void Initialize() { StoragedData = []; From 92937bbebd37944e388907bcfad7b85c12f1b25f Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:29:41 +0200 Subject: [PATCH 308/778] fix host --- Modules/ExtendedPlayerControl.cs | 4 ++-- Patches/ChatCommandPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 6 +++--- Patches/TaskAdderPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 8 ++++---- Roles/Core/CustomRoleManager.cs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5b8fc782a4..e1cf38bd2e 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -254,12 +254,12 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, b if (player == null) return; if (AmongUsClient.Instance.ClientId == clientId) { - player.SetRole(role, canOverride); + player.SetRole(role, true); return; } MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); writer.Write((ushort)role); - writer.Write(canOverride); + writer.Write(true); AmongUsClient.Instance.FinishRpcImmediately(writer); } diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index d8d06c4ebb..32a605693a 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -945,7 +945,7 @@ public static bool Prefix(ChatController __instance) if (setRole == roleName) { PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); - PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes()); + PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes(), true); PlayerControl.LocalPlayer.RpcSetCustomRole(rl); PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index ce9797c06d..c0de5925c6 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1874,10 +1874,9 @@ class PlayerControlSetRolePatch private static readonly Dictionary ghostRoles = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) { - // canOverrideRole = true; /* set this to true no matter the case */ + canOverrideRole = true; if (GameStates.IsHideNSeek || __instance == null) return true; if (!ShipStatus.Instance.enabled || !AmongUsClient.Instance.AmHost) return true; - canOverrideRole = true; var target = __instance; var targetName = __instance.GetNameWithRole().RemoveHtmlTags(); @@ -1945,7 +1944,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol { if (seer == null || target == null) continue; Logger.Info($"Desync {targetName} => {role} for {seer.GetNameWithRole().RemoveHtmlTags()}", "PlayerControl.RpcSetRole"); - target.RpcSetRoleDesync(role, false, seer.GetClientId()); + target.RpcSetRoleDesync(role, true, seer.GetClientId()); } return false; } @@ -2020,6 +2019,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref Ro if (!DidSetGhost.ContainsKey(__instance.PlayerId)) DidSetGhost.Add(__instance.PlayerId, true); } + _ = new LateTask(() => { Main.AllPlayerControls.Do(x => Logger.Info($"{x.GetRealName()}/{x.Data.Role.GetType().Name}/{x.Data.Role.Role}", "Test All Roletypes, and Roleclass")); }, 3f); } catch (Exception e) { diff --git a/Patches/TaskAdderPatch.cs b/Patches/TaskAdderPatch.cs index bfcecc3c64..344a475f6c 100644 --- a/Patches/TaskAdderPatch.cs +++ b/Patches/TaskAdderPatch.cs @@ -97,7 +97,7 @@ public static bool Prefix(TaskAddButton __instance) { CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; PlayerControl.LocalPlayer.RpcSetCustomRole(FileCustomRole); - PlayerControl.LocalPlayer.RpcSetRole(FileCustomRole.GetRoleTypes()); + PlayerControl.LocalPlayer.RpcSetRole(FileCustomRole.GetRoleTypes(), true); return false; } } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index a20570a341..9b6392a4be 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -281,7 +281,7 @@ public static void Prefix() if (Main.EnableGM.Value) { PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); - PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate); + PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate, true); PlayerControl.LocalPlayer.Data.IsDead = true; Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); } @@ -299,7 +299,7 @@ public static void Prefix() if (Main.EnableGM.Value && Options.CurrentGameMode == CustomGameMode.Standard) { PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); - PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate); + PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate, true); PlayerControl.LocalPlayer.Data.IsDead = true; Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); } @@ -666,7 +666,7 @@ private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dic RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); //Set role for host - player.SetRole(othersRole, false); + player.SetRole(othersRole, true); // Override RoleType for host if (isHost && BaseRole == RoleTypes.Shapeshifter) @@ -740,7 +740,7 @@ public static void Release() SetDisconnectedMessage(sender.Value.stream, true); - seer.SetRole(roleType, false); + seer.SetRole(roleType, true); sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) .Write((ushort)roleType) .Write(true) diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 1ec0670f3c..ac222f7e22 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -337,7 +337,7 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target, bo break; case CustomRoles.EvilSpirit when !inMeeting && !isSuicide: - target.RpcSetRole(RoleTypes.GuardianAngel); + target.RpcSetRole(RoleTypes.GuardianAngel, true); break; case CustomRoles.Spurt: From eea089ab530a9d4d7ce69562b18835c62e168106 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:46:54 +0200 Subject: [PATCH 309/778] fix funky stuff happening --- Modules/CustomRpcSender.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 1a88a2c1e0..cb478e8199 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -236,12 +236,12 @@ public static class CustomRpcSenderExtensions public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) { - RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, true); + //RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, true); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) .Write((ushort)role) .Write(true) .EndRpc(); - RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, false); + // RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, false); } public static void RpcMurderPlayerV3(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) { From ae1091d4746e8093ca67d4dfd04eeb4f7b7a5318 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:33:43 +0200 Subject: [PATCH 310/778] remove --- Modules/CustomRpcSender.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index cb478e8199..35ac5960c9 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -235,13 +235,10 @@ public static class CustomRpcSenderExtensions { public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) { - - //RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, true); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) .Write((ushort)role) .Write(true) .EndRpc(); - // RpcSetRoleReplacer.SetDisconnectedMessage(sender.stream, false); } public static void RpcMurderPlayerV3(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) { From f0df73302312fa187387c9d6988414dd28e3fe72 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:58:42 +0200 Subject: [PATCH 311/778] updates --- Modules/ExtendedPlayerControl.cs | 34 ++++++ Modules/RoleBasisChanger.cs | 195 ++++++++++++++++--------------- Patches/PlayerControlPatch.cs | 3 + Patches/onGameStartedPatch.cs | 4 +- 4 files changed, 138 insertions(+), 98 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e1cf38bd2e..fdaffbbc48 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; +using MonoMod.Cil; using System; using System.Text; using TOHE.Modules; @@ -12,6 +13,7 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -249,6 +251,38 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, writer.WritePacked(ventId); AmongUsClient.Instance.FinishRpcImmediately(writer); } + public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes Type, bool IsDesyncImpostor = false) + { + //Havne't tested method yet. + if (!GameStates.IsInGame || AmongUsClient.Instance.AmHost) return; + + if (IsDesyncImpostor) + { + foreach (var seer in Main.AllPlayerControls) + { + if (seer.PlayerId == player.PlayerId) continue; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.PlayerId, (byte)RpcCalls.SetRole, SendOption.Reliable, seer.GetClientId()); + writer.Write((ushort)RoleTypes.Crewmate); + writer.Write(true); + AmongUsClient.Instance.FinishRpcImmediately(writer); + + MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(seer.PlayerId, (byte)RpcCalls.SetRole, SendOption.Reliable, player.GetClientId()); + writer2.Write((ushort)RoleTypes.Crewmate); + writer2.Write(true); + AmongUsClient.Instance.FinishRpcImmediately(writer2); + } + MessageWriter writer3 = AmongUsClient.Instance.StartRpcImmediately(player.PlayerId, (byte)RpcCalls.SetRole, SendOption.Reliable, player.GetClientId()); + writer3.Write((ushort)Type); + writer3.Write(true); + AmongUsClient.Instance.FinishRpcImmediately(writer3); + } + else + { + player.RpcSetRole(Type, true); + } + + } public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, bool canOverride, int clientId) { if (player == null) return; diff --git a/Modules/RoleBasisChanger.cs b/Modules/RoleBasisChanger.cs index 785aa28118..064df853b7 100644 --- a/Modules/RoleBasisChanger.cs +++ b/Modules/RoleBasisChanger.cs @@ -7,100 +7,103 @@ namespace TOHE.Modules; // https://github.com/Rabek009/MoreGamemodes // https://github.com/Gurge44/EndlessHostRoles -internal static class RoleBasisChanger -{ - public static bool IsChangeInProgress; - public static bool SkipTasksAfterAssignRole; - public static void ChangeRoleBasis(this PlayerControl player, RoleTypes targetVNRole) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (player.OwnedByHost()) - { - DestroyableSingleton.Instance.SetRole(player, targetVNRole); - //player.RpcSetRole(targetVNRole); - player.SyncSettings(); - - Utils.NotifyRoles(SpecifySeer: player); - Utils.NotifyRoles(SpecifyTarget: player); - - HudManager.Instance.SetHudActive(player, player.Data.Role, !GameStates.IsMeeting); - - return; - } - - IsChangeInProgress = true; - SkipTasksAfterAssignRole = true; - - Vector2 position = player.GetTruePosition(); - PlayerControl PlayerPrefab = AmongUsClient.Instance.PlayerPrefab; - PlayerControl newplayer = Object.Instantiate(PlayerPrefab, position, Quaternion.identity); - - newplayer.PlayerId = player.PlayerId; - newplayer.FriendCode = player.FriendCode; - newplayer.Puid = player.Puid; - - ClientData pclient = player.GetClient(); - - player.RpcTeleport(ExtendedPlayerControl.GetBlackRoomPosition()); - AmongUsClient.Instance.Despawn(player); - AmongUsClient.Instance.Spawn(newplayer, player.OwnerId); - pclient.Character = newplayer; - - newplayer.OwnerId = player.OwnerId; - - pclient.InScene = true; - pclient.IsReady = true; - - newplayer.MyPhysics.ResetMoveState(); - - GameData.Instance.RemovePlayer(player.PlayerId); - GameData.Instance.AddPlayer(newplayer, newplayer.GetClient()); - - //newplayer.RpcSetRoleDesync(targetVNRole, true, newplayer.GetClientId()); - newplayer.RpcSetRole(targetVNRole, true); - - //Used instead of GameData.Instance.DirtyAllData(); - foreach (var innerNetObject in GameData.Instance.AllPlayers) - { - innerNetObject.SetDirtyBit(uint.MaxValue); - } - - newplayer.ReactorFlash(0.2f); - newplayer.RpcTeleport(position); - - _ = new LateTask(() => { IsChangeInProgress = false; }, 5f, "Desync Role Basis"); - _ = new LateTask(() => { SkipTasksAfterAssignRole = false; }, 8f, "Wait End Intro", shoudLog: false); - } - - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] - public static class InnerNetClientSpawnPatch - { - public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] InnerNetObject netObjParent, [HarmonyArgument(1)] int ownerId, [HarmonyArgument(2)] SpawnFlags flags) - { - if (!IsChangeInProgress) return true; - - ownerId = (ownerId == -3) ? __instance.ClientId : ownerId; - MessageWriter messageWriter = __instance.Streams[0]; - __instance.WriteSpawnMessage(netObjParent, ownerId, flags, messageWriter); - return false; - } - } - - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Despawn))] - public static class InnerNetClientDespawnPatch - { - public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] InnerNetObject objToDespawn) - { - if (!IsChangeInProgress) return true; - - MessageWriter messageWriter = __instance.Streams[0]; - messageWriter.StartMessage(5); - messageWriter.WritePacked(objToDespawn.NetId); - messageWriter.EndMessage(); - __instance.RemoveNetObject(objToDespawn); - return false; - } - } -} \ No newline at end of file +//Not in use ever since the 2024.6.18 update break it + +//internal static class RoleBasisChanger +//{ +//public static bool IsChangeInProgress; +//public static bool SkipTasksAfterAssignRole; + +//public static void ChangeRoleBasis(this PlayerControl player, RoleTypes targetVNRole) +//{ +//if (!AmongUsClient.Instance.AmHost) return; +// +//if (player.OwnedByHost()) +//{ +//DestroyableSingleton.Instance.SetRole(player, targetVNRole); +//player.RpcSetRole(targetVNRole); +// player.SyncSettings(); +// +// Utils.NotifyRoles(SpecifySeer: player); +// Utils.NotifyRoles(SpecifyTarget: player); +// +// HudManager.Instance.SetHudActive(player, player.Data.Role, !GameStates.IsMeeting); +// +// return; +//} +// +//IsChangeInProgress = true; +//SkipTasksAfterAssignRole = true; +// +//Vector2 position = player.GetTruePosition(); +//PlayerControl PlayerPrefab = AmongUsClient.Instance.PlayerPrefab; +//PlayerControl newplayer = Object.Instantiate(PlayerPrefab, position, Quaternion.identity); +// +//newplayer.PlayerId = player.PlayerId; +//newplayer.FriendCode = player.FriendCode; +//newplayer.Puid = player.Puid; +// +//ClientData pclient = player.GetClient(); +// +//player.RpcTeleport(ExtendedPlayerControl.GetBlackRoomPosition()); +//AmongUsClient.Instance.Despawn(player); +//AmongUsClient.Instance.Spawn(newplayer, player.OwnerId); +//pclient.Character = newplayer; +// +//newplayer.OwnerId = player.OwnerId; +// +//pclient.InScene = true; +//pclient.IsReady = true; +// +//newplayer.MyPhysics.ResetMoveState(); +// +//GameData.Instance.RemovePlayer(player.PlayerId); +//GameData.Instance.AddPlayer(newplayer, newplayer.GetClient()); +// +//newplayer.RpcSetRoleDesync(targetVNRole, true, newplayer.GetClientId()); +//newplayer.RpcSetRole(targetVNRole, true); +// +//Used instead of GameData.Instance.DirtyAllData(); +// foreach (var innerNetObject in GameData.Instance.AllPlayers) +// { +// innerNetObject.SetDirtyBit(uint.MaxValue); +// } +// +// newplayer.ReactorFlash(0.2f); +// newplayer.RpcTeleport(position); +// +// _ = new LateTask(() => { IsChangeInProgress = false; }, 5f, "Desync Role Basis"); +// _ = new LateTask(() => { SkipTasksAfterAssignRole = false; }, 8f, "Wait End Intro", shoudLog: false); +// } +// +// [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] +// public static class InnerNetClientSpawnPatch +// { +// public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] InnerNetObject netObjParent, [HarmonyArgument(1)] int ownerId, [HarmonyArgument(2)] SpawnFlags flags) +// { +// if (!IsChangeInProgress) return true; +// +// ownerId = (ownerId == -3) ? __instance.ClientId : ownerId; +// MessageWriter messageWriter = __instance.Streams[0]; +// __instance.WriteSpawnMessage(netObjParent, ownerId, flags, messageWriter); +// return false; +// } +// } +// +// [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Despawn))] +// public static class InnerNetClientDespawnPatch +// { +// public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] InnerNetObject objToDespawn) +// { +// if (!IsChangeInProgress) return true; +// +// MessageWriter messageWriter = __instance.Streams[0]; +// messageWriter.StartMessage(5); +// messageWriter.WritePacked(objToDespawn.NetId); +// messageWriter.EndMessage(); +// __instance.RemoveNetObject(objToDespawn); +// return false; +// } +// } +//} \ No newline at end of file diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index c0de5925c6..9c78c01a71 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -2016,8 +2016,11 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref Ro Logger.Info($" {__instance.GetRealName()} => {roleType}", "PlayerControl.RpcSetRole"); if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost or RoleTypes.GuardianAngel) + { if (!DidSetGhost.ContainsKey(__instance.PlayerId)) DidSetGhost.Add(__instance.PlayerId, true); + + } } _ = new LateTask(() => { Main.AllPlayerControls.Do(x => Logger.Info($"{x.GetRealName()}/{x.Data.Role.GetType().Name}/{x.Data.Role.Role}", "Test All Roletypes, and Roleclass")); }, 3f); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 9b6392a4be..91b7a2c6e1 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -733,12 +733,12 @@ public static void Release() if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) throw new InvalidOperationException("A CustomRpcSender had Invalid State."); + SetDisconnectedMessage(sender.Value.stream, true); foreach (var (seer, roleType) in StoragedData) { try { - SetDisconnectedMessage(sender.Value.stream, true); seer.SetRole(roleType, true); sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) @@ -746,11 +746,11 @@ public static void Release() .Write(true) .EndRpc(); - SetDisconnectedMessage(sender.Value.stream, false); } catch { } } + SetDisconnectedMessage(sender.Value.stream, false); sender.Value.EndMessage(); } doReplace = false; From ef30b3e2bc9ec7245e4d794b92ea958a9b9b595d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:59:59 +0200 Subject: [PATCH 312/778] fix --- Modules/EAC.cs | 2 +- Patches/IntroPatch.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/EAC.cs b/Modules/EAC.cs index f6b96c0b41..b608abf798 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -35,7 +35,7 @@ public static bool PlayerControlReceiveRpc(PlayerControl pc, byte callId, Messag // nvm, it works so im not doing more changes if (!AmongUsClient.Instance.AmHost) return false; - if (RoleBasisChanger.IsChangeInProgress) return false; + //if (RoleBasisChanger.IsChangeInProgress) return false; if (pc == null || reader == null) return false; try { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 7071f71520..01e21dcf2a 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -102,7 +102,7 @@ class CoBeginPatch { public static void Prefix() { - if (RoleBasisChanger.IsChangeInProgress) return; + //if (RoleBasisChanger.IsChangeInProgress) return; var logger = Logger.Handler("Info"); @@ -507,7 +507,7 @@ class IntroCutsceneDestroyPatch { public static void Postfix() { - if (!GameStates.IsInGame || RoleBasisChanger.SkipTasksAfterAssignRole) return; + if (!GameStates.IsInGame/* || RoleBasisChanger.SkipTasksAfterAssignRole*/) return; Main.introDestroyed = true; From 636d9861316df9252eb74d9e4367c806f88ce86a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:15:58 +0200 Subject: [PATCH 313/778] fix --- Patches/onGameStartedPatch.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 91b7a2c6e1..3de3c8676b 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -733,24 +733,22 @@ public static void Release() if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) throw new InvalidOperationException("A CustomRpcSender had Invalid State."); - SetDisconnectedMessage(sender.Value.stream, true); foreach (var (seer, roleType) in StoragedData) { try { - - + SetDisconnectedMessage(sender.Value.stream, true); seer.SetRole(roleType, true); sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) .Write((ushort)roleType) .Write(true) .EndRpc(); + SetDisconnectedMessage(sender.Value.stream, false); } catch { } } - SetDisconnectedMessage(sender.Value.stream, false); sender.Value.EndMessage(); } doReplace = false; From e2076967b6b7fde3f077ebbeb516a02a12bc5ddb Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:19:07 +0200 Subject: [PATCH 314/778] private again --- Patches/onGameStartedPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3de3c8676b..f7c4897e07 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -753,7 +753,7 @@ public static void Release() } doReplace = false; } - public static void SetDisconnectedMessage(MessageWriter stream, bool disconnected) + private static void SetDisconnectedMessage(MessageWriter stream, bool disconnected) { foreach (var pc in Main.AllPlayerControls) { From 4d3b5db1e94bd2b6f19400bc2a0e6f68d315a761 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:02:53 +0200 Subject: [PATCH 315/778] rolebasischanger --- Modules/ExtendedPlayerControl.cs | 20 +++++--------------- Patches/IntroPatch.cs | 3 ++- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index fdaffbbc48..e7f3911c60 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -253,8 +253,8 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, } public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes Type, bool IsDesyncImpostor = false) { - //Havne't tested method yet. - if (!GameStates.IsInGame || AmongUsClient.Instance.AmHost) return; + //Seems work + if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; if (IsDesyncImpostor) { @@ -262,20 +262,10 @@ public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes Type, { if (seer.PlayerId == player.PlayerId) continue; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.PlayerId, (byte)RpcCalls.SetRole, SendOption.Reliable, seer.GetClientId()); - writer.Write((ushort)RoleTypes.Crewmate); - writer.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(seer.PlayerId, (byte)RpcCalls.SetRole, SendOption.Reliable, player.GetClientId()); - writer2.Write((ushort)RoleTypes.Crewmate); - writer2.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(writer2); + player.RpcSetRoleDesync(RoleTypes.Crewmate, true, seer.GetClientId()); + seer.RpcSetRoleDesync(RoleTypes.Crewmate, true, player.GetClientId()); } - MessageWriter writer3 = AmongUsClient.Instance.StartRpcImmediately(player.PlayerId, (byte)RpcCalls.SetRole, SendOption.Reliable, player.GetClientId()); - writer3.Write((ushort)Type); - writer3.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(writer3); + player.RpcSetRoleDesync(Type, true, player.GetClientId()); } else { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 01e21dcf2a..7f4c4f5c61 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -593,7 +593,8 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } - _ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Phantom, true); }, 3f); + _ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Impostor, true); }, 3f); + _ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcChangeRoleBasis(RoleTypes.Impostor, true); }, 6f); if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch From 76ce082bf76692539403208ce872c0578d755995 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 21 Aug 2024 22:54:32 +0800 Subject: [PATCH 316/778] Fix Innocent didn't win with Impostor when setting is On --- Roles/Neutral/Innocent.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Roles/Neutral/Innocent.cs b/Roles/Neutral/Innocent.cs index a0a2058110..e4823b8465 100644 --- a/Roles/Neutral/Innocent.cs +++ b/Roles/Neutral/Innocent.cs @@ -40,12 +40,12 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) { - var role = exiled.GetCustomRole(); - var pcArray = Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId); + var exiledRole = exiled.GetCustomRole(); + var innocentArray = Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId); - if (!pcArray.Any()) return; + if (!innocentArray.Any()) return; - if (!InnocentCanWinByImp.GetBool() && role.IsImpostor()) + if (!InnocentCanWinByImp.GetBool() && exiledRole.IsImpostor()) { if (!isMeetingHud) Logger.Info("Exeiled Winner Check for impostor", "Innocent"); @@ -60,7 +60,7 @@ public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool Decid else { bool isInnocentWinConverted = false; - foreach (var Innocent in pcArray) + foreach (var Innocent in innocentArray) { if (CustomWinnerHolder.CheckForConvertedWinner(Innocent.PlayerId)) { @@ -79,7 +79,11 @@ public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool Decid CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Innocent); } - pcArray.Do(x => CustomWinnerHolder.WinnerIds.Add(x.PlayerId)); + innocentArray.Do(x => CustomWinnerHolder.WinnerIds.Add(x.PlayerId)); + if (exiledRole.IsImpostor()) + { + CustomWinnerHolder.WinnerIds.Add(exiled.PlayerId); + } } } DecidedWinner = true; From 6740165e60029014df34f467571565f76aa1590d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 21 Aug 2024 23:01:16 +0800 Subject: [PATCH 317/778] Add OnRemove in Oiiai --- Roles/AddOns/Common/Oiiai.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Roles/AddOns/Common/Oiiai.cs b/Roles/AddOns/Common/Oiiai.cs index 4fc07e94f6..24e421051a 100644 --- a/Roles/AddOns/Common/Oiiai.cs +++ b/Roles/AddOns/Common/Oiiai.cs @@ -94,9 +94,12 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) if (changeValue != 0) { + killer.GetRoleClass().OnRemove(killer.PlayerId); killer.RpcSetCustomRole(NRoleChangeRoles[changeValue - 1]); killer.GetRoleClass().OnAdd(killer.PlayerId); + killer.SyncSettings(); + Logger.Info($"Oiiai {killer.GetNameWithRole().RemoveHtmlTags()} with Neutrals with kill button assign.", "Oiiai"); } } From f12b790c9f627dd23431576962a136e70fbdf594 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:48:34 +0200 Subject: [PATCH 318/778] comment --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e7f3911c60..acf78e3794 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -253,7 +253,7 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, } public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes Type, bool IsDesyncImpostor = false) { - //Seems work + //not complete yet, but the current version works with normal roles (have yet to fix for noisemaker etc..) if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; if (IsDesyncImpostor) From 1c070ebfe81cd53ea5a3afec34fa3f4587cea954 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:56:56 +0200 Subject: [PATCH 319/778] lotta stuffs --- Modules/AntiBlackout.cs | 26 ++++++++++++++++++ Modules/ExtendedPlayerControl.cs | 27 +++++++++++++------ Modules/OptionHolder.cs | 6 +++++ Patches/ExilePatch.cs | 30 +++++++++++---------- Patches/IntroPatch.cs | 4 +-- Resources/Lang/en_US.json | 1 + Roles/Core/AssignManager/GhostRoleAssign.cs | 8 +++--- 7 files changed, 74 insertions(+), 28 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 720981393a..be4537434a 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -1,8 +1,10 @@ +using AmongUs.GameOptions; using Hazel; using System; using System.Runtime.CompilerServices; using TOHE.Modules; using TOHE.Roles.Core; +using TOHE.Roles.Core.AssignManager; namespace TOHE; @@ -248,4 +250,28 @@ public static void Reset() public static bool ShowExiledInfo = false; public static string StoreExiledMessage = ""; +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Die))] +public static class ReassignImpostorPatch +{ + public static void Postfix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor()) return; + + foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) + { + Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); + } + } + + public static void FixDesyncImpostorRoles(this PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() + && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; + + foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) + { + Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); + } + } } \ No newline at end of file diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index acf78e3794..50ba3fbad4 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -34,7 +34,7 @@ public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role) { if (Cleanser.CantGetAddon() && player.Is(CustomRoles.Cleansed)) return; if (role == CustomRoles.Cleansed) Main.PlayerStates[player.PlayerId].SetSubRole(role, pc: player); - else Main.PlayerStates[player.PlayerId].SetSubRole(role); + else Main.PlayerStates[player.PlayerId].SetSubRole(role); } if (AmongUsClient.Instance.AmHost) { @@ -251,25 +251,36 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, writer.WritePacked(ventId); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes Type, bool IsDesyncImpostor = false) + + /// + /// Changes the RoleBase of player during the game. + /// + /// The type to change into + /// If the player should be desynced from impostor teammates + /// Will only function if is active and makes it so you can't kill them, neither can they kill you + public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleTypes, bool IsDesyncImpostor = false, List FellowImps = null) { - //not complete yet, but the current version works with normal roles (have yet to fix for noisemaker etc..) if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; + FellowImps = []; - if (IsDesyncImpostor) + if (IsDesyncImpostor && roleTypes is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) { foreach (var seer in Main.AllPlayerControls) { if (seer.PlayerId == player.PlayerId) continue; + RoleTypes Typa = RoleTypes.Scientist; - player.RpcSetRoleDesync(RoleTypes.Crewmate, true, seer.GetClientId()); - seer.RpcSetRoleDesync(RoleTypes.Crewmate, true, player.GetClientId()); + if (seer.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) Typa = RoleTypes.Noisemaker; + else if (FellowImps.Contains(seer) && seer.HasKillButton()) Typa = seer.GetCustomRole().GetVNRole().GetRoleTypes(); + + player.RpcSetRoleDesync(Typa, true, seer.GetClientId()); + seer.RpcSetRoleDesync(Typa, true, player.GetClientId()); } - player.RpcSetRoleDesync(Type, true, player.GetClientId()); + player.RpcSetRoleDesync(roleTypes, true, player.GetClientId()); } else { - player.RpcSetRole(Type, true); + player.RpcSetRole(roleTypes, true); } } diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index a78047b462..4359e3f2b7 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -377,6 +377,7 @@ private enum RatesZeroOne public static OptionItem GhostCanSeeOtherVotes; public static OptionItem GhostCanSeeDeathReason; public static OptionItem ConvertedCanBecomeGhost; + public static OptionItem NeutralCanBecomeGhost; public static OptionItem MaxImpGhost; public static OptionItem MaxCrewGhost; public static OptionItem DefaultAngelCooldown; @@ -1818,6 +1819,11 @@ private static System.Collections.IEnumerator CoLoadOptions() ConvertedCanBecomeGhost = BooleanOptionItem.Create(60840, "ConvertedCanBeGhostRole", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(217, 218, 255, byte.MaxValue)); + ConvertedCanBecomeGhost = BooleanOptionItem.Create(60841, "NeutralCanBeGhostRole", false, TabGroup.ModSettings, false) + .SetParent(ConvertedCanBecomeGhost) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(217, 218, 255, byte.MaxValue)); + MaxImpGhost = IntegerOptionItem.Create(60850, "MaxImpGhostRole", new(0, 15, 1), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetValueFormat(OptionFormat.Times) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index d9093dc6df..eb582c429f 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -73,10 +73,10 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (CLThingy && exiled != null) { // Reset player cam for exiled desync impostor - if (exiled.Object.HasDesyncRole()) - { - exiled.Object?.ResetPlayerCam(1f); - } + //if (exiled.Object.HasDesyncRole()) + // { + // exiled.Object?.ResetPlayerCam(1f); + // } exiled.IsDead = true; exiled.PlayerId.SetDeathReason(PlayerState.DeathReason.Vote); @@ -104,12 +104,12 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) player.GetRoleClass()?.OnPlayerExiled(player, exiled); // Check Anti BlackOut - if (player.GetCustomRole().IsImpostor() - && !player.IsAlive() // if player is dead impostor - && AntiBlackout.BlackOutIsActive) // if Anti BlackOut is activated - { - player.ResetPlayerCam(1f); - } + // if (player.GetCustomRole().IsImpostor() + // && !player.IsAlive() // if player is dead impostor + // && AntiBlackout.BlackOutIsActive) // if Anti BlackOut is activated + // { + // player.ResetPlayerCam(1f); + // } // Check for remove pet player.RpcRemovePet(); @@ -117,6 +117,8 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) // Reset Kill/Ability cooldown player.ResetKillCooldown(); player.RpcResetAbilityCooldown(); + + player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles, also doing this once after death dosen't help and has to be done every exile. } Main.MeetingIsStarted = false; @@ -181,10 +183,10 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) player?.SetRealKiller(player, true); // Reset player cam for dead desync impostor - if (player.HasDesyncRole()) - { - player?.ResetPlayerCam(1f); - } + // if (player.HasDesyncRole()) + //{ + // player?.ResetPlayerCam(1f); + // } MurderPlayerPatch.AfterPlayerDeathTasks(player, player, true); }); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 7f4c4f5c61..59d768c405 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -593,8 +593,8 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } - _ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Impostor, true); }, 3f); - _ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcChangeRoleBasis(RoleTypes.Impostor, true); }, 6f); + //_ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Impostor, true); }, 3f); + //_ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcChangeRoleBasis(RoleTypes.Impostor, true); }, 6f); if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9dba0055e3..9b80cebb63 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1155,6 +1155,7 @@ "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", "GhostIgnoreTasks": "Ghosts Exempt From Tasks", "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", + "NeutralCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles (Will change team respectively)", "MaxImpGhostRole": "Max Impostor Ghost-Roles", "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", "DefaultAngelCooldown": "Default Ability Cooldown", diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 4532bc36ec..7ac06bbf49 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -20,8 +20,7 @@ public static void GhostAssignPatch(PlayerControl player) || Options.CurrentGameMode == CustomGameMode.FFA || player == null || player.Data.Disconnected - || GhostGetPreviousRole.ContainsKey(player.PlayerId) - || player.HasDesyncRole()) return; + || GhostGetPreviousRole.ContainsKey(player.PlayerId)) return; if (forceRole.TryGetValue(player.PlayerId, out CustomRoles forcerole)) { Logger.Info($" Debug set {player.GetRealName()}'s role to {forcerole}", "GhostAssignPatch"); player.GetRoleClass()?.OnRemove(player.PlayerId); @@ -38,8 +37,9 @@ public static void GhostAssignPatch(PlayerControl player) if (getplrRole is CustomRoles.GM or CustomRoles.Nemesis or CustomRoles.Retributionist or CustomRoles.NiceMini) return; var IsNeutralAllowed = !player.IsAnySubRole(x => x.IsConverted()) || Options.ConvertedCanBecomeGhost.GetBool(); - var IsCrewmate = (getplrRole.IsCrewmate() || player.Is(CustomRoles.Admired)) && IsNeutralAllowed; - var IsImpostor = (getplrRole.IsImpostor()) && (IsNeutralAllowed || player.Is(CustomRoles.Madmate)); + var CheckNeutral = player.GetCustomRole().IsNeutral() && Options.NeutralCanBecomeGhost.GetBool(); + var IsCrewmate = ((getplrRole.IsCrewmate() || player.Is(CustomRoles.Admired)) && IsNeutralAllowed) || CheckNeutral; + var IsImpostor = ((getplrRole.IsImpostor()) && (IsNeutralAllowed || player.Is(CustomRoles.Madmate))) || CheckNeutral; if (getplrRole.IsGhostRole() || player.IsAnySubRole(x => x.IsGhostRole() || x == CustomRoles.Gravestone) || !Options.CustomGhostRoleCounts.Any()) return; From 93321f4e493a3df9c83b19225cfeb536aff0b3db Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:02:47 +0200 Subject: [PATCH 320/778] bruh --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9b80cebb63..51e6d6e7f6 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1155,7 +1155,7 @@ "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", "GhostIgnoreTasks": "Ghosts Exempt From Tasks", "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", - "NeutralCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles (Will change team respectively)", + "NeutralCanBeGhostRole": "Neutral Players Can Be Any Ghost-Roles (Will change team respectively)", "MaxImpGhostRole": "Max Impostor Ghost-Roles", "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", "DefaultAngelCooldown": "Default Ability Cooldown", From 454d088130d786b4140ef9f173af015099c59562 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:08:10 +0200 Subject: [PATCH 321/778] comment --- Modules/AntiBlackout.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index be4537434a..7981aec385 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -258,6 +258,8 @@ public static void Postfix(PlayerControl __instance) { if (!AmongUsClient.Instance.AmHost || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor()) return; + //idk if this is needed since anyways ghost-role desyncs aren't in here (cuz that is set later), so maybe I'll remove. + foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) { Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); From 7d39f3fb93ec34353a6e2ca1e83d19e48ce38b5c Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:14:46 +0200 Subject: [PATCH 322/778] fix desync roles again --- Modules/AntiBlackout.cs | 4 ++-- Modules/ExtendedPlayerControl.cs | 2 +- Modules/OptionHolder.cs | 6 +++--- Patches/IntroPatch.cs | 10 ++++++++++ Patches/onGameStartedPatch.cs | 30 +++++++++++++++++++++--------- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 7981aec385..1acb6fce7e 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -13,7 +13,7 @@ public static class AntiBlackout /// /// Check num alive Impostors & Crewmates & NeutralKillers /// - public static bool BlackOutIsActive => !Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut(); + public static bool BlackOutIsActive => /*!Options.DisableAntiBlackoutProtects.GetBool() &&*/ CheckBlackOut(); /// /// Count alive players and check black out @@ -65,7 +65,7 @@ public static bool CheckBlackOut() BlackOutIsActive = numAliveNeutralKillers == 1 && numAliveImpostors == 1 && numAliveCrewmates <= 2; Logger.Info($" {BlackOutIsActive}", "BlackOut Is Active"); - return BlackOutIsActive; + return false; //blackout is not needed anymore BlackOutIsActive; } public static bool IsCached { get; private set; } = false; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 50ba3fbad4..9851a9eb86 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -253,7 +253,7 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, } /// - /// Changes the RoleBase of player during the game. + /// Changes the Role Basis of player during the game. /// /// The type to change into /// If the player should be desynced from impostor teammates diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 4359e3f2b7..6034342e12 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -224,7 +224,7 @@ private enum RatesZeroOne public static OptionItem NoGameEnd; public static OptionItem AllowConsole; - public static OptionItem DisableAntiBlackoutProtects; + //public static OptionItem DisableAntiBlackoutProtects; public static OptionItem RoleAssigningAlgorithm; public static OptionItem KPDCamouflageMode; @@ -1115,9 +1115,9 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetHeader(true); AllowConsole = BooleanOptionItem.Create(60382, "AllowConsole", false, TabGroup.SystemSettings, false) .SetColor(Color.red); - DisableAntiBlackoutProtects = BooleanOptionItem.Create(60384, "DisableAntiBlackoutProtects", false, TabGroup.SystemSettings, false) + /* DisableAntiBlackoutProtects = BooleanOptionItem.Create(60384, "DisableAntiBlackoutProtects", false, TabGroup.SystemSettings, false) .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.red); + .SetColor(Color.red);*/ RoleAssigningAlgorithm = StringOptionItem.Create(60400, "RoleAssigningAlgorithm", roleAssigningAlgorithms, 4, TabGroup.SystemSettings, true) .RegisterUpdateValueEvent((object obj, OptionItem.UpdateValueEventArgs args) => IRandom.SetInstanceById(args.CurrentValue)) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 59d768c405..a0d5ff105d 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -9,6 +9,7 @@ using TOHE.Roles.Core.AssignManager; using TOHE.Roles.Neutral; using UnityEngine; +using static TOHE.SelectRolesPatch; using static TOHE.Translator; namespace TOHE; @@ -595,6 +596,15 @@ public static void Postfix() //_ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Impostor, true); }, 3f); //_ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcChangeRoleBasis(RoleTypes.Impostor, true); }, 6f); + _ = new LateTask(() => { + foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) + { + DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); + } + + }, 1f, "Assign Impostor desync roels for crewmates"); + + if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index f7c4897e07..dd98c04d6b 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -389,14 +389,18 @@ private static void SetRolesAfterSelect() AssignDesyncRole(role, pc, senders, rolesMap, BaseRole: role.GetDYRole()); // Set Desync RoleType by "RpcSetRole" - MakeDesyncSender(senders, rolesMap); + //MakeDesyncSender(senders, rolesMap); + RpcSetRoleReplacer.DesyncPlayers.Clear(); // Override RoleType for others players foreach (var (pc, role) in RoleAssign.RoleResult) { - if (pc == null || role.IsDesyncRole()) continue; + if (pc == null) continue; + var realrole = role; + if (role.IsDesyncRole()) realrole = CustomRoles.Scientist; - RpcSetRoleReplacer.StoragedData.Add(pc, role.GetRoleTypes()); + if (role.IsDesyncRole()) RpcSetRoleReplacer.DesyncPlayers.Add(pc, role); + RpcSetRoleReplacer.StoragedData.Add(pc, realrole.GetRoleTypes()); Logger.Warn($"Set original role type => {pc.GetRealName()}: {role} => {role.GetRoleTypes()}", "Override Role Select"); } @@ -719,6 +723,7 @@ public static class RpcSetRoleReplacer public static bool doReplace = false; public static Dictionary senders; public static Dictionary StoragedData = []; + public static Dictionary DesyncPlayers = []; // List of Senders that do not require additional writing because SetRoleRpc has already been written by another process such as Position Desync public static List OverriddenSenderList; public static bool Prefix() @@ -729,7 +734,7 @@ public static void Release() { foreach (var sender in senders) { - if (OverriddenSenderList.Contains(sender.Value)) continue; + //if (OverriddenSenderList.Contains(sender.Value)) continue; if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) throw new InvalidOperationException("A CustomRpcSender had Invalid State."); @@ -738,11 +743,18 @@ public static void Release() try { SetDisconnectedMessage(sender.Value.stream, true); - seer.SetRole(roleType, true); - sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) - .Write((ushort)roleType) - .Write(true) - .EndRpc(); + if (!DesyncPlayers.TryGetValue(seer, out var role) || role.IsCrewmate()) + { + seer.SetRole(roleType, true); + sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) + .Write((ushort)roleType) + .Write(true) + .EndRpc(); + } + else + { + seer.RpcChangeRoleBasis(role.GetRoleTypes(), true); + } SetDisconnectedMessage(sender.Value.stream, false); } From ef9989264279b43f3b47a7ad4b6d3f05f7e9f504 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:28:51 +0200 Subject: [PATCH 323/778] stuff --- Modules/GameOptionsSender/PlayerGameOptionsSender.cs | 2 +- Patches/IntroPatch.cs | 8 ++++++-- Patches/onGameStartedPatch.cs | 7 ++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 77b422d3b8..f1df0e225d 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -79,7 +79,7 @@ public override IGameOptions BuildGameOptions() else if (GameStates.IsHideNSeek) return opt; var state = Main.PlayerStates[player.PlayerId]; - opt.BlackOut(state.IsBlackOut); + opt.BlackOut(state.IsBlackOut || !Main.introDestroyed); CustomRoles role = player.GetCustomRole(); if (Options.CurrentGameMode == CustomGameMode.FFA) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index a0d5ff105d..978ef23277 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -512,6 +512,7 @@ public static void Postfix() Main.introDestroyed = true; + if (!GameStates.AirshipIsActive) { foreach (var state in Main.PlayerStates.Values) @@ -602,8 +603,11 @@ public static void Postfix() DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); } - }, 1f, "Assign Impostor desync roels for crewmates"); - + }, 1f, "Assign Impostor desync roles for crewmates"); + foreach (var pc in Main.AllPlayerControls) + { + pc.MarkDirtySettings(); + } if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index dd98c04d6b..abba25099a 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -397,7 +397,7 @@ private static void SetRolesAfterSelect() { if (pc == null) continue; var realrole = role; - if (role.IsDesyncRole()) realrole = CustomRoles.Scientist; + if (role.IsDesyncRole()) realrole = CustomRoles.Crewmate; if (role.IsDesyncRole()) RpcSetRoleReplacer.DesyncPlayers.Add(pc, role); RpcSetRoleReplacer.StoragedData.Add(pc, realrole.GetRoleTypes()); @@ -752,8 +752,9 @@ public static void Release() .EndRpc(); } else - { - seer.RpcChangeRoleBasis(role.GetRoleTypes(), true); + { // DESYNC NEUTRALS TEMPORALILY BROKEN, I need to fix it. + throw new NotImplementedException(); + // seer.RpcChangeRoleBasis(role.GetRoleTypes(), true); } SetDisconnectedMessage(sender.Value.stream, false); From 2933f6a9e545fdd0e2827e95f3e3743279f927c0 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:08:05 +0200 Subject: [PATCH 324/778] fix dy neutrals --- Modules/ExtendedPlayerControl.cs | 9 ++- .../PlayerGameOptionsSender.cs | 3 +- Patches/IntroPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 76 +++++++++++++------ 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 9851a9eb86..de55425ef8 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -269,12 +269,17 @@ public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleT { if (seer.PlayerId == player.PlayerId) continue; RoleTypes Typa = RoleTypes.Scientist; + RoleTypes TypaTwo = RoleTypes.Scientist; if (seer.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) Typa = RoleTypes.Noisemaker; - else if (FellowImps.Contains(seer) && seer.HasKillButton()) Typa = seer.GetCustomRole().GetVNRole().GetRoleTypes(); + else if (FellowImps.Contains(seer) && seer.HasKillButton()) + { + Typa = seer.GetCustomRole().GetVNRole().GetRoleTypes(); + TypaTwo = seer.GetCustomRole().GetVNRole().GetRoleTypes(); ; + } player.RpcSetRoleDesync(Typa, true, seer.GetClientId()); - seer.RpcSetRoleDesync(Typa, true, player.GetClientId()); + seer.RpcSetRoleDesync(TypaTwo, true, player.GetClientId()); } player.RpcSetRoleDesync(roleTypes, true, player.GetClientId()); } diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index f1df0e225d..16db0dbf64 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -78,8 +78,9 @@ public override IGameOptions BuildGameOptions() if (GameStates.IsNormalGame) AURoleOptions.SetOpt(opt); else if (GameStates.IsHideNSeek) return opt; + var CheckStartGameCREW = player.GetCustomRole().IsDesyncRole() && player.GetCustomRole().IsCrewmate() && !Main.introDestroyed; var state = Main.PlayerStates[player.PlayerId]; - opt.BlackOut(state.IsBlackOut || !Main.introDestroyed); + opt.BlackOut(state.IsBlackOut || CheckStartGameCREW); CustomRoles role = player.GetCustomRole(); if (Options.CurrentGameMode == CustomGameMode.FFA) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 978ef23277..99c0e9452a 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -603,7 +603,7 @@ public static void Postfix() DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); } - }, 1f, "Assign Impostor desync roles for crewmates"); + }, 0.1f, "Assign Impostor desync roles for crewmates"); foreach (var pc in Main.AllPlayerControls) { pc.MarkDirtySettings(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index abba25099a..76a0b837ed 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -397,16 +397,19 @@ private static void SetRolesAfterSelect() { if (pc == null) continue; var realrole = role; - if (role.IsDesyncRole()) realrole = CustomRoles.Crewmate; + if (role.IsDesyncRole()) + { + realrole = CustomRoles.Crewmate; + RpcSetRoleReplacer.DesyncPlayers.Add(pc, role); + } - if (role.IsDesyncRole()) RpcSetRoleReplacer.DesyncPlayers.Add(pc, role); RpcSetRoleReplacer.StoragedData.Add(pc, realrole.GetRoleTypes()); Logger.Warn($"Set original role type => {pc.GetRealName()}: {role} => {role.GetRoleTypes()}", "Override Role Select"); } // Set RoleType by "RpcSetRole" - RpcSetRoleReplacer.Release(); //Write RpcSetRole for all players + RpcSetRoleReplacer.Release(senders, rolesMap); //Write RpcSetRole for all players RpcSetRoleReplacer.senders.Do(kvp => kvp.Value.SendMessage()); // Delete unwanted objects @@ -682,29 +685,58 @@ private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dic Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); } - private static void MakeDesyncSender(Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap) + private static void MakeDesyncSender(PlayerControl target, Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap) { - foreach (var seer in Main.AllPlayerControls) + foreach (var seer in Main.AllPlayerControls) // HOW OTHER PEOPLE SEE DESYNC {target} { - foreach (var target in Main.AllPlayerControls) + if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleType)) { - if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleType)) + try { - try + // Change Scientist to Noisemaker when role is desync and target have Noisemaker role + if (roleType is RoleTypes.Scientist && RoleAssign.RoleResult.Any(x => x.Key.PlayerId == seer.PlayerId && x.Value is CustomRoles.NoisemakerTOHE or CustomRoles.Noisemaker)) { - // Change Scientist to Noisemaker when role is desync and target have Noisemaker role - if (roleType is RoleTypes.Scientist && RoleAssign.RoleResult.Any(x => x.Key.PlayerId == seer.PlayerId && x.Value is CustomRoles.NoisemakerTOHE or CustomRoles.Noisemaker)) - { - Logger.Info($"seer: {seer.PlayerId}, target: {target.PlayerId}, {roleType} => {RoleTypes.Noisemaker}", "OverrideRoleForDesync"); - roleType = RoleTypes.Noisemaker; - } + Logger.Info($"seer: {seer.PlayerId}, target: {target.PlayerId}, {roleType} => {RoleTypes.Noisemaker}", "OverrideRoleForDesync"); + roleType = RoleTypes.Noisemaker; + } - var sender = senders[seer.PlayerId]; - sender.RpcSetRole(seer, roleType, target.GetClientId()); + + Logger.Info($"seer: {seer.GetCustomRole()} target: {target.GetCustomRole()} , Roletype {roleType}", "Desync Neutral Roles [FOR OTHERS]"); + var sender = senders[target.PlayerId]; + sender.RpcSetRole(seer, roleType, target.GetClientId()); + } + catch + { } + } + else + { + Logger.Fatal($"{seer.GetRealName()}/ {seer.GetCustomRole()} Was desync but cannot get out Rolesmap group", "OnGameStartedPatch.MakeDesyncSender"); + } + } + foreach (var VTAR in Main.AllPlayerControls) // HOW DESYNC {target} SEE OTHER PEOPLE + { + if (rolesMap.TryGetValue((target.PlayerId, VTAR.PlayerId), out var roleType)) + { + try + { + // Change Scientist to Noisemaker when role is desync and target have Noisemaker role + if (roleType is RoleTypes.Scientist && RoleAssign.RoleResult.Any(x => x.Key.PlayerId == VTAR.PlayerId && x.Value is CustomRoles.NoisemakerTOHE or CustomRoles.Noisemaker)) + { + Logger.Info($"seer: {target.PlayerId}, target: {VTAR.PlayerId}, {roleType} => {RoleTypes.Noisemaker}", "OverrideRoleForDesync"); + roleType = RoleTypes.Noisemaker; } - catch - { } + + + Logger.Info($"seer: {target.GetCustomRole()} target: {VTAR.GetCustomRole()} , Roletype {roleType}", "Desync Neutral Roles [FOR SELF]"); + var sender = senders[VTAR.PlayerId]; + sender.RpcSetRole(target, roleType, VTAR.GetClientId()); } + catch + { } + } + else + { + Logger.Fatal($"{target.GetRealName()}/ {target.GetCustomRole()} Was desync but cannot get out Rolesmap group", "OnGameStartedPatch.MakeDesyncSender"); } } } @@ -730,7 +762,7 @@ public static bool Prefix() { return !doReplace; } - public static void Release() + public static void Release(Dictionary senderDY, Dictionary<(byte, byte), RoleTypes> RolesMapDY) { foreach (var sender in senders) { @@ -743,6 +775,7 @@ public static void Release() try { SetDisconnectedMessage(sender.Value.stream, true); + if (!DesyncPlayers.TryGetValue(seer, out var role) || role.IsCrewmate()) { seer.SetRole(roleType, true); @@ -752,9 +785,8 @@ public static void Release() .EndRpc(); } else - { // DESYNC NEUTRALS TEMPORALILY BROKEN, I need to fix it. - throw new NotImplementedException(); - // seer.RpcChangeRoleBasis(role.GetRoleTypes(), true); + { + MakeDesyncSender(seer, senderDY, RolesMapDY); } SetDisconnectedMessage(sender.Value.stream, false); From 5bc9707e7a58eb37fd5ae80adb9456446799932d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:10:53 +0200 Subject: [PATCH 325/778] stuff --- Modules/AntiBlackout.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 1acb6fce7e..cb1b4e1463 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -13,7 +13,7 @@ public static class AntiBlackout /// /// Check num alive Impostors & Crewmates & NeutralKillers /// - public static bool BlackOutIsActive => /*!Options.DisableAntiBlackoutProtects.GetBool() &&*/ CheckBlackOut(); + public static bool BlackOutIsActive => false; /*!Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut();*/ /// /// Count alive players and check black out @@ -65,7 +65,7 @@ public static bool CheckBlackOut() BlackOutIsActive = numAliveNeutralKillers == 1 && numAliveImpostors == 1 && numAliveCrewmates <= 2; Logger.Info($" {BlackOutIsActive}", "BlackOut Is Active"); - return false; //blackout is not needed anymore BlackOutIsActive; + return BlackOutIsActive; } public static bool IsCached { get; private set; } = false; From ab639a18a58b485602fb073cd09e0c8f86b76c63 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:15:15 +0200 Subject: [PATCH 326/778] exiled.object --- Modules/AntiBlackout.cs | 13 ++++++++++--- Patches/ExilePatch.cs | 9 +++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index cb1b4e1463..a9b2ccc959 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -254,7 +254,7 @@ public static void Reset() [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Die))] public static class ReassignImpostorPatch { - public static void Postfix(PlayerControl __instance) + /*public static void Postfix(PlayerControl __instance) { if (!AmongUsClient.Instance.AmHost || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor()) return; @@ -264,11 +264,11 @@ public static void Postfix(PlayerControl __instance) { Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); } - } + }*/ // If this part is needed, then it would have to be rpcsetrole and I'd try to avoid that cuz it's very goofy, so hopefully it is not. public static void FixDesyncImpostorRoles(this PlayerControl __instance) { - if (!AmongUsClient.Instance.AmHost || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() + if (!AmongUsClient.Instance.AmHost || __instance.IsAlive() || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) @@ -276,4 +276,11 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance) Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); } } + public static void FixDesyncImpostorRolesBYPASS(this PlayerControl __instance) // Completely skips all related checks and does it anyways + { + foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) + { + Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); + } + } } \ No newline at end of file diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index eb582c429f..eb06475ce5 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -73,10 +73,11 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (CLThingy && exiled != null) { // Reset player cam for exiled desync impostor - //if (exiled.Object.HasDesyncRole()) - // { - // exiled.Object?.ResetPlayerCam(1f); - // } + if (exiled.Object.HasDesyncRole()) + { + //exiled.Object?.ResetPlayerCam(1f); + exiled.Object.FixDesyncImpostorRolesBYPASS(); + } exiled.IsDead = true; exiled.PlayerId.SetDeathReason(PlayerState.DeathReason.Vote); From 81d1beecc4b3d28d412ae70613288176f8643167 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:23:34 +0200 Subject: [PATCH 327/778] BRIJADS --- Patches/ExilePatch.cs | 2 +- Patches/onGameStartedPatch.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index eb06475ce5..4e20cdd768 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -119,7 +119,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) player.ResetKillCooldown(); player.RpcResetAbilityCooldown(); - player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles, also doing this once after death dosen't help and has to be done every exile. + player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles } Main.MeetingIsStarted = false; diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 76a0b837ed..d1e26f975d 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -710,7 +710,7 @@ private static void MakeDesyncSender(PlayerControl target, Dictionary Date: Thu, 22 Aug 2024 10:43:36 +0200 Subject: [PATCH 328/778] fix --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index de55425ef8..929b328a6c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -261,7 +261,7 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleTypes, bool IsDesyncImpostor = false, List FellowImps = null) { if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; - FellowImps = []; + FellowImps ??= []; if (IsDesyncImpostor && roleTypes is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) { From 3f54cafcaac21d9ee18bcc6d8aef3da5ef627c97 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:35:15 +0200 Subject: [PATCH 329/778] fix --- Modules/AURoleOptions.cs | 5 +++++ Modules/ExtendedPlayerControl.cs | 2 +- Modules/GameOptionsSender/PlayerGameOptionsSender.cs | 6 ++++++ Modules/RPC.cs | 5 +++++ Patches/IntroPatch.cs | 10 ++++++---- Patches/onGameStartedPatch.cs | 6 +++--- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Modules/AURoleOptions.cs b/Modules/AURoleOptions.cs index 45412eda6e..79986011f8 100644 --- a/Modules/AURoleOptions.cs +++ b/Modules/AURoleOptions.cs @@ -6,6 +6,11 @@ public static class AURoleOptions { private static IGameOptions Opt; public static void SetOpt(IGameOptions opt) => Opt = opt; + public static int EmergencyMeetings + { + get => Opt.GetInt(Int32OptionNames.NumEmergencyMeetings); + set => Opt.SetInt(Int32OptionNames.NumEmergencyMeetings, value); + } public static float KillCooldown { get => Opt.GetFloat(FloatOptionNames.KillCooldown); diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 929b328a6c..01429c01e1 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -275,7 +275,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleT else if (FellowImps.Contains(seer) && seer.HasKillButton()) { Typa = seer.GetCustomRole().GetVNRole().GetRoleTypes(); - TypaTwo = seer.GetCustomRole().GetVNRole().GetRoleTypes(); ; + TypaTwo = seer.GetCustomRole().GetVNRole().GetRoleTypes(); } player.RpcSetRoleDesync(Typa, true, seer.GetClientId()); diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 16db0dbf64..b300ff951f 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -82,6 +82,12 @@ public override IGameOptions BuildGameOptions() var state = Main.PlayerStates[player.PlayerId]; opt.BlackOut(state.IsBlackOut || CheckStartGameCREW); + AURoleOptions.EmergencyMeetings = 0; + //if (!Main.introDestroyed) // If someone calls emergency meeting before host is loaded, the game goes KABOOM ?? + //{ + // AURoleOptions.EmergencyMeetings = 0; + //} NVM DIDN'T WORK + CustomRoles role = player.GetCustomRole(); if (Options.CurrentGameMode == CustomGameMode.FFA) { diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 368d1ee053..d3aef501b0 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -185,6 +185,11 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte ca case RpcCalls.StartMeeting: var p = Utils.GetPlayerById(subReader.ReadByte()); Logger.Info($"{__instance.GetNameWithRole()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); + + if (!Main.GameIsLoaded) + { + return false; + } break; } if (!__instance.OwnedByHost() && diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 99c0e9452a..9b1afd6e5e 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -595,15 +595,17 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } - //_ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcSetRole(RoleTypes.Impostor, true); }, 3f); - //_ = new LateTask(() => { Main.AllPlayerControls.First(x => x.PlayerId != 0).RpcChangeRoleBasis(RoleTypes.Impostor, true); }, 6f); - _ = new LateTask(() => { + foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) + { + DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); + } + _ = new LateTask(() => { // try again jsut incase it didn't work the first time foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) { DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); } - }, 0.1f, "Assign Impostor desync roles for crewmates"); + }, 3f, "Assign Impostor desync roles for crewmates"); foreach (var pc in Main.AllPlayerControls) { pc.MarkDirtySettings(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index d1e26f975d..220f88eae7 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -681,13 +681,13 @@ private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dic DestroyableSingleton.Instance.SetRole(player, BaseRole); DestroyableSingleton.Instance.CanBeKilled = true; } - player.Data.IsDead = true; + // player.Data.IsDead = true; Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); } private static void MakeDesyncSender(PlayerControl target, Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap) { - foreach (var seer in Main.AllPlayerControls) // HOW OTHER PEOPLE SEE DESYNC {target} + foreach (var seer in Main.AllPlayerControls) // HOW {target} see other people { if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleType)) { @@ -713,7 +713,7 @@ private static void MakeDesyncSender(PlayerControl target, Dictionary Date: Thu, 22 Aug 2024 15:27:38 +0200 Subject: [PATCH 330/778] fix --- Modules/ExtendedPlayerControl.cs | 14 +++++++++----- Patches/onGameStartedPatch.cs | 4 ++-- README.md | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 01429c01e1..7a934e6b34 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -269,17 +269,21 @@ public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleT { if (seer.PlayerId == player.PlayerId) continue; RoleTypes Typa = RoleTypes.Scientist; - RoleTypes TypaTwo = RoleTypes.Scientist; + RoleTypes Typatwo = RoleTypes.Scientist; if (seer.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) Typa = RoleTypes.Noisemaker; else if (FellowImps.Contains(seer) && seer.HasKillButton()) { Typa = seer.GetCustomRole().GetVNRole().GetRoleTypes(); - TypaTwo = seer.GetCustomRole().GetVNRole().GetRoleTypes(); + Typatwo = seer.GetCustomRole().GetVNRole().GetRoleTypes(); } - - player.RpcSetRoleDesync(Typa, true, seer.GetClientId()); - seer.RpcSetRoleDesync(TypaTwo, true, player.GetClientId()); + else if (!seer.HasKillButton()) + { + Typatwo = roleTypes; + } + + seer.RpcSetRoleDesync(Typa, true, player.GetClientId()); + player.RpcSetRoleDesync(Typatwo, true, seer.GetClientId()); } player.RpcSetRoleDesync(roleTypes, true, player.GetClientId()); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 220f88eae7..bef394a1b1 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -701,7 +701,7 @@ private static void MakeDesyncSender(PlayerControl target, Dictionary - Provided roles: AntiAdminer, CursedWolf, Workaholic, Greedier (Greedy), DarkHide (Stalker) > - Reference: Modify game announcement > - Ported new UI for settings (for version AU v2024.6.18) +> - Reference: Role-Basis changer fix > ### :star: [TOH:TOR](https://github.com/discus-sions/TownOfHost-TheOtherRoles) : > From 034c01868c716a9376b041897d03ca36d4be2cdb Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:19:07 +0200 Subject: [PATCH 331/778] rework entire system (tbh it was trash ngl) --- Patches/ExilePatch.cs | 4 +- Patches/IntroPatch.cs | 9 +- Patches/MeetingHudPatch.cs | 6 + Patches/PlayerJoinAndLeftPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 251 ++++++++++++------------------ Roles/Crewmate/Sheriff.cs | 2 +- 6 files changed, 115 insertions(+), 159 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 4e20cdd768..3e1d1f78b8 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -76,7 +76,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (exiled.Object.HasDesyncRole()) { //exiled.Object?.ResetPlayerCam(1f); - exiled.Object.FixDesyncImpostorRolesBYPASS(); + // exiled.Object.FixDesyncImpostorRolesBYPASS(); } exiled.IsDead = true; @@ -119,7 +119,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) player.ResetKillCooldown(); player.RpcResetAbilityCooldown(); - player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles + //player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles } Main.MeetingIsStarted = false; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 9b1afd6e5e..936ba7deed 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -595,17 +595,18 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } - foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) + + /*foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) { DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); - } + }*/ _ = new LateTask(() => { // try again jsut incase it didn't work the first time - foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) + foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && x.GetCustomRole().IsDesyncRole())) { DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); } - }, 3f, "Assign Impostor desync roles for crewmates"); + }, 0.1f, "Assign Impostor desync roles for crewmates"); foreach (var pc in Main.AllPlayerControls) { pc.MarkDirtySettings(); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 51211a33a2..5bc800eb83 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -366,6 +366,12 @@ public static bool Prefix(MeetingHud __instance) ExileControllerWrapUpPatch.AntiBlackout_LastExiled = exiledPlayer; Main.LastVotedPlayerInfo = exiledPlayer; + foreach (var pc in Main.AllPlayerControls) + { + pc.FixDesyncImpostorRoles(); + } + exiledPlayer?.Object?.FixDesyncImpostorRolesBYPASS(); + //RPC if (AntiBlackout.BlackOutIsActive) { diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index cb644e9959..6fbafc5e69 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -317,7 +317,7 @@ static void Prefix([HarmonyArgument(0)] ClientData data) Logger.Warn($"Assign roles not ended, try remove player {data.Character.PlayerId} from role assign", "OnPlayerLeft"); RoleAssign.RoleResult?.Remove(data.Character); RpcSetRoleReplacer.senders?.Remove(data.Character.PlayerId); - RpcSetRoleReplacer.StoragedData?.Remove(data.Character); + RpcSetRoleReplacer.StoragedPlayerRoleData = RpcSetRoleReplacer.StoragedPlayerRoleData.Where(x => x.Key.target != data.Character && x.Key.seer != data.Character).ToDictionary(x => x.Key, x => x.Value); } if (GameStates.IsNormalGame && GameStates.IsInGame) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index bef394a1b1..80cbfc4b8c 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -12,6 +12,9 @@ using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; using System.Linq; +using static UnityEngine.GraphicsBuffer; +using static UnityEngine.ParticleSystem.PlaybackState; +using Il2CppInterop.Generator.Extensions; namespace TOHE; @@ -375,47 +378,63 @@ private static void SetRolesAfterSelect() //Main.AssignRolesIsStarted = true; //Initialization of CustomRpcSender and RpcSetRoleReplacer - Dictionary senders = []; - foreach (var pc in Main.AllPlayerControls) - { - senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) - .StartMessage(pc.GetClientId()); - } - RpcSetRoleReplacer.StartReplace(senders); + RpcSetRoleReplacer.StartReplace(); - Dictionary<(byte, byte), RoleTypes> rolesMap = []; - // Assign desync roles - foreach (var (pc, role) in RoleAssign.RoleResult.Where(x => x.Value.IsDesyncRole())) - AssignDesyncRole(role, pc, senders, rolesMap, BaseRole: role.GetDYRole()); + //Not in use rn, but is gonna make it able to have neutral players of the same team spawn together + //Important to remember that all team players need to have all teamplayers in their lists + //gonna make a seperate thing making so that it's a rolebasething in another PR. + Dictionary> DesyncImpTeammates = []; - // Set Desync RoleType by "RpcSetRole" - //MakeDesyncSender(senders, rolesMap); + foreach (var blotnik in Main.AllPlayerControls) + { + RoleAssign.RoleResult[blotnik].GetStaticRoleClass(); + } - RpcSetRoleReplacer.DesyncPlayers.Clear(); - // Override RoleType for others players foreach (var (pc, role) in RoleAssign.RoleResult) { if (pc == null) continue; - var realrole = role; - if (role.IsDesyncRole()) + + + foreach (var seer in Main.AllPlayerControls) { - realrole = CustomRoles.Crewmate; - RpcSetRoleReplacer.DesyncPlayers.Add(pc, role); - } + CustomRoles ResultRole = RoleAssign.RoleResult[seer]; - RpcSetRoleReplacer.StoragedData.Add(pc, realrole.GetRoleTypes()); + bool isSelf = seer == pc; + RoleTypes typa = role.GetRoleTypes(); - Logger.Warn($"Set original role type => {pc.GetRealName()}: {role} => {role.GetRoleTypes()}", "Override Role Select"); + if (role is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) typa = RoleTypes.Noisemaker; + + //Desynced Imps see others as scientist if they are not a teammate + else if (!isSelf && ResultRole.IsDesyncRole() && !ResultRole.IsCrewmate() && + (!DesyncImpTeammates.TryGetValue(seer, out var teammates) || !teammates.Contains(pc))) typa = RoleTypes.Scientist; + + //Other see the desynced-imp target as scientist if they are not a teammate or not an crew + else if (!isSelf && role.IsDesyncRole() && !role.IsCrewmate() && !CheckSeerPassive(ResultRole) && + (!DesyncImpTeammates.TryGetValue(pc, out var bracy) || !bracy.Contains(seer))) typa = RoleTypes.Scientist; + + //Crewmates are assigned later + else if (role.IsCrewmate() && role.IsDesyncRole()) typa = RoleTypes.Crewmate; + + Logger.Warn($"Set Role for Target: {pc.GetRealName(clientData: true)}|{role} Seer: {seer.GetRealName(clientData: true)}|{ResultRole} of RoleType: {typa}", "SetStoragedPlayerData"); + RpcSetRoleReplacer.StoragedPlayerRoleData[(pc, seer)] = typa; + + + } + + // Logger.Warn($"Set original role type => {pc.GetRealName()} : {role} => {role.GetRoleTypes()}", "Override Role Select"); + } + static bool CheckSeerPassive(CustomRoles role) + { + return role.GetVNRole() is not CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom; } // Set RoleType by "RpcSetRole" - RpcSetRoleReplacer.Release(senders, rolesMap); //Write RpcSetRole for all players + RpcSetRoleReplacer.Release(); //Write RpcSetRole for all players RpcSetRoleReplacer.senders.Do(kvp => kvp.Value.SendMessage()); // Delete unwanted objects RpcSetRoleReplacer.senders = null; - RpcSetRoleReplacer.OverriddenSenderList = null; - RpcSetRoleReplacer.StoragedData = null; + RpcSetRoleReplacer.StoragedPlayerRoleData = null; //Main.AssignRolesIsStarted = false; //Utils.ApplySuffix(); @@ -474,8 +493,6 @@ private static void SetRolesAfterSelect() foreach (var kv in RoleAssign.RoleResult) { - if (kv.Value.IsDesyncRole()) continue; - AssignCustomRole(kv.Value, kv.Key); } @@ -650,96 +667,6 @@ private static void SetRolesAfterSelect() Utils.ThrowException(ex); } } - private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) - { - if (player == null) return; - - var hostId = PlayerControl.LocalPlayer.PlayerId; - var isHost = player.PlayerId == hostId; - - Main.PlayerStates[player.PlayerId].SetMainRole(role); - - var selfRole = isHost ? hostBaseRole : BaseRole; - var othersRole = isHost ? RoleTypes.Crewmate : RoleTypes.Scientist; - - // Set Desync role for self and for others - foreach (var target in Main.AllPlayerControls) - rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? othersRole : selfRole; - - // Set Desync role for others - foreach (var seer in Main.AllPlayerControls.Where(x => player.PlayerId != x.PlayerId).ToArray()) - rolesMap[(seer.PlayerId, player.PlayerId)] = othersRole; - - RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); - - //Set role for host - player.SetRole(othersRole, true); - - // Override RoleType for host - if (isHost && BaseRole == RoleTypes.Shapeshifter) - { - DestroyableSingleton.Instance.SetRole(player, BaseRole); - DestroyableSingleton.Instance.CanBeKilled = true; - } - // player.Data.IsDead = true; - - Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); - } - private static void MakeDesyncSender(PlayerControl target, Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap) - { - foreach (var seer in Main.AllPlayerControls) // HOW {target} see other people - { - if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleType)) - { - try - { - // Change Scientist to Noisemaker when role is desync and target have Noisemaker role - if (roleType is RoleTypes.Scientist && RoleAssign.RoleResult.Any(x => x.Key.PlayerId == seer.PlayerId && x.Value is CustomRoles.NoisemakerTOHE or CustomRoles.Noisemaker)) - { - Logger.Info($"seer: {seer.PlayerId}, target: {target.PlayerId}, {roleType} => {RoleTypes.Noisemaker}", "OverrideRoleForDesync"); - roleType = RoleTypes.Noisemaker; - } - - - Logger.Info($"seer: {seer.GetCustomRole()} target: {target.GetCustomRole()} , Roletype {roleType}", "Desync Neutral Roles [FOR SELF]"); - var sender = senders[target.PlayerId]; - sender.RpcSetRole(seer, roleType, target.GetClientId()); - } - catch - { } - } - else - { - Logger.Fatal($"seer: {seer.GetRealName()}/ target: {target.GetCustomRole()} Was desync but cannot get out Rolesmap group", "OnGameStartedPatch.MakeDesyncSender"); - } - } - foreach (var VTAR in Main.AllPlayerControls) // HOW other people see {target} - { - if (rolesMap.TryGetValue((target.PlayerId, VTAR.PlayerId), out var roleType)) - { - try - { - // Change Scientist to Noisemaker when role is desync and target have Noisemaker role - if (roleType is RoleTypes.Scientist && RoleAssign.RoleResult.Any(x => x.Key.PlayerId == VTAR.PlayerId && x.Value is CustomRoles.NoisemakerTOHE or CustomRoles.Noisemaker)) - { - Logger.Info($"seer: {target.PlayerId}, target: {VTAR.PlayerId}, {roleType} => {RoleTypes.Noisemaker}", "OverrideRoleForDesync"); - roleType = RoleTypes.Noisemaker; - } - - - Logger.Info($"seer: {target.GetCustomRole()} target: {VTAR.GetCustomRole()} , Roletype {roleType}", "Desync Neutral Roles [FOR OTHERS]"); - var sender = senders[VTAR.PlayerId]; - sender.RpcSetRole(target, roleType, VTAR.GetClientId()); - } - catch - { } - } - else - { - Logger.Fatal($" seer: {target.GetRealName()}/ target: {VTAR.GetCustomRole()} Was desync but cannot get out Rolesmap group", "OnGameStartedPatch.MakeDesyncSender"); - } - } - } private static void AssignCustomRole(CustomRoles role, PlayerControl player) { @@ -754,55 +681,72 @@ public static class RpcSetRoleReplacer { public static bool doReplace = false; public static Dictionary senders; - public static Dictionary StoragedData = []; - public static Dictionary DesyncPlayers = []; - // List of Senders that do not require additional writing because SetRoleRpc has already been written by another process such as Position Desync - public static List OverriddenSenderList; + public static Dictionary<(PlayerControl target, PlayerControl seer), RoleTypes> StoragedPlayerRoleData = []; public static bool Prefix() { return !doReplace; } - public static void Release(Dictionary senderDY, Dictionary<(byte, byte), RoleTypes> RolesMapDY) + public static void Release() { - foreach (var sender in senders) + foreach (var ((target, seer), roleType) in StoragedPlayerRoleData) { - //if (OverriddenSenderList.Contains(sender.Value)) continue; - if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) - throw new InvalidOperationException("A CustomRpcSender had Invalid State."); + if (target == seer) continue; - foreach (var (seer, roleType) in StoragedData) + if (seer.OwnedByHost()) { - try - { - SetDisconnectedMessage(sender.Value.stream, true); + target.SetRole(roleType, true); + continue; + } + var sender = senders[seer.PlayerId]; - if (!DesyncPlayers.TryGetValue(seer, out var role) || role.IsCrewmate()) - { - seer.SetRole(roleType, true); - sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) - .Write((ushort)roleType) - .Write(true) - .EndRpc(); - } - else - { - MakeDesyncSender(seer, senderDY, RolesMapDY); - } + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) + .Write((ushort)roleType) + .Write(true) + .EndRpc(); + } + SetSelfRoles(); + + doReplace = false; + } + + //Self roles set seperately so that we can trick the game into intro-cutsene via disconnecting everyone temporarily for client. + private static void SetSelfRoles() + { + foreach (var pc in Main.AllPlayerControls) + { + var roleType = StoragedPlayerRoleData[(pc, pc)]; + + var stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(6); + stream.Write(AmongUsClient.Instance.GameId); + stream.WritePacked(pc.GetClientId()); + { + SetDisconnectedMessage(stream, true); - SetDisconnectedMessage(sender.Value.stream, false); + if (pc.OwnedByHost()) + { + pc.SetRole(roleType, true); } - catch - { } + + stream.StartMessage(2); + stream.WritePacked(pc.NetId); + stream.Write((byte)RpcCalls.SetRole); + stream.Write((ushort)roleType); + stream.Write(true); //canOverrideRole + stream.EndMessage(); + Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); + + SetDisconnectedMessage(stream, false); } - sender.Value.EndMessage(); + stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); } - doReplace = false; } private static void SetDisconnectedMessage(MessageWriter stream, bool disconnected) { foreach (var pc in Main.AllPlayerControls) { - //if (pc.PlayerId != target.PlayerId) continue; pc.Data.Disconnected = disconnected; stream.StartMessage(1); @@ -813,12 +757,17 @@ private static void SetDisconnectedMessage(MessageWriter stream, bool disconnect } public static void Initialize() { - StoragedData = []; - OverriddenSenderList = []; + StoragedPlayerRoleData = []; doReplace = true; } - public static void StartReplace(Dictionary senders) + public static void StartReplace() { + Dictionary senders = []; + foreach (var pc in Main.AllPlayerControls) + { + senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) + .StartMessage(pc.GetClientId()); + } RpcSetRoleReplacer.senders = senders; doReplace = true; } diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index f826955729..ffeba09e97 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -11,7 +11,7 @@ internal class Sheriff : RoleBase private const int Id = 11200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sheriff); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; // Lol don't mind this, I was testing something public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ From 7029aa97fdc500b4b877eeb517162b8666b96492 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:21:07 +0200 Subject: [PATCH 332/778] fix --- Patches/onGameStartedPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 80cbfc4b8c..df6e16137a 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -425,7 +425,7 @@ private static void SetRolesAfterSelect() } static bool CheckSeerPassive(CustomRoles role) { - return role.GetVNRole() is not CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom; + return role.GetVNRole() is not CustomRoles.Impostor and not CustomRoles.Shapeshifter and not CustomRoles.Phantom; } // Set RoleType by "RpcSetRole" From c9b9dcd051282e7413b8ef8aa5a9f5c787799608 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:27:58 +0200 Subject: [PATCH 333/778] remove some unwanted stuff --- Modules/AURoleOptions.cs | 5 ----- Modules/GameOptionsSender/PlayerGameOptionsSender.cs | 6 ------ Modules/RPC.cs | 5 ----- Patches/onGameStartedPatch.cs | 3 --- 4 files changed, 19 deletions(-) diff --git a/Modules/AURoleOptions.cs b/Modules/AURoleOptions.cs index 79986011f8..45412eda6e 100644 --- a/Modules/AURoleOptions.cs +++ b/Modules/AURoleOptions.cs @@ -6,11 +6,6 @@ public static class AURoleOptions { private static IGameOptions Opt; public static void SetOpt(IGameOptions opt) => Opt = opt; - public static int EmergencyMeetings - { - get => Opt.GetInt(Int32OptionNames.NumEmergencyMeetings); - set => Opt.SetInt(Int32OptionNames.NumEmergencyMeetings, value); - } public static float KillCooldown { get => Opt.GetFloat(FloatOptionNames.KillCooldown); diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index b300ff951f..16db0dbf64 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -82,12 +82,6 @@ public override IGameOptions BuildGameOptions() var state = Main.PlayerStates[player.PlayerId]; opt.BlackOut(state.IsBlackOut || CheckStartGameCREW); - AURoleOptions.EmergencyMeetings = 0; - //if (!Main.introDestroyed) // If someone calls emergency meeting before host is loaded, the game goes KABOOM ?? - //{ - // AURoleOptions.EmergencyMeetings = 0; - //} NVM DIDN'T WORK - CustomRoles role = player.GetCustomRole(); if (Options.CurrentGameMode == CustomGameMode.FFA) { diff --git a/Modules/RPC.cs b/Modules/RPC.cs index d3aef501b0..368d1ee053 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -185,11 +185,6 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte ca case RpcCalls.StartMeeting: var p = Utils.GetPlayerById(subReader.ReadByte()); Logger.Info($"{__instance.GetNameWithRole()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); - - if (!Main.GameIsLoaded) - { - return false; - } break; } if (!__instance.OwnedByHost() && diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index df6e16137a..cda5bad43c 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -12,9 +12,6 @@ using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; using System.Linq; -using static UnityEngine.GraphicsBuffer; -using static UnityEngine.ParticleSystem.PlaybackState; -using Il2CppInterop.Generator.Extensions; namespace TOHE; From 127262d42ed0210ec93b6e110e5dcc7387e4fca0 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:15:03 +0200 Subject: [PATCH 334/778] stuffs --- Patches/PlayerJoinAndLeftPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 2 +- Roles/Core/RoleBase.cs | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 6fbafc5e69..1ec280e3e2 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -317,7 +317,7 @@ static void Prefix([HarmonyArgument(0)] ClientData data) Logger.Warn($"Assign roles not ended, try remove player {data.Character.PlayerId} from role assign", "OnPlayerLeft"); RoleAssign.RoleResult?.Remove(data.Character); RpcSetRoleReplacer.senders?.Remove(data.Character.PlayerId); - RpcSetRoleReplacer.StoragedPlayerRoleData = RpcSetRoleReplacer.StoragedPlayerRoleData.Where(x => x.Key.target != data.Character && x.Key.seer != data.Character).ToDictionary(x => x.Key, x => x.Value); + RpcSetRoleReplacer.StoragedPlayerRoleData = RpcSetRoleReplacer.StoragedPlayerRoleData?.Where(x => x.Key.target != data.Character && x.Key.seer != data.Character).ToDictionary(x => x.Key, x => x.Value); } if (GameStates.IsNormalGame && GameStates.IsInGame) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index cda5bad43c..30e56a4c44 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -384,7 +384,7 @@ private static void SetRolesAfterSelect() foreach (var blotnik in Main.AllPlayerControls) { - RoleAssign.RoleResult[blotnik].GetStaticRoleClass(); + RoleAssign.RoleResult[blotnik].GetStaticRoleClass().SetDesyncImpostorBuddies(ref DesyncImpTeammates, blotnik); } foreach (var (pc, role) in RoleAssign.RoleResult) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 89d0cb8396..3fbb625644 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -19,6 +19,8 @@ public abstract class RoleBase public bool HasVoted = false; public virtual bool IsExperimental => false; public virtual bool IsDesyncRole => false; + public virtual bool IsSideKick => false; + public void OnInit() // CustomRoleManager.RoleClass executes this { IsEnable = false; @@ -84,6 +86,15 @@ public virtual void Remove(byte playerId) /// public CustomRoles ThisCustomRole => System.Enum.Parse(GetType().Name, true); + + + /// + /// A generic method to set if someone (desync imps) should see each-other on the reveal screen. + /// + public virtual void SetDesyncImpostorBuddies(ref Dictionary> DesyncImpostorBuddy, PlayerControl caller) + { + + } /// /// A generic method to set if a impostor/SS base may use kill button. /// From ee2268070ade57ee0abf7cbf94504db5aba066b6 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:19:59 +0200 Subject: [PATCH 335/778] remove comment --- Patches/IntroPatch.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 936ba7deed..c0d92c2063 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -596,11 +596,7 @@ public static void Postfix() } - /*foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && RpcSetRoleReplacer.DesyncPlayers.TryGetValue(x, out _))) - { - DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); - }*/ - _ = new LateTask(() => { // try again jsut incase it didn't work the first time + _ = new LateTask(() => { foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && x.GetCustomRole().IsDesyncRole())) { DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); From 9f480d13f5052a741640696a3b317d80595fb156 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:12:52 +0200 Subject: [PATCH 336/778] temp-fix blackscreen --- Modules/AntiBlackout.cs | 34 ++++++++++++++++++++++++++++---- Modules/ExtendedPlayerControl.cs | 12 +++++++++++ Patches/ChatCommandPatch.cs | 9 +++++++++ Patches/ExilePatch.cs | 1 + Patches/MeetingHudPatch.cs | 5 ----- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index a9b2ccc959..1766b08c44 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -93,6 +93,11 @@ public static void SetIsDead(bool doSend = true, [CallerMemberName] string calle } public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { + foreach (var pc in Main.AllPlayerControls) + { + pc.FixDesyncImpostorRoles(); + } + logger.Info($"RestoreIsDead is called from {callerMethodName}"); foreach (var info in GameData.Instance.AllPlayers) { @@ -251,7 +256,6 @@ public static void Reset() public static bool ShowExiledInfo = false; public static string StoreExiledMessage = ""; } -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Die))] public static class ReassignImpostorPatch { /*public static void Postfix(PlayerControl __instance) @@ -271,16 +275,38 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance) if (!AmongUsClient.Instance.AmHost || __instance.IsAlive() || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; - foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) + Logger.Info($"I am running for {__instance.GetRealName()}/{__instance.GetCustomRole()}", "DesyncIMPFIX"); + + + //Toh-y somehow works, so it would be better to get a similar, and this is a simple temp-fix that works + __instance.ReactorFlash(); + RoleTypes prevtype = __instance.Data.Role.Role; + __instance.RpcSetRoleDesync(RoleTypes.Crewmate, true, __instance.GetClientId()); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.Exiled, SendOption.None, __instance.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + __instance.RpcSetRoleDesync(prevtype, true, __instance.GetClientId()); + + /* + foreach (var Killer in Main.AllPlayerControls.Where(x => x != __instance)) { Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); - } + }*/ // dosen't work, will have to change it to be similar to toh-y but not change to crewmate ghost. } public static void FixDesyncImpostorRolesBYPASS(this PlayerControl __instance) // Completely skips all related checks and does it anyways { + + //Toh-y somehow works, so it would be better to get smt similar, and this is a simple temp-fix that works + __instance.ReactorFlash(); + RoleTypes prevtype = __instance.Data.Role.Role; + __instance.RpcSetRoleDesync(RoleTypes.Crewmate, true, __instance.GetClientId()); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.Exiled, SendOption.None, __instance.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + __instance.RpcSetRoleDesync(prevtype, true, __instance.GetClientId()); + + /* foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) { Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); - } + }*/ } } \ No newline at end of file diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 7a934e6b34..deb9e7657e 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -252,6 +252,18 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, AmongUsClient.Instance.FinishRpcImmediately(writer); } + public static void RpcRevive(this PlayerControl player, RoleTypes roleTypes, bool IsDesyncImpostor = false, List FellowImps = null) + { + if (player.Data.IsDead == false || roleTypes is RoleTypes.GuardianAngel or RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) + { + Logger.Warn($"Invalid Revive for {player.GetRealName()} of roletype: {roleTypes} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); + return; + } + + player.RpcChangeRoleBasis(roleTypes, IsDesyncImpostor, FellowImps); + Main.PlayerStates[player.PlayerId].IsDead = false; + } + /// /// Changes the Role Basis of player during the game. /// diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 32a605693a..d64586c4ef 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1,3 +1,4 @@ +using AmongUs.GameOptions; using Assets.CoreScripts; using Hazel; using System; @@ -187,6 +188,14 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); break; + case "/revive": + if (args.Length != 2 || !int.TryParse(args[1], out int identity)) break; + + Utils.GetPlayerById(identity).RpcRevive(RoleTypes.Crewmate); + + + break; + case "/rn": case "/rename": case "/renomear": diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 3e1d1f78b8..a1d56e091a 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -121,6 +121,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) //player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles } + exiled?.Object?.FixDesyncImpostorRolesBYPASS(); Main.MeetingIsStarted = false; Main.MeetingsPassed++; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 5bc800eb83..89325f3a3a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -366,11 +366,6 @@ public static bool Prefix(MeetingHud __instance) ExileControllerWrapUpPatch.AntiBlackout_LastExiled = exiledPlayer; Main.LastVotedPlayerInfo = exiledPlayer; - foreach (var pc in Main.AllPlayerControls) - { - pc.FixDesyncImpostorRoles(); - } - exiledPlayer?.Object?.FixDesyncImpostorRolesBYPASS(); //RPC if (AntiBlackout.BlackOutIsActive) From ed282ff2e97f65e37c377079efd4ce0d95ebf067 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 23 Aug 2024 21:40:30 +0800 Subject: [PATCH 337/778] CoLoggerGameInfo() (Optimize intro scene) --- Modules/Debugger.cs | 36 +++++++--- Modules/Utils.cs | 15 ++-- Patches/IntroPatch.cs | 156 +++++++++++++++++++++++------------------- 3 files changed, 123 insertions(+), 84 deletions(-) diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index 7af5f27292..da2e254e62 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -53,13 +53,13 @@ private static void SendToFile(string text, LogLevel level = LogLevel.Info, stri if (!IsEnable || DisableList.Contains(tag)) return; var logger = Main.Logger; - if (SendToGameList.Contains(tag) || isAlsoInGame) + if (SendToGameList.Contains(tag)) { SendInGame($"[{tag}]{text}"); } string log_text; - if (level is LogLevel.Error or LogLevel.Fatal or LogLevel.Warning && !multiLine && !NowDetailedErrorLog.Contains(tag)) + if (level is LogLevel.Error or LogLevel.Fatal && !multiLine && !NowDetailedErrorLog.Contains(tag)) { string t = DateTime.Now.ToString("HH:mm:ss"); StackFrame stack = new(2); @@ -78,26 +78,44 @@ private static void SendToFile(string text, LogLevel level = LogLevel.Info, stri switch (level) { - case LogLevel.Info: + case LogLevel.Info when !multiLine: logger.LogInfo(log_text); break; - case LogLevel.Warning: + case LogLevel.Info: + log_text.Split("\\n").Do(logger.LogInfo); + break; + case LogLevel.Warning when !multiLine: logger.LogWarning(log_text); break; - case LogLevel.Error: + case LogLevel.Warning: + log_text.Split("\\n").Do(logger.LogWarning); + break; + case LogLevel.Error when !multiLine: logger.LogError(log_text); break; - case LogLevel.Fatal: + case LogLevel.Error: + log_text.Split("\\n").Do(logger.LogError); + break; + case LogLevel.Fatal when !multiLine: logger.LogFatal(log_text); break; - case LogLevel.Message: + case LogLevel.Fatal: + log_text.Split("\\n").Do(logger.LogFatal); + break; + case LogLevel.Message when !multiLine: logger.LogMessage(log_text); break; - case LogLevel.Debug: + case LogLevel.Message: + log_text.Split("\\n").Do(logger.LogMessage); + break; + case LogLevel.Debug when !multiLine: logger.LogFatal(log_text); break; + case LogLevel.Debug: + log_text.Split("\\n").Do(logger.LogFatal); + break; default: - logger.LogWarning("Error:Invalid LogLevel"); + logger.LogWarning("Error: Invalid LogLevel"); logger.LogInfo(log_text); break; } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 528a8202a0..3fe71e110b 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1015,13 +1015,13 @@ public static void ThrowException(Exception ex, [CallerFilePath] string fileName { try { - StackTrace st = new(1, true); + StackTrace st = new(0, true); StackFrame[] stFrames = st.GetFrames(); StackFrame firstFrame = stFrames.FirstOrDefault(); var sb = new StringBuilder(); - sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName} at line {lineNumber} in {callerMemberName}\n------ Stack Trace ------"); + sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); bool skip = true; foreach (StackFrame sf in stFrames) @@ -1037,10 +1037,17 @@ public static void ThrowException(Exception ex, [CallerFilePath] string fileName string callerMethodName = callerMethod?.Name; string callerClassName = callerMethod?.DeclaringType?.FullName; - sb.Append($"\n at {callerClassName}.{callerMethodName}"); + var line = $"line {sf.GetFileLineNumber()} ({sf.GetFileColumnNumber()}) in {sf.GetFileName()}"; + + sb.Append($"\n at {callerClassName}.{callerMethodName} ({line})"); } - sb.Append("\n------ End of Stack Trace ------"); + sb.Append("\n------ End of Method Stack Trace ------"); + sb.Append("\n------ Exception Stack Trace ------"); + + sb.Append(ex.StackTrace?.Replace("\r\n", "\n").Replace("\\n", "\n").Replace("\n", "\n ")); + + sb.Append("\n------ End of Exception Stack Trace ------"); Logger.Error(sb.ToString(), firstFrame?.GetMethod()?.ToString(), multiLine: true); } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 4c7fe4d0e5..f9b0827393 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using System; using System.IO; using System.Security.Cryptography; @@ -100,27 +101,78 @@ public static void Postfix(IntroCutscene __instance) [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] class CoBeginPatch { - public static void Prefix() + public static void Prefix(IntroCutscene __instance) { if (RoleBasisChanger.IsChangeInProgress) return; - var logger = Logger.Handler("Info"); + if (GameStates.IsNormalGame) + { + foreach (var player in Main.AllPlayerControls) + { + Main.PlayerStates[player.PlayerId].InitTask(player); + } + + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + } + + __instance.StartCoroutine(CoLoggerGameInfo().WrapToIl2Cpp()); + + GameStates.InGame = true; + RPC.RpcVersionCheck(); + + // Do not move this code, it should be executed at the very end to prevent a visual bug + Utils.DoNotifyRoles(ForceLoop: true); + if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) + { + RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch + { + 0 => new RandomSpawn.SkeldSpawnMap(), + 1 => new RandomSpawn.MiraHQSpawnMap(), + 2 => new RandomSpawn.PolusSpawnMap(), + 3 => new RandomSpawn.DleksSpawnMap(), + 5 => new RandomSpawn.FungleSpawnMap(), + _ => null, + }; + if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); + } + } + public static byte[] EncryptDES(byte[] data, string key) + { + using SymmetricAlgorithm desAlg = DES.Create(); + + // Incoming key must be 8 bit or will cause error + desAlg.Key = Encoding.UTF8.GetBytes(key); + desAlg.IV = Encoding.UTF8.GetBytes(key); + + using MemoryStream msEncrypt = new(); + using (CryptoStream csEncrypt = new(msEncrypt, desAlg.CreateEncryptor(), CryptoStreamMode.Write)) + { + csEncrypt.Write(data, 0, data.Length); + } + return msEncrypt.ToArray(); + } + private static System.Collections.IEnumerator CoLoggerGameInfo() + { var allPlayerControlsArray = Main.AllPlayerControls; + var sb = new StringBuilder(); - logger.Info("------------Player Names------------"); - foreach ( var pc in allPlayerControlsArray) + sb.Append("------------Player Names------------\n"); + foreach (var pc in allPlayerControlsArray) { - logger.Info($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", "")})"); + sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", "")})\n"); pc.cosmetics.nameText.text = pc.name; } - logger.Info("------------Roles / Add-ons------------"); + yield return null; + + sb.Append("------------Roles / Add-ons------------\n"); if (PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug || GameStates.IsLocalGame) { foreach (var pc in allPlayerControlsArray) { - logger.Info($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc?.Data?.PlayerName?.PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags()}"); + sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc?.Data?.PlayerName?.PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); } } else @@ -138,107 +190,69 @@ public static void Prefix() byte[] logBytes = Encoding.UTF8.GetBytes(logStringBuilder.ToString()); byte[] encryptedBytes = EncryptDES(logBytes, $"TOHE{PlayerControl.LocalPlayer.PlayerId}00000000"[..8]); string encryptedString = Convert.ToBase64String(encryptedBytes); - logger.Info(encryptedString); + sb.Append(encryptedString); } catch (Exception ex) { - logger.Error($"Encryption error: {ex.Message}"); + Logger.Error($"Encryption error: {ex.Message}", "Intro.Roles"); } } //https://www.toolhelper.cn/SymmetricEncryption/DES //mode CBC, PKCS7, 64bit, Key = IV= "TOHE" + playerid + 000/00 "to 8 bits - logger.Info("------------Player Platforms------------"); + yield return null; + + sb.Append("------------Player Platforms------------\n"); foreach (var pc in allPlayerControlsArray) { try { - var text = pc.AmOwner ? "[*]" : " "; - text += $"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString()?.Replace("Standalone", ""),-11}"; + var text = new StringBuilder(); + sb.Append(pc.AmOwner ? "[*]" : " "); + sb.Append($"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString()?.Replace("Standalone", ""),-11}"); if (Main.playerVersion.TryGetValue(pc.GetClientId(), out PlayerVersion pv)) { - text += $":Mod({pv.forkId}/{pv.version}:{pv.tag}), ClientId :{pc.GetClientId()}"; + sb.Append($":Mod({pv.forkId}/{pv.version}:{pv.tag}), ClientId :{pc.GetClientId()}"); } else { - text += ":Vanilla, ClientId :" + pc.GetClientId() ; + sb.Append($":Vanilla, ClientId :{pc.GetClientId()}"); } - logger.Info(text); + sb.Append(text + "\n"); } catch (Exception ex) { - Logger.Exception(ex, "Platform"); + Logger.Exception(ex, "Intro.Platform"); } } - logger.Info("------------Vanilla Settings------------"); + yield return null; + + sb.Append("------------Vanilla Settings------------\n"); var tmp = GameOptionsManager.Instance.CurrentGameOptions.ToHudString(GameData.Instance ? GameData.Instance.PlayerCount : 10).Split("\r\n").Skip(1).ToArray(); foreach (var text in tmp) { - logger.Info(text); + sb.Append(text + "\n"); } + yield return null; - logger.Info("------------Mod Settings------------"); - var allOptionsArray = OptionItem.AllOptions.ToArray(); - foreach (var option in allOptionsArray) + sb.Append("------------Modded Settings------------\n"); + foreach (OptionItem o in OptionItem.AllOptions) { - if (!option.IsHiddenOn(Options.CurrentGameMode) && (option.Parent == null ? !option.GetString().Equals("0%") : option.Parent.GetBool())) - { - logger.Info($"{(option.Parent == null - ? option.GetName(true, true).RemoveHtmlTags().PadRightV2(40) - : $"┗ {option.GetName(true, true).RemoveHtmlTags()}".PadRightV2(41))}:{option.GetString().RemoveHtmlTags()}"); - } + if (!o.IsHiddenOn(Options.CurrentGameMode) && (o.Parent?.GetBool() ?? !o.GetString().Equals("0%"))) + sb.Append($"{(o.Parent == null ? o.GetName(true, true).RemoveHtmlTags().PadRightV2(40) : $"┗ {o.GetName(true, true).RemoveHtmlTags()}".PadRightV2(41))}:{o.GetString().RemoveHtmlTags()}\n"); } - if (GameStates.IsNormalGame) - { - logger.Info("-------------Other Information-------------"); - logger.Info($"Number players: {allPlayerControlsArray.Length}"); - foreach (var player in allPlayerControlsArray) - { - Main.PlayerStates[player.PlayerId].InitTask(player); - } - - GameData.Instance.RecomputeTaskCounts(); - TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - } + yield return null; - GameStates.InGame = true; - RPC.RpcVersionCheck(); + sb.Append("-------------Other Information-------------\n"); + sb.Append($"Number players: {allPlayerControlsArray.Length}"); - // Do not move this code, it should be executed at the very end to prevent a visual bug - Utils.DoNotifyRoles(ForceLoop: true); + yield return null; - if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) - { - RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null, - }; - if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); - } - } - public static byte[] EncryptDES(byte[] data, string key) - { - using SymmetricAlgorithm desAlg = DES.Create(); - - // Incoming key must be 8 bit or will cause error - desAlg.Key = Encoding.UTF8.GetBytes(key); - desAlg.IV = Encoding.UTF8.GetBytes(key); - - using MemoryStream msEncrypt = new(); - using (CryptoStream csEncrypt = new(msEncrypt, desAlg.CreateEncryptor(), CryptoStreamMode.Write)) - { - csEncrypt.Write(data, 0, data.Length); - } - return msEncrypt.ToArray(); + Logger.Info(sb.ToString(), "GameInfo", multiLine: true); } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] From 892d0ff432e920a07db34e2f823a4ce5882b63bd Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 23 Aug 2024 21:42:53 +0800 Subject: [PATCH 338/778] Fix --- Modules/Debugger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index da2e254e62..77ec9b49f9 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -53,13 +53,13 @@ private static void SendToFile(string text, LogLevel level = LogLevel.Info, stri if (!IsEnable || DisableList.Contains(tag)) return; var logger = Main.Logger; - if (SendToGameList.Contains(tag)) + if (SendToGameList.Contains(tag) || isAlsoInGame) { SendInGame($"[{tag}]{text}"); } string log_text; - if (level is LogLevel.Error or LogLevel.Fatal && !multiLine && !NowDetailedErrorLog.Contains(tag)) + if (level is LogLevel.Error or LogLevel.Fatal or LogLevel.Warning && !multiLine && !NowDetailedErrorLog.Contains(tag)) { string t = DateTime.Now.ToString("HH:mm:ss"); StackFrame stack = new(2); From 6b4aff41bbffdefb4f372cbab8c8c446d163bd30 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:17:52 +0200 Subject: [PATCH 339/778] add rpc --- Modules/ExtendedPlayerControl.cs | 1 + Modules/RPC.cs | 26 +++++++++++++++++++++++++- Modules/Utils.cs | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index deb9e7657e..a3f561f1d5 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -262,6 +262,7 @@ public static void RpcRevive(this PlayerControl player, RoleTypes roleTypes, boo player.RpcChangeRoleBasis(roleTypes, IsDesyncImpostor, FellowImps); Main.PlayerStates[player.PlayerId].IsDead = false; + player.SyncGeneralOptions(); } /// diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 368d1ee053..611494c1d4 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -15,7 +15,7 @@ namespace TOHE; -enum CustomRPC : byte // 197/255 USED +enum CustomRPC : byte // 198/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 @@ -51,6 +51,7 @@ enum CustomRPC : byte // 197/255 USED ShowChat, SyncShieldPersonDiedFirst, RemoveSubRole, + SyncGeneralOptions, //Roles SetBountyTarget, @@ -516,6 +517,29 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SyncPsychicRedList: Psychic.ReceiveRPC(reader); break; + case CustomRPC.SyncGeneralOptions: + byte paciefID = reader.ReadByte(); + //playerstate: + { + CustomRoles rola = (CustomRoles)reader.ReadPackedInt32(); + bool isdead = reader.ReadBoolean(); + bool IsDC = reader.ReadBoolean(); + PlayerState.DeathReason drip = (PlayerState.DeathReason)reader.ReadPackedInt32(); + if (Main.PlayerStates.ContainsKey(paciefID)) + { + var state = Main.PlayerStates[paciefID]; + state.MainRole = rola; + state.IsDead = isdead; + state.Disconnected = isdead; + state.deathReason = drip; + } + } + float Killcd = reader.ReadSingle(); + float speed = reader.ReadSingle(); + + Main.AllPlayerKillCooldown[paciefID] = Killcd; + Main.AllPlayerSpeed[paciefID] = speed; + break; case CustomRPC.SyncPlayerSetting: byte playerid = reader.ReadByte(); CustomRoles rl = (CustomRoles)reader.ReadPackedInt32(); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 3fe71e110b..c4e9eeef36 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -379,6 +379,21 @@ public static string GetDeathReason(PlayerState.DeathReason status) { return GetString("DeathReason." + Enum.GetName(typeof(PlayerState.DeathReason), status)); } + + public static void SyncGeneralOptions(this PlayerControl player) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions, SendOption.Reliable, -1); + writer.Write(player.PlayerId); + writer.WritePacked((int)player.GetCustomRole()); + writer.Write(Main.PlayerStates[player.PlayerId].IsDead); + writer.Write(Main.PlayerStates[player.PlayerId].Disconnected); + writer.WritePacked((int)Main.PlayerStates[player.PlayerId].deathReason); + writer.Write(Main.AllPlayerKillCooldown[player.PlayerId]); + writer.Write(Main.AllPlayerSpeed[player.PlayerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } public static float GetDistance(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); public static Color GetRoleColor(CustomRoles role) { From cda38fa0127695d0c0786b50439320bf89121033 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:24:49 +0200 Subject: [PATCH 340/778] change --- Modules/AntiBlackout.cs | 23 ++++--------------- .../PlayerGameOptionsSender.cs | 3 +-- Patches/ExilePatch.cs | 2 +- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 1766b08c44..3e4289a789 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -270,10 +270,12 @@ public static class ReassignImpostorPatch } }*/ // If this part is needed, then it would have to be rpcsetrole and I'd try to avoid that cuz it's very goofy, so hopefully it is not. - public static void FixDesyncImpostorRoles(this PlayerControl __instance) + public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool skipCheck = false) { + if (AmongUsClient.Instance.AmHost && skipCheck) goto fixrole; if (!AmongUsClient.Instance.AmHost || __instance.IsAlive() || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() - && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; + && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; + fixrole: Logger.Info($"I am running for {__instance.GetRealName()}/{__instance.GetCustomRole()}", "DesyncIMPFIX"); @@ -292,21 +294,4 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance) Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); }*/ // dosen't work, will have to change it to be similar to toh-y but not change to crewmate ghost. } - public static void FixDesyncImpostorRolesBYPASS(this PlayerControl __instance) // Completely skips all related checks and does it anyways - { - - //Toh-y somehow works, so it would be better to get smt similar, and this is a simple temp-fix that works - __instance.ReactorFlash(); - RoleTypes prevtype = __instance.Data.Role.Role; - __instance.RpcSetRoleDesync(RoleTypes.Crewmate, true, __instance.GetClientId()); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.Exiled, SendOption.None, __instance.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - __instance.RpcSetRoleDesync(prevtype, true, __instance.GetClientId()); - - /* - foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) - { - Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); - }*/ - } } \ No newline at end of file diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 16db0dbf64..77b422d3b8 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -78,9 +78,8 @@ public override IGameOptions BuildGameOptions() if (GameStates.IsNormalGame) AURoleOptions.SetOpt(opt); else if (GameStates.IsHideNSeek) return opt; - var CheckStartGameCREW = player.GetCustomRole().IsDesyncRole() && player.GetCustomRole().IsCrewmate() && !Main.introDestroyed; var state = Main.PlayerStates[player.PlayerId]; - opt.BlackOut(state.IsBlackOut || CheckStartGameCREW); + opt.BlackOut(state.IsBlackOut); CustomRoles role = player.GetCustomRole(); if (Options.CurrentGameMode == CustomGameMode.FFA) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index a1d56e091a..5af68cc250 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -121,7 +121,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) //player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles } - exiled?.Object?.FixDesyncImpostorRolesBYPASS(); + exiled?.Object?.FixDesyncImpostorRoles(true); Main.MeetingIsStarted = false; Main.MeetingsPassed++; From 4816e5f2691581972818b27c01cc6bc805d6c9af Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:25:58 +0200 Subject: [PATCH 341/778] nothing --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 3e4289a789..eae0677c41 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -280,7 +280,7 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool sk Logger.Info($"I am running for {__instance.GetRealName()}/{__instance.GetCustomRole()}", "DesyncIMPFIX"); - //Toh-y somehow works, so it would be better to get a similar, and this is a simple temp-fix that works + //Toh-y somehow works, so it would be better to get a similar fix, and this is a simple temp-fix that works __instance.ReactorFlash(); RoleTypes prevtype = __instance.Data.Role.Role; __instance.RpcSetRoleDesync(RoleTypes.Crewmate, true, __instance.GetClientId()); From 4298684949d79bae8ee627a37f88d3a29ff166fe Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 23 Aug 2024 22:26:36 +0800 Subject: [PATCH 342/778] Remove old API Url --- Modules/dbConnect.cs | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/Modules/dbConnect.cs b/Modules/dbConnect.cs index a2b7086055..a964db84d0 100644 --- a/Modules/dbConnect.cs +++ b/Modules/dbConnect.cs @@ -12,11 +12,9 @@ namespace TOHE; public class dbConnect { private static bool InitOnce = false; - private static Dictionary userType = []; + private static Dictionary UserType = []; - // API Url - private const string oldApiUrl = "https://api.tohre.dev"; - private const string newApiUrl = "https://api.weareten.ca"; + private const string ApiUrl = "https://api.weareten.ca"; public static IEnumerator Init() { @@ -38,7 +36,7 @@ public static IEnumerator Init() yield break; } - if (userType.Count < 1) + if (UserType.Count < 1) { HandleFailure(FailedConnectReason.Error_Getting_User_Role_Table); yield break; @@ -159,12 +157,7 @@ private static IEnumerator GetRoleTable() yield return null; } - string apiUrl; - var discontinuationDate = new DateTime(2024, 8, 21); - var today = DateTime.UtcNow; - if (today < discontinuationDate) apiUrl = oldApiUrl; // Replace with your actual API URL - else apiUrl = newApiUrl; - + string apiUrl = ApiUrl; string endpoint = $"{apiUrl}/userInfo?token={apiToken}"; UnityWebRequest webRequest = UnityWebRequest.Get(endpoint); @@ -199,7 +192,7 @@ private static IEnumerator GetRoleTable() tempUserType[userData["friendcode"].ToString()] = userData["type"].ToString(); // Store the data in the temporary dictionary } if (tempUserType.Count > 1) - userType = tempUserType; // Replace userType with the temporary dictionary + UserType = tempUserType; // Replace userType with the temporary dictionary else if (!InitOnce) { Logger.Error($"Incoming RoleTable is null, cannot init!", "GetRoleTable.error"); @@ -236,8 +229,8 @@ private static string ToAutoTranslate(JsonElement tag) } public static bool IsBooster(string friendcode) { - if (!userType.ContainsKey(friendcode)) return false; - return userType[friendcode] == "s_bo"; + if (!UserType.ContainsKey(friendcode)) return false; + return UserType[friendcode] == "s_bo"; } private static IEnumerator GetEACList() @@ -249,12 +242,7 @@ private static IEnumerator GetEACList() yield break; } - string apiUrl; - var discontinuationDate = new DateTime(2024, 8, 21); - var today = DateTime.UtcNow; - if (today < discontinuationDate) apiUrl = oldApiUrl; // Replace with your actual API URL - else apiUrl = newApiUrl; - + string apiUrl = ApiUrl; string endpoint = $"{apiUrl}/eac?token={apiToken}"; UnityWebRequest webRequest = UnityWebRequest.Get(endpoint); @@ -287,15 +275,15 @@ private static IEnumerator GetEACList() private static bool CanAccessDev(string friendCode) { - if (!userType.ContainsKey(friendCode)) + if (!UserType.ContainsKey(friendCode)) { Logger.Error($"no user found, with friendcode {friendCode}", "CanAccessDev"); return false; } - if (userType[friendCode] == "s_bo" || userType[friendCode] == "s_it" || userType[friendCode].StartsWith("t_")) + if (UserType[friendCode] == "s_bo" || UserType[friendCode] == "s_it" || UserType[friendCode].StartsWith("t_")) { - Logger.Error($"Error : Dev access denied to user {friendCode}, type = {userType[friendCode]}", "CanAccessDev"); + Logger.Error($"Error : Dev access denied to user {friendCode}, type = {UserType[friendCode]}", "CanAccessDev"); return false; } return true; From 4b23a4ebeb09f2344698cc0dc3292c0358e74948 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:28:51 +0200 Subject: [PATCH 343/778] turns out it's not needed lol --- Modules/AntiBlackout.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index eae0677c41..dd35886bf5 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -258,18 +258,6 @@ public static void Reset() } public static class ReassignImpostorPatch { - /*public static void Postfix(PlayerControl __instance) - { - if (!AmongUsClient.Instance.AmHost || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor()) return; - - //idk if this is needed since anyways ghost-role desyncs aren't in here (cuz that is set later), so maybe I'll remove. - - foreach (var Killer in Main.AllPlayerControls.Where(x => x.HasKillButton() && x != __instance)) - { - Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); - } - }*/ // If this part is needed, then it would have to be rpcsetrole and I'd try to avoid that cuz it's very goofy, so hopefully it is not. - public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool skipCheck = false) { if (AmongUsClient.Instance.AmHost && skipCheck) goto fixrole; From 5b275958920593710183f5c35223f15d9082ada7 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Fri, 23 Aug 2024 23:00:34 +0800 Subject: [PATCH 344/778] Fix antiblackout false ending the game --- Modules/GameState.cs | 2 +- Modules/RPC.cs | 65 ++++++++++++++++++++++++++++++--------- Resources/Lang/en_US.json | 3 +- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index b53fe6d194..3bd002680f 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -423,7 +423,7 @@ public static class GameStates public static bool DleksIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Dleks; public static bool AirshipIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Airship; public static bool FungleIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Fungle; - public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Joined; + public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Joined || LobbyBehaviour.Instance != null; public static bool IsInGame => InGame; public static bool IsEnded => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Ended; public static bool IsNotJoined => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.NotJoined; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 368d1ee053..5e27053cbb 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -211,30 +211,65 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c switch (rpcType) { case CustomRPC.AntiBlackout: - if (Options.EndWhenPlayerBug.GetBool()) + Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance.PlayerId}): Error: {reader.ReadString()} - end the game according to the setting", "Anti-black"); + + if (GameStates.IsShip || !GameStates.IsLobby) { - Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance.PlayerId}): Error: {reader.ReadString()} - end the game according to the setting", "Anti-black"); + //CoStartGame is running, we are fucked. ChatUpdatePatch.DoBlockChat = true; Main.OverrideWelcomeMsg = string.Format(GetString("RpcAntiBlackOutNotifyInLobby"), __instance?.Data?.PlayerName, GetString("EndWhenPlayerBug")); - _ = new LateTask(() => + + if (Options.EndWhenPlayerBug.GetBool()) { - Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutEndGame"), __instance?.Data?.PlayerName)); - }, 3f, "RPC Anti-Black Msg SendInGame Error During Loading"); + _ = new LateTask(() => + { + Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutEndGame"), __instance?.Data?.PlayerName)); + }, 3f, "RPC Anti-Black Msg SendInGame Error During Loading"); - _ = new LateTask(() => + if (AmongUsClient.Instance.AmHost) + { + _ = new LateTask(() => + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + RPC.ForceEndGame(CustomWinner.Error); + }, 5.5f, "RPC Anti-Black End Game As Critical Error"); + } + } + else + { + _ = new LateTask(() => + { + Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutIgnored"), __instance?.Data?.PlayerName)); + }, 3f, "RPC Anti-Black Msg SendInGame Out Ignored"); + + if (AmongUsClient.Instance.AmHost && __instance != null) + { + _ = new LateTask(() => + { + AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); + Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutKicked"), __instance?.Data?.PlayerName)); + }, 5.5f, "RPC Anti-Black Ignored Kick Player"); + } + } + } + else if (GameStartManager.Instance != null) + { + // We imagine rpc is received when starting game in lobby, not fucked yet + if (AmongUsClient.Instance.AmHost) { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - RPC.ForceEndGame(CustomWinner.Error); - }, 5.5f, "RPC Anti-Black End Game As Critical Error"); + GameStartManager.Instance.ResetStartState(); + if (__instance != null) + { + AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); + } + } + Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutKicked"), __instance?.Data?.PlayerName)); } else { - Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance.PlayerId}): Error: {reader.ReadString()} - continue the game according to the settings", "Anti-black"); - _ = new LateTask(() => - { - Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutIgnored"), __instance?.Data?.PlayerName)); - }, 3f, "RPC Anti-Black Msg SendInGame Out Ignored"); + Logger.SendInGame("[Critical Error] Your client is in a unknow state while receiving AntiBlackOut rpcs from others."); + Logger.Fatal($"Client is in a unknow state while receiving AntiBlackOut rpcs from others.", "Anti-black"); } break; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3c7b2beda2..2e2305da0a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2563,7 +2563,8 @@ "RpcAntiBlackOutNotifyInLobby": "Because of {0}, an unknown error occurred. To prevent a black screen, turn off [{1}] in settings.", "RpcAntiBlackOutEndGame": "Because of {0}, an unknown error occurred, the game will end to prevent a black screen.", - "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, RPC will be ignored.", + "RpcAntiBlackOutIgnored": "Because of {0}, an unknown error occurred, but the game will continue without that player due to host settings.", + "RpcAntiBlackOutKicked": "{0} was kicked due to having a blackout error on its side.", "NextPage": "Next Page", "PreviousPage": "Previous Page", From e5f054320229bdd2314f0134435ce0bf3347b17f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 23 Aug 2024 23:43:35 +0800 Subject: [PATCH 345/778] Add new add-on: Evader --- Modules/OptionHolder.cs | 2 +- Patches/MeetingHudPatch.cs | 5 +++++ Resources/Lang/en_US.json | 7 ++++++- Resources/roleColor.json | 1 + Roles/AddOns/Common/Evader.cs | 26 ++++++++++++++++++++++++++ Roles/AddOns/Common/Rebirth.cs | 12 +++++------- main.cs | 1 + 7 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 Roles/AddOns/Common/Evader.cs diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index a78047b462..640a7e6631 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -621,7 +621,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29500 last id for roles/add-ons (Next use 29600) + // 29700 last id for roles/add-ons (Next use 29800) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 51211a33a2..4f79545761 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -804,6 +804,11 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta } //Set influenced vote num to zero while counting votes, and count influenced vote upon finishing influenced check + if (target.Is(CustomRoles.Evader)) + { + Evader.CheckExile(ref VoteNum); + } + //Add 1 vote If key is not defined, overwrite with 1 and define dic[ps.VotedFor] = !dic.TryGetValue(ps.VotedFor, out int num) ? VoteNum : num + VoteNum; //Count the number of times this player has been voted in } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e363db662f..da348393cf 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -392,6 +392,7 @@ "Rainbow": "Rainbow", "Tired": "Tired", "Statue": "Statue", + "Evader": "Evader", "DollMaster": "Dollmaster", "DoubleAgent": "Double Agent", "BracketAddons": "Add Brackets To Add-ons", @@ -693,6 +694,7 @@ "TrickyInfo": "Tricky slays, in mysterious ways.", "TiredInfo": "Labor makes you rest Zzz..", "StatueInfo": "You're still as a rock nearby people", + "EvaderInfo": "Get chance not be exiled!", "GMInfo": "Spectate the chaos!", "NotAssignedInfo": "No assigned role", "SunnyboyInfo": "Shine, shine my sunshine!", @@ -994,6 +996,7 @@ "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", + "EvaderInfoLong": "(Add-ons):\nYou have a definite chance of not be exiled", "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", @@ -3730,5 +3733,7 @@ "WardenWarn": "DANGER! RUN!", "MinionAbilityTime": "Ability Duration", - "Minion_Blind": "blinded" + "Minion_Blind": "blinded", + + "Evader_ChanceNotExiled": "Chance not be exiled" } diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 6c242adbc4..bbeb4b1575 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -145,6 +145,7 @@ "Medusa": "#9900CC", "Spiritcaller": "#003366", "EvilSpirit": "#003366", + "Evader": "#9beb34", "Amnesiac": "#7FBFFF", "Doomsayer": "#14f786", "PunchingBag": "#684405", diff --git a/Roles/AddOns/Common/Evader.cs b/Roles/AddOns/Common/Evader.cs new file mode 100644 index 0000000000..220f744b53 --- /dev/null +++ b/Roles/AddOns/Common/Evader.cs @@ -0,0 +1,26 @@ + +namespace TOHE.Roles.AddOns.Common; + +public class Evader : IAddon +{ + private const int Id = 29600; + public AddonTypes Type => AddonTypes.Helpful; + + private static OptionItem ChanceNotExiled; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(Id, CustomRoles.Evader, canSetNum: true, teamSpawnOptions: true); + ChanceNotExiled = IntegerOptionItem.Create(Id + 10, "Evader_ChanceNotExiled", new(0, 100, 5), 25, TabGroup.Addons, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evader]) + .SetValueFormat(OptionFormat.Percent); + } + + public static void CheckExile(ref int VoteNum) + { + if (IRandom.Instance.Next(1, 100) < ChanceNotExiled.GetInt()) + { + VoteNum = 0; + } + } +} diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 1e9d817d88..6aab4d8e0b 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -1,9 +1,7 @@ -using static TOHE.Options; +using TOHE.Modules; +using static TOHE.Options; using static TOHE.Utils; using static TOHE.Translator; -using TOHE.Modules; -using static UnityEngine.GraphicsBuffer; -using TOHE.Roles.Neutral; namespace TOHE.Roles.AddOns.Common; @@ -19,9 +17,9 @@ public class Rebirth : IAddon public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Rebirth, canSetNum: true, teamSpawnOptions: true); - RebirthUses = IntegerOptionItem.Create(Id + 11, "RebirthUses", new(1, 14, 1), 1, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rebirth]) + RebirthUses = IntegerOptionItem.Create(Id + 11, "RebirthUses", new(1, 14, 1), 1, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Rebirth]) .SetValueFormat(OptionFormat.Times); - OnlyVoted = BooleanOptionItem.Create(Id + 12, "RebirthCountVotes", false, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rebirth]); + OnlyVoted = BooleanOptionItem.Create(Id + 12, "RebirthCountVotes", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Rebirth]); } public static void Init() { @@ -63,7 +61,7 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled var ViablePlayer = list.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && -/*All converters */ (!x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer)) && +/*All converters */ !x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer) && !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); if (ViablePlayer == null) diff --git a/main.cs b/main.cs index d0d8e73a27..21f07f591a 100644 --- a/main.cs +++ b/main.cs @@ -883,6 +883,7 @@ public enum CustomRoles Diseased, DoubleShot, Egoist, + Evader, EvilSpirit, Flash, Fool, From 1ffe88e24bc6c717e9d7c3f048d935b33c3f8548 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:48:40 +0200 Subject: [PATCH 346/778] change --- Modules/AntiBlackout.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index dd35886bf5..5126e5810a 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -267,6 +267,7 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool sk Logger.Info($"I am running for {__instance.GetRealName()}/{__instance.GetCustomRole()}", "DesyncIMPFIX"); + //okay idfk how toh-y made it work, feel free to make it like that, cuz idk //Toh-y somehow works, so it would be better to get a similar fix, and this is a simple temp-fix that works __instance.ReactorFlash(); @@ -277,7 +278,7 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool sk __instance.RpcSetRoleDesync(prevtype, true, __instance.GetClientId()); /* - foreach (var Killer in Main.AllPlayerControls.Where(x => x != __instance)) + foreach (var Killer in Main.AllAlivePlayerControls.Where(x => x != __instance && x.HasKillButton())) { Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); }*/ // dosen't work, will have to change it to be similar to toh-y but not change to crewmate ghost. From fadf38487ae4b722b232f746c913a5158a1b4c04 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:52:53 +0200 Subject: [PATCH 347/778] some change --- Modules/AntiBlackout.cs | 4 ++-- Modules/ExtendedPlayerControl.cs | 14 +++++++------- Patches/PlayerControlPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 5126e5810a..9c7ffaf792 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -272,10 +272,10 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool sk //Toh-y somehow works, so it would be better to get a similar fix, and this is a simple temp-fix that works __instance.ReactorFlash(); RoleTypes prevtype = __instance.Data.Role.Role; - __instance.RpcSetRoleDesync(RoleTypes.Crewmate, true, __instance.GetClientId()); + __instance.RpcSetRoleDesync(RoleTypes.Crewmate, __instance.GetClientId()); MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.Exiled, SendOption.None, __instance.GetClientId()); AmongUsClient.Instance.FinishRpcImmediately(writer); - __instance.RpcSetRoleDesync(prevtype, true, __instance.GetClientId()); + __instance.RpcSetRoleDesync(prevtype, __instance.GetClientId()); /* foreach (var Killer in Main.AllAlivePlayerControls.Where(x => x != __instance && x.HasKillButton())) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index a3f561f1d5..9d5381b7d2 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -19,9 +19,9 @@ namespace TOHE; static class ExtendedPlayerControl { - public static void SetRole(this PlayerControl player, RoleTypes role, bool canOverride = false) + public static void SetRole(this PlayerControl player, RoleTypes role/*, bool canOverride = false*/) { - player.StartCoroutine(player.CoSetRole(role, canOverride)); + player.StartCoroutine(player.CoSetRole(role, true)); } public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role) @@ -295,10 +295,10 @@ public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleT Typatwo = roleTypes; } - seer.RpcSetRoleDesync(Typa, true, player.GetClientId()); - player.RpcSetRoleDesync(Typatwo, true, seer.GetClientId()); + seer.RpcSetRoleDesync(Typa, player.GetClientId()); + player.RpcSetRoleDesync(Typatwo, seer.GetClientId()); } - player.RpcSetRoleDesync(roleTypes, true, player.GetClientId()); + player.RpcSetRoleDesync(roleTypes, player.GetClientId()); } else { @@ -306,12 +306,12 @@ public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleT } } - public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, bool canOverride, int clientId) + public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* bool canOverride,*/ int clientId) { if (player == null) return; if (AmongUsClient.Instance.ClientId == clientId) { - player.SetRole(role, true); + player.SetRole(role); return; } MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 9c78c01a71..5d88005081 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1944,7 +1944,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol { if (seer == null || target == null) continue; Logger.Info($"Desync {targetName} => {role} for {seer.GetNameWithRole().RemoveHtmlTags()}", "PlayerControl.RpcSetRole"); - target.RpcSetRoleDesync(role, true, seer.GetClientId()); + target.RpcSetRoleDesync(role, seer.GetClientId()); } return false; } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 30e56a4c44..d70329f244 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -691,7 +691,7 @@ public static void Release() if (seer.OwnedByHost()) { - target.SetRole(roleType, true); + target.SetRole(roleType); continue; } var sender = senders[seer.PlayerId]; @@ -722,7 +722,7 @@ private static void SetSelfRoles() if (pc.OwnedByHost()) { - pc.SetRole(roleType, true); + pc.SetRole(roleType); } stream.StartMessage(2); From cf147c7034fa1f75db2253617a10b2deec625c61 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:03:09 +0200 Subject: [PATCH 348/778] fix --- Modules/AntiBlackout.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 9c7ffaf792..96906608b3 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -260,6 +260,7 @@ public static class ReassignImpostorPatch { public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool skipCheck = false) { + if (__instance.OwnedByHost()) return; if (AmongUsClient.Instance.AmHost && skipCheck) goto fixrole; if (!AmongUsClient.Instance.AmHost || __instance.IsAlive() || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; From a1226209ee91ab1c1ec6ec46c64503a883c91cd9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 00:29:49 +0800 Subject: [PATCH 349/778] Set original name after player left --- Patches/PlayerJoinAndLeftPatch.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index cb644e9959..e368681e5d 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -371,8 +371,9 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client if (Spiritualist.HasEnabled) Spiritualist.RemoveTarget(data.Character.PlayerId); - Main.PlayerStates[data.Character.PlayerId].Disconnected = true; - Main.PlayerStates[data.Character.PlayerId].SetDead(); + var state = Main.PlayerStates[data.Character.PlayerId]; + state.Disconnected = true; + state.SetDead(); // if the player left while he had a Notice message, clear it if (NameNotifyManager.Notifying(data.Character)) @@ -381,6 +382,16 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client //Utils.DoNotifyRoles(SpecifyTarget: data.Character, ForceLoop: true); } + if (AmongUsClient.Instance.AmHost) + { + try + { + data.Character.RpcSetName(state.NormalOutfit.PlayerName); + } + catch + { } + } + AntiBlackout.OnDisconnect(data.Character.Data); PlayerGameOptionsSender.RemoveSender(data.Character); } From eb8524e1589b3d2cc08232e9b4ed1c0cb7edc7c4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:00:49 +0200 Subject: [PATCH 350/778] rpcresetability to revive --- Modules/AntiBlackout.cs | 23 +++++++---------------- Modules/ExtendedPlayerControl.cs | 1 + Patches/ExilePatch.cs | 7 ++++++- Patches/IntroPatch.cs | 1 - Patches/MeetingHudPatch.cs | 1 - 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 96906608b3..e0259f9c43 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -93,10 +93,6 @@ public static void SetIsDead(bool doSend = true, [CallerMemberName] string calle } public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { - foreach (var pc in Main.AllPlayerControls) - { - pc.FixDesyncImpostorRoles(); - } logger.Info($"RestoreIsDead is called from {callerMethodName}"); foreach (var info in GameData.Instance.AllPlayers) @@ -258,8 +254,11 @@ public static void Reset() } public static class ReassignImpostorPatch { + public readonly static Dictionary DesyncAlive = []; public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool skipCheck = false) { + //everytime I fix it, it decidec to break again, how fun.-. + if (__instance.OwnedByHost()) return; if (AmongUsClient.Instance.AmHost && skipCheck) goto fixrole; if (!AmongUsClient.Instance.AmHost || __instance.IsAlive() || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() @@ -268,20 +267,12 @@ public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool sk Logger.Info($"I am running for {__instance.GetRealName()}/{__instance.GetCustomRole()}", "DesyncIMPFIX"); - //okay idfk how toh-y made it work, feel free to make it like that, cuz idk - - //Toh-y somehow works, so it would be better to get a similar fix, and this is a simple temp-fix that works - __instance.ReactorFlash(); - RoleTypes prevtype = __instance.Data.Role.Role; - __instance.RpcSetRoleDesync(RoleTypes.Crewmate, __instance.GetClientId()); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.Exiled, SendOption.None, __instance.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - __instance.RpcSetRoleDesync(prevtype, __instance.GetClientId()); + DesyncAlive[__instance.PlayerId] = __instance.GetCustomRole().GetVNRole().GetRoleTypes(); + __instance.RpcSetRoleDesync(RoleTypes.Impostor, __instance.GetClientId()); - /* foreach (var Killer in Main.AllAlivePlayerControls.Where(x => x != __instance && x.HasKillButton())) { - Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), true, __instance.GetClientId()); - }*/ // dosen't work, will have to change it to be similar to toh-y but not change to crewmate ghost. + Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), __instance.GetClientId()); + } // this is supposed to be a fix for them to see phantom players now, but I think it dosen't work lmfao } } \ No newline at end of file diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 9d5381b7d2..b0617a3a68 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -262,6 +262,7 @@ public static void RpcRevive(this PlayerControl player, RoleTypes roleTypes, boo player.RpcChangeRoleBasis(roleTypes, IsDesyncImpostor, FellowImps); Main.PlayerStates[player.PlayerId].IsDead = false; + player.SetKillCooldown(); player.SyncGeneralOptions(); } diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 5af68cc250..9c03b3cddf 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -119,9 +119,14 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) player.ResetKillCooldown(); player.RpcResetAbilityCooldown(); + if (ReassignImpostorPatch.DesyncAlive.TryGetValue(player.PlayerId, out var Typa)) + { + player.RpcSetRoleDesync(Typa, player.GetClientId()); + } //player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles } - exiled?.Object?.FixDesyncImpostorRoles(true); + ReassignImpostorPatch.DesyncAlive.Clear(); + //exiled?.Object?.FixDesyncImpostorRoles(true); Main.MeetingIsStarted = false; Main.MeetingsPassed++; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 35bcf41915..9fddcfbd96 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -615,7 +615,6 @@ public static void Postfix() { DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); } - }, 0.1f, "Assign Impostor desync roles for crewmates"); foreach (var pc in Main.AllPlayerControls) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 89325f3a3a..51211a33a2 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -366,7 +366,6 @@ public static bool Prefix(MeetingHud __instance) ExileControllerWrapUpPatch.AntiBlackout_LastExiled = exiledPlayer; Main.LastVotedPlayerInfo = exiledPlayer; - //RPC if (AntiBlackout.BlackOutIsActive) { From 8dda60d532a0aa0fffc12e1242d79d9779c74b28 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:02:02 +0200 Subject: [PATCH 351/778] remove --- Patches/ExilePatch.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 9c03b3cddf..650d0f2633 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -118,15 +118,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) // Reset Kill/Ability cooldown player.ResetKillCooldown(); player.RpcResetAbilityCooldown(); - - if (ReassignImpostorPatch.DesyncAlive.TryGetValue(player.PlayerId, out var Typa)) - { - player.RpcSetRoleDesync(Typa, player.GetClientId()); - } - //player.FixDesyncImpostorRoles(); // Fix Impostor For Desync roles } - ReassignImpostorPatch.DesyncAlive.Clear(); - //exiled?.Object?.FixDesyncImpostorRoles(true); Main.MeetingIsStarted = false; Main.MeetingsPassed++; From 8ddd9dbbe9f8b8b7763d89b2599b21ca6440e1b3 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 01:18:18 +0800 Subject: [PATCH 352/778] Not use vanilla Data.Role.IsImpostor --- Modules/Utils.cs | 5 ++--- Patches/PlayerControlPatch.cs | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 3fe71e110b..d473a6f8d2 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -549,8 +549,8 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = } if (playerData.Disconnected) return false; - if (playerData.Role.IsImpostor) - hasTasks = false; //Tasks are determined based on CustomRole + //if (playerData.Role.IsImpostor) + // hasTasks = false; //Tasks are determined based on CustomRole if (Options.CurrentGameMode == CustomGameMode.FFA) return false; if (playerData.IsDead && Options.GhostIgnoreTasks.GetBool()) hasTasks = false; @@ -586,7 +586,6 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = case CustomRoles.Contagious: case CustomRoles.Soulless: case CustomRoles.Rascal: - //Lovers don't count the task as a win hasTasks &= !ForRecompute; break; case CustomRoles.Mundane: diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index c12338d72d..810a0098c1 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1781,8 +1781,7 @@ public static void Postfix(PlayerControl __instance) } // if player is Desync Impostor and the vanilla sees player as Imposter, the vanilla process does not hide your name, so the other person's name is hidden - if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && // Impostor with vanilla - !PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) && // Not an Impostor + if (!PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) && // Not an Impostor PlayerControl.LocalPlayer.HasDesyncRole()) // Desync Impostor { // Hide names From 22ebce7f60bb2c08b46b2e91b53bab6f4d62c5a8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 01:30:47 +0800 Subject: [PATCH 353/778] Fix --- Modules/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index d473a6f8d2..7e6ef4cc5c 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -569,7 +569,7 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = break; default: // player based on an impostor not should have tasks - if (States.RoleClass.ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter) + if (States.RoleClass.ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom) hasTasks = false; break; } From 5ba3a9ac4a48e814c046c4cd55a6c2396cd89ed4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 01:40:15 +0800 Subject: [PATCH 354/778] Taskinator and Schrodingers Cat --- Roles/Neutral/SchrodingersCat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/SchrodingersCat.cs b/Roles/Neutral/SchrodingersCat.cs index f71153da2f..e70e378886 100644 --- a/Roles/Neutral/SchrodingersCat.cs +++ b/Roles/Neutral/SchrodingersCat.cs @@ -49,7 +49,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { - if (killer == null || target == null) return true; + if (killer.Is(CustomRoles.Taskinator)) return true; if (teammate[target.PlayerId] != byte.MaxValue) return true; teammate[target.PlayerId] = killer.PlayerId; From ae587b4a77ad60b1a036ab826db007dc032fb317 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 01:42:46 +0800 Subject: [PATCH 355/778] Set Kill Cooldown for Berserker --- Roles/Neutral/Berserker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index fd5db62e5a..80b951a7c3 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -118,6 +118,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (BerserkerKillMax[killer.PlayerId] >= BerserkerKillCooldownLevel.GetInt() && BerserkerOneCanKillCooldown.GetBool()) { Main.AllPlayerKillCooldown[killer.PlayerId] = BerserkerOneKillCooldown.GetFloat(); + killer.SetKillCooldown(); } if (BerserkerKillMax[killer.PlayerId] >= BerserkerScavengerLevel.GetInt() && BerserkerTwoCanScavenger.GetBool()) From ae8f39cbc17928905a3982a78125ccf6d20e555b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 01:45:08 +0800 Subject: [PATCH 356/778] Fix bug --- Roles/Neutral/Berserker.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 80b951a7c3..8d56b7771c 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -79,6 +79,7 @@ public override void Init() } public override void Add(byte playerId) { + Main.AllPlayerKillCooldown[playerId] = BerserkerKillCooldown.GetFloat(); BerserkerKillMax[playerId] = 0; PlayerIds.Add(playerId); } @@ -92,8 +93,6 @@ public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Berserker).ShadeColor(0.25f), BerserkerKillMax.TryGetValue(playerId, out var x) ? $"({x}/{BerserkerMax.GetInt()})" : "Invalid"); - public override void SetKillCooldown(byte id) - => Main.AllPlayerKillCooldown[id] = BerserkerKillCooldown.GetFloat(); public override bool CanUseImpostorVentButton(PlayerControl pc) => BerserkerCanVent.GetBool(); public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(BerserkerHasImpostorVision.GetBool()); From ae52776ed483edc12565489142b7b33db49b6d93 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:46:07 +0200 Subject: [PATCH 357/778] FINALLYUAIY UAS KUSHKJ ALJHKSALKJHSA --- Modules/AntiBlackout.cs | 51 ++++++++++++++++++++------------------ Patches/ExilePatch.cs | 5 ++-- Patches/MeetingHudPatch.cs | 2 +- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index e0259f9c43..575fe396b0 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -14,6 +14,7 @@ public static class AntiBlackout /// Check num alive Impostors & Crewmates & NeutralKillers /// public static bool BlackOutIsActive => false; /*!Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut();*/ + public static int ExilePlayerId = -1; /// /// Count alive players and check black out @@ -74,6 +75,7 @@ public static bool CheckBlackOut() public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { + SetRole(); logger.Info($"SetIsDead is called from {callerMethodName}"); if (IsCached) { @@ -239,6 +241,31 @@ public static void AfterMeetingTasks() Logger.Error($"{error}", "AntiBlackout.AfterMeetingTasks"); } } + public static void SetRole() + { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + + PlayerControl OurImp = Main.AllAlivePlayerControls.First(pc => pc.PlayerId != ExilePlayerId && pc.HasKillButton()); + + foreach (var pc in Main.AllPlayerControls.Where(x => !x.Data.Disconnected)) + { + if (pc.PlayerId == PlayerControl.LocalPlayer.PlayerId) continue; + if (pc.IsAlive() && pc.GetCustomRole().IsDesyncRole()) continue; + + + OurImp.RpcSetRoleDesync(OurImp.GetCustomRole().GetVNRole().GetRoleTypes(), pc.GetClientId()); + + foreach (var dead in Main.AllPlayerControls.Where(x => !x.Data.Disconnected && x.Data.IsDead)) + { + RoleTypes typa = RoleTypes.CrewmateGhost; + if (dead.GetCustomRole().IsGhostRole()) typa = RoleTypes.GuardianAngel; + + dead.RpcSetRoleDesync(typa, pc.GetClientId()); + } + Logger.Info($"SetDummyImpostor player: {pc?.name}", "AntiBlackout"); + } + ExilePlayerId = -1; + } public static void Reset() { logger.Info("==Reset=="); @@ -251,28 +278,4 @@ public static void Reset() public static bool ShowExiledInfo = false; public static string StoreExiledMessage = ""; -} -public static class ReassignImpostorPatch -{ - public readonly static Dictionary DesyncAlive = []; - public static void FixDesyncImpostorRoles(this PlayerControl __instance, bool skipCheck = false) - { - //everytime I fix it, it decidec to break again, how fun.-. - - if (__instance.OwnedByHost()) return; - if (AmongUsClient.Instance.AmHost && skipCheck) goto fixrole; - if (!AmongUsClient.Instance.AmHost || __instance.IsAlive() || !__instance.GetCustomRole().IsDesyncRole() && !__instance.GetCustomRole().IsImpostor() - && (!GhostRoleAssign.GhostGetPreviousRole.TryGetValue(__instance.PlayerId, out var role) || !role.IsDesyncRole() || !role.IsImpostor())) return; - fixrole: - - Logger.Info($"I am running for {__instance.GetRealName()}/{__instance.GetCustomRole()}", "DesyncIMPFIX"); - - DesyncAlive[__instance.PlayerId] = __instance.GetCustomRole().GetVNRole().GetRoleTypes(); - __instance.RpcSetRoleDesync(RoleTypes.Impostor, __instance.GetClientId()); - - foreach (var Killer in Main.AllAlivePlayerControls.Where(x => x != __instance && x.HasKillButton())) - { - Killer.RpcSetRoleDesync(Killer.GetCustomRole().GetVNRole().GetRoleTypes(), __instance.GetClientId()); - } // this is supposed to be a fix for them to see phantom players now, but I think it dosen't work lmfao - } } \ No newline at end of file diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 650d0f2633..26cf275ad2 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -74,9 +74,8 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) { // Reset player cam for exiled desync impostor if (exiled.Object.HasDesyncRole()) - { - //exiled.Object?.ResetPlayerCam(1f); - // exiled.Object.FixDesyncImpostorRolesBYPASS(); + { + exiled.Object?.ResetPlayerCam(1f); } exiled.IsDead = true; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 51211a33a2..77c571f91c 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -365,7 +365,7 @@ public static bool Prefix(MeetingHud __instance) ExileControllerWrapUpPatch.AntiBlackout_LastExiled = exiledPlayer; Main.LastVotedPlayerInfo = exiledPlayer; - + if (exiledPlayer != null) AntiBlackout.ExilePlayerId = exiledPlayer.PlayerId; //RPC if (AntiBlackout.BlackOutIsActive) { From 3d65446d853ff57cc2f87b5428ff7f28709e295a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:48:23 +0200 Subject: [PATCH 358/778] remove --- Modules/AntiBlackout.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 575fe396b0..1400d2a407 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -262,7 +262,6 @@ public static void SetRole() dead.RpcSetRoleDesync(typa, pc.GetClientId()); } - Logger.Info($"SetDummyImpostor player: {pc?.name}", "AntiBlackout"); } ExilePlayerId = -1; } From fe25057222c715f718093e14873279fed83c1d28 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:58:17 +0200 Subject: [PATCH 359/778] fix --- Modules/AntiBlackout.cs | 14 +++++++++----- Modules/Utils.cs | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 1400d2a407..5775eaebaf 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -245,16 +245,20 @@ public static void SetRole() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - PlayerControl OurImp = Main.AllAlivePlayerControls.First(pc => pc.PlayerId != ExilePlayerId && pc.HasKillButton()); + List list = Main.AllAlivePlayerControls.Where(x => x.HasKillButton()).ToList(); foreach (var pc in Main.AllPlayerControls.Where(x => !x.Data.Disconnected)) { if (pc.PlayerId == PlayerControl.LocalPlayer.PlayerId) continue; - if (pc.IsAlive() && pc.GetCustomRole().IsDesyncRole()) continue; + if (pc.IsAlive() && (pc.GetCustomRole().IsDesyncRole())) continue; + + + foreach (var dummy in list) + { + if (pc.GetCustomRole().IsImpostor() && !pc.IsSameTeammate(dummy, out _)) continue; + dummy.RpcSetRoleDesync(dummy.GetCustomRole().GetVNRole().GetRoleTypes(), pc.GetClientId()); + } - - OurImp.RpcSetRoleDesync(OurImp.GetCustomRole().GetVNRole().GetRoleTypes(), pc.GetClientId()); - foreach (var dead in Main.AllPlayerControls.Where(x => !x.Data.Disconnected && x.Data.IsDead)) { RoleTypes typa = RoleTypes.CrewmateGhost; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index c4e9eeef36..2ba423ba65 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1689,7 +1689,25 @@ public static List GetPlayerListByIds(this IEnumerable Play } public static List GetPlayerListByRole(this CustomRoles role) => GetPlayerListByIds(Main.PlayerStates.Values.Where(x => x.MainRole == role).Select(r => r.PlayerId)); - + public static bool IsSameTeammate(this PlayerControl player, PlayerControl target, out Custom_Team team) + { + team = default; + if (player.IsAnySubRole(x => x.IsConverted())) + { + var Compare = player.GetCustomSubRoles().First(x => x.IsConverted()); + + team = player.Is(CustomRoles.Madmate) ? Custom_Team.Impostor : Custom_Team.Neutral; + return target.Is(Compare); + } + else if (!target.IsAnySubRole(x => x.IsConverted())) + { + team = player.GetCustomRole().GetCustomRoleTeam(); + return target.Is(team); + } + + + return false; + } public static IEnumerable GetRoleBasesByType () where t : RoleBase { try From f49980d7bbb36e84ac9ced935ce24611feafc9dc Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:58:51 +0200 Subject: [PATCH 360/778] bruh --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 5775eaebaf..ae770d1e34 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -255,7 +255,7 @@ public static void SetRole() foreach (var dummy in list) { - if (pc.GetCustomRole().IsImpostor() && !pc.IsSameTeammate(dummy, out _)) continue; + if (pc.GetCustomRole().IsImpostor() && !pc.IsSameTeammate(dummy, out _) && pc.IsAlive()) continue; dummy.RpcSetRoleDesync(dummy.GetCustomRole().GetVNRole().GetRoleTypes(), pc.GetClientId()); } From 2009f9ab0590f27e17faf3fb2146f382b76de17e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:06:01 +0200 Subject: [PATCH 361/778] fix again --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index ae770d1e34..87edb40beb 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -245,7 +245,7 @@ public static void SetRole() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - List list = Main.AllAlivePlayerControls.Where(x => x.HasKillButton()).ToList(); + List list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != ExilePlayerId && x.HasKillButton()).ToList(); foreach (var pc in Main.AllPlayerControls.Where(x => !x.Data.Disconnected)) { From 528cd7aeb8971d671dd11c23058f836a863cb8ca Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:07:18 +0200 Subject: [PATCH 362/778] ghost roles need to be desynced now. --- Modules/AntiBlackout.cs | 5 +- Patches/ExilePatch.cs | 9 +++ Patches/PlayerControlPatch.cs | 67 ++------------------- Patches/onGameStartedPatch.cs | 2 - Resources/Lang/en_US.json | 2 +- Roles/Core/AssignManager/GhostRoleAssign.cs | 60 +++++++++++++++++- 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 87edb40beb..79b1b19857 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -261,10 +261,7 @@ public static void SetRole() foreach (var dead in Main.AllPlayerControls.Where(x => !x.Data.Disconnected && x.Data.IsDead)) { - RoleTypes typa = RoleTypes.CrewmateGhost; - if (dead.GetCustomRole().IsGhostRole()) typa = RoleTypes.GuardianAngel; - - dead.RpcSetRoleDesync(typa, pc.GetClientId()); + dead.RpcSetRoleDesync(RoleTypes.CrewmateGhost, pc.GetClientId()); } } ExilePlayerId = -1; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 26cf275ad2..81d8875e23 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -1,4 +1,5 @@ using AmongUs.Data; +using AmongUs.GameOptions; using System; using TOHE.Roles.Core; using TOHE.Roles.Neutral; @@ -190,6 +191,14 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) }); Main.AfterMeetingDeathPlayers.Clear(); + + //WELP, all GuardianAngel players have to be desynced + reset cams now, but hey at least everyone else desync is fine now. + foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) + { + pc.ResetPlayerCam(); + pc.RpcSetRoleDesync(RoleTypes.GuardianAngel, pc.GetClientId()); + } + }, 0.8f, "AfterMeetingDeathPlayers Task"); } //This should happen shortly after the Exile Controller wrap up finished for clients diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 5d88005081..d754254359 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1870,7 +1870,6 @@ public static void Postfix(PlayerControl __instance) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] class PlayerControlSetRolePatch { - public static readonly Dictionary DidSetGhost = []; private static readonly Dictionary ghostRoles = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) { @@ -1884,8 +1883,6 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol // Ghost assign if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) { - if (DidSetGhost.TryGetValue(target.PlayerId, out var isGhost) && isGhost) // Prevent double assignment if player gets killed as a ghost - return false; try { @@ -1924,7 +1921,9 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol if (ghostRoles.All(kvp => kvp.Value == RoleTypes.GuardianAngel)) { roleType = RoleTypes.GuardianAngel; - return true; + __instance.RpcSetRoleDesync(RoleTypes.GuardianAngel, __instance.GetClientId()); + GhostRoleAssign.CreateGAMessage(__instance); + return false; } // If all players see player as Crewmate Ghost else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.CrewmateGhost)) @@ -1958,69 +1957,11 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref Ro try { - if (roleType == RoleTypes.GuardianAngel && !DidSetGhost.ContainsKey(__instance.PlayerId)) - { - Utils.NotifyRoles(SpecifyTarget: __instance); - _ = new LateTask(() => { - - __instance.RpcResetAbilityCooldown(); - - if (Options.SendRoleDescriptionFirstMeeting.GetBool()) - { - var host = PlayerControl.LocalPlayer; - var name = host.Data.PlayerName; - var lp = __instance; - var sb = new StringBuilder(); - var conf = new StringBuilder(); - var role = __instance.GetCustomRole(); - var rlHex = Utils.GetRoleColorCode(role); - sb.Append(Utils.GetRoleTitle(role) + lp.GetRoleInfo(true)); - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref conf); - var cleared = conf.ToString(); - conf.Clear().Append($"" + $"{GetString(role.ToString())} {GetString("Settings:")}\n" + cleared + ""); - - var writer = CustomRpcSender.Create("SendGhostRoleInfo", SendOption.None); - writer.StartMessage(__instance.GetClientId()); - writer.StartRpc(host.NetId, (byte)RpcCalls.SetName) - .Write(host.Data.NetId) - .Write(Utils.ColorString(Utils.GetRoleColor(role), GetString("GhostTransformTitle"))) - .EndRpc(); - writer.StartRpc(host.NetId, (byte)RpcCalls.SendChat) - .Write(sb.ToString()) - .EndRpc(); - writer.EndMessage(); - writer.SendMessage(); - - var writer2 = CustomRpcSender.Create("SendGhostRoleConfig", SendOption.None); - writer2.StartMessage(__instance.GetClientId()); - writer2.StartRpc(host.NetId, (byte)RpcCalls.SendChat) - .Write(conf.ToString()) - .EndRpc(); - writer2.StartRpc(host.NetId, (byte)RpcCalls.SetName) - .Write(host.Data.NetId) - .Write(name) - .EndRpc(); - writer2.EndMessage(); - writer2.SendMessage(); - - // Utils.SendMessage(sb.ToString(), __instance.PlayerId, Utils.ColorString(Utils.GetRoleColor(role), GetString("GhostTransformTitle"))); - - } - - }, 0.1f, $"SetGuardianAngel for playerId: {__instance.PlayerId}"); - } + if (__runOriginal) { Logger.Info($" {__instance.GetRealName()} => {roleType}", "PlayerControl.RpcSetRole"); - - if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost or RoleTypes.GuardianAngel) - { - if (!DidSetGhost.ContainsKey(__instance.PlayerId)) - DidSetGhost.Add(__instance.PlayerId, true); - - } } _ = new LateTask(() => { Main.AllPlayerControls.Do(x => Logger.Info($"{x.GetRealName()}/{x.Data.Role.GetType().Name}/{x.Data.Role.Role}", "Test All Roletypes, and Roleclass")); }, 3f); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index d70329f244..429b472ad4 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -67,8 +67,6 @@ public static void Postfix(AmongUsClient __instance) Main.AfterMeetingDeathPlayers.Clear(); Main.clientIdList.Clear(); - PlayerControlSetRolePatch.DidSetGhost.Clear(); - Main.CheckShapeshift.Clear(); Main.ShapeshiftTarget.Clear(); Main.AllKillers.Clear(); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3713306f94..88c3d054ef 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2274,7 +2274,7 @@ "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. If the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", "Message.MaxPlayers": "Maximum players set to ", - "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", + "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win\n\nNB: You will see a death-animation of yourself after every meeting to fix blackscreen", "ApocalypseInfoTitle": "Neutral Apocalypse Info:", "Message.ApocalypseInfo": "Every role of the <#ff174f>Apocalypse Team has their own objective to carry out in order to transform.\n<#2B0804>Transformed <#ff174f>Apocalypse members have a drastic change on the game and are immortal (except for being voted), but everyone will be notified that they have transformed.\n\nRoles: <#e5f6b4>Plaguebearer, <#A675A1>Soul Collector, <#bf9f7a>Baker, <#cc0044>Berserker\nTransformed: <#343136>Pestilence, <#644661>Death, <#83461c>Famine, <#2B0804>War\n\nApocalypse members can see eachother's roles and ability icons.\nLike Neutral Killers, Apocalypse members keep the game going as well, have fun!", "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 7ac06bbf49..da9c18ea7e 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -1,4 +1,8 @@ -namespace TOHE.Roles.Core.AssignManager; +using AmongUs.GameOptions; +using Hazel; +using System.Text; + +namespace TOHE.Roles.Core.AssignManager; public static class GhostRoleAssign { @@ -139,4 +143,58 @@ public static void Add() Logger.Info($"Logged: {role.Key} / {role.Value}", "GhostAssignPatch.Add.GetCount"); } } + public static void CreateGAMessage(PlayerControl __instance) + { + Utils.NotifyRoles(SpecifyTarget: __instance); + _ = new LateTask(() => { + + __instance.RpcResetAbilityCooldown(); + + if (Options.SendRoleDescriptionFirstMeeting.GetBool()) + { + var host = PlayerControl.LocalPlayer; + var name = host.Data.PlayerName; + var lp = __instance; + var sb = new StringBuilder(); + var conf = new StringBuilder(); + var role = __instance.GetCustomRole(); + var rlHex = Utils.GetRoleColorCode(role); + sb.Append(Utils.GetRoleTitle(role) + lp.GetRoleInfo(true)); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref conf); + var cleared = conf.ToString(); + conf.Clear().Append($"" + $"{Translator.GetString(role.ToString())} {Translator.GetString("Settings:")}\n" + cleared + ""); + + var writer = CustomRpcSender.Create("SendGhostRoleInfo", SendOption.None); + writer.StartMessage(__instance.GetClientId()); + writer.StartRpc(host.NetId, (byte)RpcCalls.SetName) + .Write(host.Data.NetId) + .Write(Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString("GhostTransformTitle"))) + .EndRpc(); + writer.StartRpc(host.NetId, (byte)RpcCalls.SendChat) + .Write(sb.ToString()) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + + var writer2 = CustomRpcSender.Create("SendGhostRoleConfig", SendOption.None); + writer2.StartMessage(__instance.GetClientId()); + writer2.StartRpc(host.NetId, (byte)RpcCalls.SendChat) + .Write(conf.ToString()) + .EndRpc(); + writer2.StartRpc(host.NetId, (byte)RpcCalls.SetName) + .Write(host.Data.NetId) + .Write(name) + .EndRpc(); + writer2.EndMessage(); + writer2.SendMessage(); + + // Utils.SendMessage(sb.ToString(), __instance.PlayerId, Utils.ColorString(Utils.GetRoleColor(role), GetString("GhostTransformTitle"))); + + } + + }, 0.1f, $"SetGuardianAngel for playerId: {__instance.PlayerId}"); + } + + } From c4378c59f0db4034be9cfd74caa762fd50edc85e Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:14:12 +0200 Subject: [PATCH 363/778] oh my gosh --- Modules/RPC.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 611494c1d4..97daad37e9 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -530,7 +530,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c var state = Main.PlayerStates[paciefID]; state.MainRole = rola; state.IsDead = isdead; - state.Disconnected = isdead; + state.Disconnected = IsDC; state.deathReason = drip; } } From 28fd2e44a09d7b57414083ea5ee0378e064e13bb Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:16:37 +0200 Subject: [PATCH 364/778] remove --- Patches/PlayerControlPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index d754254359..4e5ce53b7b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1963,7 +1963,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref Ro { Logger.Info($" {__instance.GetRealName()} => {roleType}", "PlayerControl.RpcSetRole"); } - _ = new LateTask(() => { Main.AllPlayerControls.Do(x => Logger.Info($"{x.GetRealName()}/{x.Data.Role.GetType().Name}/{x.Data.Role.Role}", "Test All Roletypes, and Roleclass")); }, 3f); } catch (Exception e) { From 0fbbba32525f9e59ea1398848c1cedbafebd6911 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:18:54 +0200 Subject: [PATCH 365/778] unused bullshit --- Modules/CustomRpcSender.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 35ac5960c9..840b95c9a8 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -230,21 +230,3 @@ public enum State Finished, //送信後 何もできない } } - -public static class CustomRpcSenderExtensions -{ - public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) - { - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) - .Write((ushort)role) - .Write(true) - .EndRpc(); - } - public static void RpcMurderPlayerV3(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) - { - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.MurderPlayer, targetClientId) - .WriteNetObject(target) - .Write((int)ExtendedPlayerControl.ResultFlags) - .EndRpc(); - } -} \ No newline at end of file From 883658bf475efb3ec54c9ebcf67f43f147b5b6e1 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:23:56 +0200 Subject: [PATCH 366/778] change --- Patches/onGameStartedPatch.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 429b472ad4..2b84d38ff6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -385,33 +385,33 @@ private static void SetRolesAfterSelect() RoleAssign.RoleResult[blotnik].GetStaticRoleClass().SetDesyncImpostorBuddies(ref DesyncImpTeammates, blotnik); } - foreach (var (pc, role) in RoleAssign.RoleResult) + foreach (var (target, role) in RoleAssign.RoleResult) { - if (pc == null) continue; + if (target == null) continue; foreach (var seer in Main.AllPlayerControls) { CustomRoles ResultRole = RoleAssign.RoleResult[seer]; - bool isSelf = seer == pc; + bool isSelf = seer == target; RoleTypes typa = role.GetRoleTypes(); if (role is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) typa = RoleTypes.Noisemaker; //Desynced Imps see others as scientist if they are not a teammate else if (!isSelf && ResultRole.IsDesyncRole() && !ResultRole.IsCrewmate() && - (!DesyncImpTeammates.TryGetValue(seer, out var teammates) || !teammates.Contains(pc))) typa = RoleTypes.Scientist; + (!DesyncImpTeammates.TryGetValue(seer, out var teammates) || !teammates.Contains(target))) typa = RoleTypes.Scientist; //Other see the desynced-imp target as scientist if they are not a teammate or not an crew else if (!isSelf && role.IsDesyncRole() && !role.IsCrewmate() && !CheckSeerPassive(ResultRole) && - (!DesyncImpTeammates.TryGetValue(pc, out var bracy) || !bracy.Contains(seer))) typa = RoleTypes.Scientist; + (!DesyncImpTeammates.TryGetValue(target, out var bracy) || !bracy.Contains(seer))) typa = RoleTypes.Scientist; //Crewmates are assigned later else if (role.IsCrewmate() && role.IsDesyncRole()) typa = RoleTypes.Crewmate; - Logger.Warn($"Set Role for Target: {pc.GetRealName(clientData: true)}|{role} Seer: {seer.GetRealName(clientData: true)}|{ResultRole} of RoleType: {typa}", "SetStoragedPlayerData"); - RpcSetRoleReplacer.StoragedPlayerRoleData[(pc, seer)] = typa; + Logger.Warn($"Set Role for Target: {target.GetRealName(clientData: true)}|{role} Seer: {seer.GetRealName(clientData: true)}|{ResultRole} of RoleType: {typa}", "SetStoragedPlayerData"); + RpcSetRoleReplacer.StoragedPlayerRoleData[(target, seer)] = typa; } From 938bb3e1cde47aef04344b6d1c64dd64275f0778 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:32:50 +0200 Subject: [PATCH 367/778] lol --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 79b1b19857..c52e588651 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -256,7 +256,7 @@ public static void SetRole() foreach (var dummy in list) { if (pc.GetCustomRole().IsImpostor() && !pc.IsSameTeammate(dummy, out _) && pc.IsAlive()) continue; - dummy.RpcSetRoleDesync(dummy.GetCustomRole().GetVNRole().GetRoleTypes(), pc.GetClientId()); + dummy.RpcSetRoleDesync(dummy.GetCustomRole().GetRoleTypes(), pc.GetClientId()); } foreach (var dead in Main.AllPlayerControls.Where(x => !x.Data.Disconnected && x.Data.IsDead)) From cac76c5c326949bc8f6324aaa2d9fca0e50f10c8 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:51:45 +0200 Subject: [PATCH 368/778] fix host --- Patches/onGameStartedPatch.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 2b84d38ff6..969c8e9217 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -698,6 +698,10 @@ public static void Release() .Write((ushort)roleType) .Write(true) .EndRpc(); + + //Fix host + if (RoleResult[target].IsImpostor() && RoleResult[seer].IsImpostor() && seer.OwnedByHost()) + DestroyableSingleton.Instance.CanBeKilled = false; } SetSelfRoles(); From b8355cfec45c7a690dfc8eb9d277df42910d1a41 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:00:25 +0200 Subject: [PATCH 369/778] lol --- Roles/Crewmate/Sheriff.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index ffeba09e97..81a7b4fa83 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -11,7 +11,7 @@ internal class Sheriff : RoleBase private const int Id = 11200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sheriff); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; // Lol don't mind this, I was testing something + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateKilling; //==================================================================\\ From 0be923d08cbd4494923bf2de876010b4d52e63ef Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:11:52 +0200 Subject: [PATCH 370/778] add summary --- Modules/ExtendedPlayerControl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index b0617a3a68..780cf5c2fb 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -251,7 +251,12 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, writer.WritePacked(ventId); AmongUsClient.Instance.FinishRpcImmediately(writer); } - + /// + /// Revives the player if the given roletype is alive and player is dead. + /// + /// The type to change into + /// If the player should be desynced from impostor teammates + /// Will only function if is active and makes it so you can't kill them, neither can they kill you public static void RpcRevive(this PlayerControl player, RoleTypes roleTypes, bool IsDesyncImpostor = false, List FellowImps = null) { if (player.Data.IsDead == false || roleTypes is RoleTypes.GuardianAngel or RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) From 6e8ddd69570eb5ff8c4b6749c867bd959c7393e4 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:18:40 +0200 Subject: [PATCH 371/778] =?UTF-8?q?not=20needed=20anymore,=20and=20too=20l?= =?UTF-8?q?azy=20to=20make=20it=20an=20actual=20function,=20too=20many=20s?= =?UTF-8?q?tuffs=20to=20check=20=F0=9F=98=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Patches/ChatCommandPatch.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index d64586c4ef..28e89c7274 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -188,13 +188,6 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); break; - case "/revive": - if (args.Length != 2 || !int.TryParse(args[1], out int identity)) break; - - Utils.GetPlayerById(identity).RpcRevive(RoleTypes.Crewmate); - - - break; case "/rn": case "/rename": From aecf79019becbac4ede0adba211312da25ca6bc5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 13:50:07 +0800 Subject: [PATCH 372/778] Change --- Patches/PlayerControlPatch.cs | 4 ++-- main.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 810a0098c1..663737540c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -167,14 +167,14 @@ public static bool CheckForInvalidMurdering(PlayerControl killer, PlayerControl return false; } - var divice = Options.CurrentGameMode == CustomGameMode.FFA ? 3000f : 2000f; + var divice = Options.CurrentGameMode == CustomGameMode.FFA ? 3000f : 1500f; float minTime = Mathf.Max(0.02f, AmongUsClient.Instance.Ping / divice * 6f); //Ping value is milliseconds (ms), so ÷ 2000 // No value is stored in TimeSinceLastKill || Stored time is greater than or equal to minTime => Allow kill //↓ If not permitted if (TimeSinceLastKill.TryGetValue(killer.PlayerId, out var time) && time < minTime) { - Logger.Info($"Last kill was too shortly before, canceled - time: {time}, minTime: {minTime}", "CheckMurder"); + Logger.Info($"Last kill was too shortly before, canceled - Ping: {AmongUsClient.Instance.Ping}, Time: {time}, MinTime: {minTime}", "CheckMurder"); return false; } TimeSinceLastKill[killer.PlayerId] = 0f; diff --git a/main.cs b/main.cs index d0d8e73a27..c517a838d8 100644 --- a/main.cs +++ b/main.cs @@ -512,6 +512,8 @@ public override void Load() TOHE.Logger.Disable("SwitchSystem"); TOHE.Logger.Disable("ModNews"); TOHE.Logger.Disable("CustomRpcSender"); + TOHE.Logger.Disable("RpcSetNamePrivate"); + TOHE.Logger.Disable("KnowRoleTarget"); if (!DebugModeManager.AmDebugger) { TOHE.Logger.Disable("2018k"); @@ -522,7 +524,6 @@ public override void Load() TOHE.Logger.Disable("Info.Role"); TOHE.Logger.Disable("TaskState.Init"); //TOHE.Logger.Disable("Vote"); - TOHE.Logger.Disable("RpcSetNamePrivate"); //TOHE.Logger.Disable("SendChat"); TOHE.Logger.Disable("SetName"); //TOHE.Logger.Disable("AssignRoles"); From 337f46f126b6d2ffc26161abca6a61be50974fbf Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 14:20:47 +0800 Subject: [PATCH 373/778] Add SkillLimitTimes in GeneralOption --- Resources/Lang/en_US.json | 11 +---------- Roles/Core/RoleBase.cs | 1 + Roles/Crewmate/Deceiver.cs | 4 ++-- Roles/Crewmate/FortuneTeller.cs | 2 +- Roles/Crewmate/Medium.cs | 2 +- Roles/Crewmate/Pacifist.cs | 4 ++-- Roles/Crewmate/President.cs | 2 +- Roles/Crewmate/Witness.cs | 2 +- Roles/Neutral/Pursuer.cs | 4 ++-- 9 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index da348393cf..f81bed2728 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1026,7 +1026,6 @@ "SMUsesUsedWhenFixingReactorOrO2": "Uses it takes to fix Reactor/O2", "SMUsesUsedWhenFixingLightsOrComms": "Uses it takes to fix Lights/Comms", - "AbilityCD": "Ability Cooldown", "GrenadierSkillMaxOfUseage": "(Initial) Max number of Grenades", "ShowSpecificRole": "Know specific roles on Task Completion", "TimeMasterMaxUses": "(Initial) Max Amount of Ability Uses", @@ -1445,6 +1444,7 @@ "Cooldown": "Cooldown", "AbilityCooldown": "Ability Cooldown", + "SkillLimitTimes": "Max Number of Ability Uses", "CanKill": "Can Kill", "KillCooldown": "Kill Cooldown", "CanVent": "Can Vent", @@ -1656,11 +1656,7 @@ "BaitNotification": "Reveal Bait at the first meeting", "BaitAdviceAlive": "{0} is the Bait. Whoever kills the Bait will commit self-report.", "BaitCanBeReportedUnderAllConditions": "Bait Can Be Reported even if a meeting is disabled during comms sabotage", - "DeceiverSkillCooldown": "Ability cooldown", - "DeceiverSkillLimitTimes": "Max number of uses", "DeceiverAbilityLost": "Deceiver loses ability if it deceives player without kill button", - "PursuerSkillCooldown": "Ability cooldown", - "PursuerSkillLimitTimes": "Max number of uses", "AddictSuicideTimer": "Time Until Suicide", "GrenadierSkillCooldown": "Grenade Cooldown", "GrenadierSkillDuration": "Grenade Duration", @@ -1691,7 +1687,6 @@ "MedicShieldDeactivationIsVisible_OFF": "OFF", "MedicResetCooldown": "On kill attempt, reset murderer's cooldown to", "MedicShieldedCanBeGuessed": "Guessing ignores Medic shield", - "FortuneTellerSkillLimit": "Max number of ability uses", "MadmateSpawnMode": "Madmate spawning mode", "MadmateSpawnMode.Assign": "Assign", "MadmateSpawnMode.FirstKill": "First Kill", @@ -1821,13 +1816,10 @@ "Jackal_SidekickCanKillJackal": "Sidekick can kill Jackal", "JackalCanKillSidekick": "Jackal can kill Sidekick", - "PacifistCooldown": "Ability Cooldown", - "PacifistMaxOfUseage": "Max Number of Ability Uses", "CoronerArrowsPointingToDeadBody": "Arrows pointing to dead bodies", "CoronerLeaveDeadBodyUnreportable": "Bodies the Coroner uses can't be reported", "CoronerInformKillerBeingTracked": "Inform the Killer that he gets tracked", - "PresidentAbilityUses": "Max Number of Ability Uses", "PresidentCanBeGuessedAfterRevealing": "President can be guessed after revealing", "HidePresidentEndCommand": "Hide President's commands", "NeutralsSeePresident": "Neutrals can see revealed President", @@ -2083,7 +2075,6 @@ "MorticianGetNoInfo": "According to your inspection, {0} did not seem to have contact with anyone during their lifetime.", "MorticianGetInfo": "According to your inspection, the last person {0} came into contact with during their lifetime was {1}.", - "MediumContactLimit": "Max number of contacts (ability uses)", "MediumOnlyReceiveMsgFromCrew": "Receive messages only from Crewmates (including Madmates and Charmed Players)", "MediumTitle": "MEDIUM", "MediumHelp": "/ms yes to agree\n/ms no to disagree", diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 89d0cb8396..6164e808e6 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -465,6 +465,7 @@ public enum GeneralOption // Ability Cooldown, AbilityCooldown, + SkillLimitTimes, // Impostor-based settings CanKill, diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index a7948ae48d..94159a2a9d 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -26,9 +26,9 @@ internal class Deceiver : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deceiver); - DeceiverSkillCooldown = FloatOptionItem.Create(Id + 10, "DeceiverSkillCooldown", new(2.5f, 180f, 2.5f), 20f, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deceiver]) + DeceiverSkillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.AbilityCooldown, new(2.5f, 180f, 2.5f), 20f, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deceiver]) .SetValueFormat(OptionFormat.Seconds); - DeceiverSkillLimitTimes = IntegerOptionItem.Create(Id + 11, "DeceiverSkillLimitTimes", new(1, 15, 1), 2, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deceiver]) + DeceiverSkillLimitTimes = IntegerOptionItem.Create(Id + 11, GeneralOption.SkillLimitTimes, new(1, 15, 1), 2, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deceiver]) .SetValueFormat(OptionFormat.Times); DeceiverAbilityLost = BooleanOptionItem.Create(Id + 12, "DeceiverAbilityLost", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deceiver]); } diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 9dfaa5ddee..b1c4c1e3ef 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -34,7 +34,7 @@ internal class FortuneTeller : RoleBase public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.FortuneTeller); - CheckLimitOpt = IntegerOptionItem.Create(Id + 10, "FortuneTellerSkillLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]) + CheckLimitOpt = IntegerOptionItem.Create(Id + 10, GeneralOption.SkillLimitTimes, new(0, 20, 1), 1, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]) .SetValueFormat(OptionFormat.Times); RandomActiveRoles = BooleanOptionItem.Create(Id + 11, "RandomActiveRoles", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]); AccurateCheckMode = BooleanOptionItem.Create(Id + 12, "AccurateCheckMode", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.FortuneTeller]); diff --git a/Roles/Crewmate/Medium.cs b/Roles/Crewmate/Medium.cs index c4e2d84e91..5b608d99f9 100644 --- a/Roles/Crewmate/Medium.cs +++ b/Roles/Crewmate/Medium.cs @@ -28,7 +28,7 @@ internal class Medium : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Medium); - ContactLimitOpt = IntegerOptionItem.Create(Id + 10, "MediumContactLimit", new(0, 15, 1), 1, TabGroup.CrewmateRoles, false) + ContactLimitOpt = IntegerOptionItem.Create(Id + 10, GeneralOption.SkillLimitTimes, new(0, 15, 1), 1, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medium]) .SetValueFormat(OptionFormat.Times); OnlyReceiveMsgFromCrew = BooleanOptionItem.Create(Id + 11, "MediumOnlyReceiveMsgFromCrew", true, TabGroup.CrewmateRoles, false) diff --git a/Roles/Crewmate/Pacifist.cs b/Roles/Crewmate/Pacifist.cs index 5798f26430..721f2342fc 100644 --- a/Roles/Crewmate/Pacifist.cs +++ b/Roles/Crewmate/Pacifist.cs @@ -27,9 +27,9 @@ internal class Pacifist : RoleBase public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Pacifist); - PacifistCooldown = FloatOptionItem.Create(Id + 10, "PacifistCooldown", new(1f, 180f, 1f), 30f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Pacifist]) + PacifistCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.AbilityCooldown, new(1f, 180f, 1f), 30f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Pacifist]) .SetValueFormat(OptionFormat.Seconds); - PacifistMaxOfUseage = IntegerOptionItem.Create(Id + 11, "PacifistMaxOfUseage", new(0, 20, 1), 3, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Pacifist]) + PacifistMaxOfUseage = IntegerOptionItem.Create(Id + 11, GeneralOption.SkillLimitTimes, new(0, 20, 1), 3, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Pacifist]) .SetValueFormat(OptionFormat.Times); PacifistAbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(9204, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Pacifist]) .SetValueFormat(OptionFormat.Times); diff --git a/Roles/Crewmate/President.cs b/Roles/Crewmate/President.cs index 057cdb7a7a..502d089ec2 100644 --- a/Roles/Crewmate/President.cs +++ b/Roles/Crewmate/President.cs @@ -30,7 +30,7 @@ internal class President : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.President); - PresidentAbilityUses = IntegerOptionItem.Create(Id + 10, "PresidentAbilityUses", new(1, 20, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]) + PresidentAbilityUses = IntegerOptionItem.Create(Id + 10, GeneralOption.SkillLimitTimes, new(1, 20, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]) .SetValueFormat(OptionFormat.Times); PresidentCanBeGuessedAfterRevealing = BooleanOptionItem.Create(Id + 11, "PresidentCanBeGuessedAfterRevealing", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); NeutralsSeePresident = BooleanOptionItem.Create(Id + 12, "NeutralsSeePresident", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index 63d898eb88..5e1f758ff5 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -23,7 +23,7 @@ internal class Witness : RoleBase public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Witness, 1); - WitnessCD = FloatOptionItem.Create(Id + 10, "AbilityCD", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) + WitnessCD = FloatOptionItem.Create(Id + 10, GeneralOption.AbilityCooldown, new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) .SetValueFormat(OptionFormat.Seconds); WitnessTime = IntegerOptionItem.Create(Id + 11, "WitnessTime", new(1, 30, 1), 10, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) .SetValueFormat(OptionFormat.Seconds); diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index 7390a1b9b7..0066405714 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -25,9 +25,9 @@ internal class Pursuer : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pursuer); - PursuerSkillCooldown = FloatOptionItem.Create(Id + 10, "PursuerSkillCooldown", new(2.5f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) + PursuerSkillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.AbilityCooldown, new(2.5f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) .SetValueFormat(OptionFormat.Seconds); - PursuerSkillLimitTimes = IntegerOptionItem.Create(Id + 11, "PursuerSkillLimitTimes", new(1, 20, 1), 2, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) + PursuerSkillLimitTimes = IntegerOptionItem.Create(Id + 11, GeneralOption.SkillLimitTimes, new(1, 20, 1), 2, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) .SetValueFormat(OptionFormat.Times); } public override void Init() From 37d509c8dcf0324652ae49ea4bf88c42c60c5574 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 14:28:09 +0800 Subject: [PATCH 374/778] Fix? --- Roles/Impostor/DoubleAgent.cs | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 0c14a0a98c..78ff7fe2dd 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -1,14 +1,14 @@ -using UnityEngine; -using static TOHE.Options; -using static TOHE.Translator; +using Hazel; +using System; +using InnerNet; +using UnityEngine; +using TOHE.Modules; using TOHE.Roles.Core; using TOHE.Roles.Crewmate; -using TOHE.Modules; using TOHE.Roles.Neutral; -using Hazel; -using InnerNet; -using System; using static TOHE.Utils; +using static TOHE.Options; +using static TOHE.Translator; namespace TOHE.Roles.Impostor; internal class DoubleAgent : RoleBase @@ -104,7 +104,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) _ = new LateTask(() => { if (pc.inVent) pc.MyPhysics.RpcBootFromVent(vent.Id); - pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_DiffusedAgitaterBomb"))); + pc.Notify(ColorString(GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_DiffusedAgitaterBomb"))); }, 0.8f, "Boot Player from vent: " + vent.Id); return; } @@ -115,7 +115,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) _ = new LateTask(() => { if (pc.inVent) pc.MyPhysics.RpcBootFromVent(vent.Id); - pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_DiffusedBastionBomb"))); + pc.Notify(ColorString(GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_DiffusedBastionBomb"))); }, 0.5f, "Boot Player from vent: " + vent.Id); } } @@ -173,7 +173,7 @@ private void OnFixedUpdateOthers(PlayerControl player) if (!player.IsAlive()) // If Player is dead clear bomb. ClearBomb(); - if (BombIsActive && (GameStates.IsInTask && GameStates.IsInGame) && !(GameStates.IsMeeting && GameStates.IsExilling)) + if (BombIsActive && GameStates.IsInTask && GameStates.IsInGame && !(GameStates.IsMeeting && GameStates.IsExilling)) { var OldCurrentBombedTime = (int)CurrentBombedTime; @@ -194,11 +194,11 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) { if (!pc.IsModClient()) { - string Duration = Utils.ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); + string Duration = ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Item1 != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); } - if (CurrentBombedPlayers.Any(playerId => Utils.GetPlayerById(playerId) == null)) // If playerId is a null Player clear bomb. + if (CurrentBombedPlayers.Any(playerId => !GetPlayerById(playerId).IsAlive())) // If playerId is a null Player clear bomb. ClearBomb(); } @@ -231,10 +231,10 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) pc.GetRoleClass()?.Add(pc.PlayerId); pc.MarkDirtySettings(); - string RoleName = Utils.ColorString(Utils.GetRoleColor(pc.GetCustomRole()), Utils.GetRoleName(pc.GetCustomRole())); + string RoleName = ColorString(GetRoleColor(pc.GetCustomRole()), GetRoleName(pc.GetCustomRole())); if (Role == CustomRoles.ImpostorTOHE) - RoleName = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Admired), $"{GetString("Admired")} {GetString("ImpostorTOHE")}"); - pc.Notify(Utils.ColorString(Utils.GetRoleColor(pc.GetCustomRole()), GetString("DoubleAgentRoleChange") + RoleName)); + RoleName = ColorString(GetRoleColor(CustomRoles.Admired), $"{GetString("Admired")} {GetString("ImpostorTOHE")}"); + pc.Notify(ColorString(GetRoleColor(pc.GetCustomRole()), GetString("DoubleAgentRoleChange") + RoleName)); } } } @@ -246,7 +246,7 @@ private void BoomBoom(PlayerControl player) foreach (PlayerControl target in Main.AllAlivePlayerControls) // Get players in radius of bomb that are not in a vent. { - if (Utils.GetDistance(player.GetCustomPosition(), target.GetCustomPosition()) <= ExplosionRadius.GetFloat()) + if (GetDistance(player.GetCustomPosition(), target.GetCustomPosition()) <= ExplosionRadius.GetFloat()) { if (player.inVent) continue; Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Bombed; @@ -258,14 +258,14 @@ private void BoomBoom(PlayerControl player) CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); ClearBomb(); - _Player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); + _Player.Notify(ColorString(GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); } // Set bomb mark on player. public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { if (seen == null ) return string.Empty; - if (CurrentBombedPlayers.Contains(seen.PlayerId)) return Utils.ColorString(Color.red, "Ⓑ"); // L Rizz :) + if (CurrentBombedPlayers.Contains(seen.PlayerId)) return ColorString(Color.red, "Ⓑ"); // L Rizz :) return string.Empty; } @@ -274,7 +274,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo public override string GetLowerText(PlayerControl player, PlayerControl seen, bool isForMeeting = false, bool isForHud = false) { if (player == null || player != seen || player.IsModClient() && !isForHud) return string.Empty; - if (CurrentBombedTime > 0 && CurrentBombedTime < BombExplosionTimer.GetFloat() + 1) return Utils.ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); + if (CurrentBombedTime > 0 && CurrentBombedTime < BombExplosionTimer.GetFloat() + 1) return ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); return string.Empty; } @@ -307,7 +307,7 @@ public static void CreatePlantBombButton(MeetingHud __instance) { foreach (var pva in __instance.playerStates) { - var pc = Utils.GetPlayerById(pva.TargetPlayerId); + var pc = GetPlayerById(pva.TargetPlayerId); if (pc == null || !pc.IsAlive()) continue; if (pc.GetCustomRole().GetCustomRoleTeam() == Custom_Team.Impostor || PlayerControl.LocalPlayer == pc) continue; GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; From 80803822d7adcaee41e44c2a5e711d93fd9db730 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 14:32:15 +0800 Subject: [PATCH 375/778] Add check null --- Modules/CustomRolesHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 82c0cb6e10..33d2da56dc 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -374,9 +374,9 @@ CustomRoles.Circumvent or public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool checkLimitAddons = true) { // Only add-ons - if (!role.IsAdditionRole()) return false; + if (!role.IsAdditionRole() || pc == null) return false; - if(Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()))) + if (Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()))) return false; // if player already has this addon From 1f0ac592e2528e96693d60e7241f6ff172742610 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 14:36:12 +0800 Subject: [PATCH 376/778] Add check Any() --- Roles/Core/AssignManager/AddonAssign.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Core/AssignManager/AddonAssign.cs b/Roles/Core/AssignManager/AddonAssign.cs index a5ffcc3a64..5d5c8fadfe 100644 --- a/Roles/Core/AssignManager/AddonAssign.cs +++ b/Roles/Core/AssignManager/AddonAssign.cs @@ -113,6 +113,7 @@ public static void StartSortAndAssign() public static void AssignSubRoles(CustomRoles role, int RawCount = -1) { var allPlayers = Main.AllAlivePlayerControls.Where(x => CustomRolesHelper.CheckAddonConfilct(role, x)).ToList(); + if (!allPlayers.Any()) return; var count = Math.Clamp(RawCount, 0, allPlayers.Count); if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); if (count <= 0) return; From fe45ad42115f13a0d28c7b3bcfcf0f492ee7111d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 14:42:51 +0800 Subject: [PATCH 377/778] Add check null --- Patches/ExilePatch.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index d9093dc6df..a5f3d4d80b 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -72,22 +72,21 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (CLThingy && exiled != null) { + var exiledPC = exiled.Object; + // Reset player cam for exiled desync impostor - if (exiled.Object.HasDesyncRole()) + if (exiledPC != null && exiledPC.HasDesyncRole()) { - exiled.Object?.ResetPlayerCam(1f); + exiledPC.ResetPlayerCam(1f); } exiled.IsDead = true; exiled.PlayerId.SetDeathReason(PlayerState.DeathReason.Vote); - var exiledPC = Utils.GetPlayerById(exiled.PlayerId); - var exiledRoleClass = exiledPC.GetRoleClass(); - + var exiledRoleClass = exiled.PlayerId.GetRoleClassById(); var emptyString = string.Empty; exiledRoleClass?.CheckExile(exiled, ref DecidedWinner, isMeetingHud: false, name: ref emptyString); - CustomRoleManager.AllEnabledRoles.Do(roleClass => roleClass.CheckExileTarget(exiled, ref DecidedWinner, isMeetingHud: false, name: ref emptyString)); if (CustomWinnerHolder.WinnerTeam != CustomWinner.Terrorist) Main.PlayerStates[exiled.PlayerId].SetDead(); From 4ef0177c8510d40a2c0ecb70c5e6d2f09cf304c5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 14:43:30 +0800 Subject: [PATCH 378/778] Change --- Patches/ExilePatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index a5f3d4d80b..50e2eb8a0e 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -75,9 +75,9 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) var exiledPC = exiled.Object; // Reset player cam for exiled desync impostor - if (exiledPC != null && exiledPC.HasDesyncRole()) + if (exiledPC.HasDesyncRole()) { - exiledPC.ResetPlayerCam(1f); + exiledPC?.ResetPlayerCam(1f); } exiled.IsDead = true; From a7fc6111cc86b4ecef7d7acfc0777099c9fc5654 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 15:03:38 +0800 Subject: [PATCH 379/778] Add try {} catch {} in neutral win check --- Patches/CheckGameEndPatch.cs | 15 +++++++++++---- Patches/ShipStatusPatch.cs | 6 ++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index fda74789f1..e7cc8d9a44 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -612,10 +612,17 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) var winnnerLength = winners.Length; if (winnnerLength == 1) { - var winnerRole = winners.First().Key.GetNeutralCustomRoleFromCountType(); - reason = GameOverReason.ImpostorByKill; - ResetAndSetWinner(winnerRole.GetNeutralCustomWinnerFromRole()); - WinnerRoles.Add(winnerRole); + try + { + var winnerRole = winners.First().Key.GetNeutralCustomRoleFromCountType(); + reason = GameOverReason.ImpostorByKill; + ResetAndSetWinner(winnerRole.GetNeutralCustomWinnerFromRole()); + WinnerRoles.Add(winnerRole); + } + catch + { + return false; + } } else if (winnnerLength == 0) { diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index be8066fa8b..b3750c20ff 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -39,7 +39,8 @@ public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] SystemType or SystemTypes.Security or SystemTypes.Decontamination or SystemTypes.Decontamination2 - or SystemTypes.Decontamination3) return true; + or SystemTypes.Decontamination3 + or SystemTypes.MedBay) return true; if (GameStates.IsHideNSeek) return true; @@ -59,7 +60,8 @@ public static void Postfix(ShipStatus __instance, [HarmonyArgument(0)] SystemTyp or SystemTypes.Security or SystemTypes.Decontamination or SystemTypes.Decontamination2 - or SystemTypes.Decontamination3) return; + or SystemTypes.Decontamination3 + or SystemTypes.MedBay) return; if (GameStates.IsHideNSeek) return; From c8a1d9babef3ffc9ed06b4613cab32e23f59dfcd Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:36:08 +0200 Subject: [PATCH 380/778] ABSOLUTELY BALLING OUT OF CONTROL --- Modules/AntiBlackout.cs | 16 ++++++++++++++-- Patches/ExilePatch.cs | 12 +++++++++--- Patches/PlayerControlPatch.cs | 5 +++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index c52e588651..56f450a779 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -1,4 +1,4 @@ -using AmongUs.GameOptions; +using AmongUs.GameOptions; using Hazel; using System; using System.Runtime.CompilerServices; @@ -75,6 +75,7 @@ public static bool CheckBlackOut() public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { + TempReviveGuardianAngels(); SetRole(); logger.Info($"SetIsDead is called from {callerMethodName}"); if (IsCached) @@ -241,7 +242,18 @@ public static void AfterMeetingTasks() Logger.Error($"{error}", "AntiBlackout.AfterMeetingTasks"); } } - public static void SetRole() + private static void TempReviveGuardianAngels() // FUCK IT WE BALL 🗣💯💯 + { + foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) + { + foreach (var reciever in Main.AllPlayerControls) + { + if (reciever.OwnedByHost()) continue; + pc.RpcSetRoleDesync(RoleTypes.Impostor, reciever.GetClientId()); + } + } + } + private static void SetRole() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 81d8875e23..5b733926d0 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -192,11 +192,17 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) Main.AfterMeetingDeathPlayers.Clear(); - //WELP, all GuardianAngel players have to be desynced + reset cams now, but hey at least everyone else desync is fine now. + //Kill off GAS again foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { - pc.ResetPlayerCam(); - pc.RpcSetRoleDesync(RoleTypes.GuardianAngel, pc.GetClientId()); + foreach (var reciever in Main.AllPlayerControls) + { + if (reciever.OwnedByHost()) continue; + RoleTypes typa = pc == reciever ? RoleTypes.GuardianAngel : RoleTypes.CrewmateGhost; + pc.RpcSetRoleDesync(typa, reciever.GetClientId()); + } + //pc.ResetPlayerCam(); + //pc.RpcSetRoleDesync(RoleTypes.GuardianAngel, pc.GetClientId()); } }, 0.8f, "AfterMeetingDeathPlayers Task"); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 4e5ce53b7b..2de430bcc4 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1922,6 +1922,11 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol { roleType = RoleTypes.GuardianAngel; __instance.RpcSetRoleDesync(RoleTypes.GuardianAngel, __instance.GetClientId()); + foreach (var seer in Main.AllPlayerControls) + { + if (seer == __instance) continue; + __instance.RpcSetRoleDesync(RoleTypes.CrewmateGhost, seer.GetClientId()); + } GhostRoleAssign.CreateGAMessage(__instance); return false; } From d76ec0851ceedce306131ad959a0c32b1c774e8d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:39:46 +0200 Subject: [PATCH 381/778] woopsie daisy- --- Modules/AntiBlackout.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 56f450a779..aef51154d6 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -244,6 +244,8 @@ public static void AfterMeetingTasks() } private static void TempReviveGuardianAngels() // FUCK IT WE BALL 🗣💯💯 { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { foreach (var reciever in Main.AllPlayerControls) From d20fb293c7345ef7f1900cf662f5ad9e678e4778 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:45:47 +0200 Subject: [PATCH 382/778] no longer needed --- Patches/IntroPatch.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 9fddcfbd96..5b84913e97 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -616,10 +616,6 @@ public static void Postfix() DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); } }, 0.1f, "Assign Impostor desync roles for crewmates"); - foreach (var pc in Main.AllPlayerControls) - { - pc.MarkDirtySettings(); - } if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { From eb5dcf7d5363cad5ab88c1f3ea86287dddb0e8ea Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:57:56 +0200 Subject: [PATCH 383/778] this is cap btw --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index bab79c2be2..c1084704b7 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2274,7 +2274,7 @@ "Message.YTPlanNotice": "Note: The [YouTuber Plan] is enabled in this lobby, which means the Host can specify their role in the next game to make it easier to get content. If the Host abuses this feature, please exit the game or report it.\nCurrent Creator Credentials:", "Message.OnlyCanBeUsedByHost": "ERROR\n\nThis command may only be used by the host.", "Message.MaxPlayers": "Maximum players set to ", - "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win\n\nNB: You will see a death-animation of yourself after every meeting to fix blackscreen", + "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", "ApocalypseInfoTitle": "Neutral Apocalypse Info:", "Message.ApocalypseInfo": "Every role of the <#ff174f>Apocalypse Team has their own objective to carry out in order to transform.\n<#2B0804>Transformed <#ff174f>Apocalypse members have a drastic change on the game and are immortal (except for being voted), but everyone will be notified that they have transformed.\n\nRoles: <#e5f6b4>Plaguebearer, <#A675A1>Soul Collector, <#bf9f7a>Baker, <#cc0044>Berserker\nTransformed: <#343136>Pestilence, <#644661>Death, <#83461c>Famine, <#2B0804>War\n\nApocalypse members can see eachother's roles and ability icons.\nLike Neutral Killers, Apocalypse members keep the game going as well, have fun!", "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", From ea5f1e8ea87e6c19466231c305d6d36d511d5b2d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sat, 24 Aug 2024 10:00:05 +0200 Subject: [PATCH 384/778] comment --- Roles/Core/RoleBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 3fbb625644..1d5614c942 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -87,9 +87,10 @@ public virtual void Remove(byte playerId) public CustomRoles ThisCustomRole => System.Enum.Parse(GetType().Name, true); + //this is a draft, it is not usable yet, Imma fix it in another PR /// - /// A generic method to set if someone (desync imps) should see each-other on the reveal screen. + /// A generic method to set if someone (desync imps) should see each-other on the reveal screen. (they will also not be able to kill eachother) /// public virtual void SetDesyncImpostorBuddies(ref Dictionary> DesyncImpostorBuddy, PlayerControl caller) { From 7143243cfb61e819ee94661d2c7d28ab8359ce7c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 16:08:29 +0800 Subject: [PATCH 385/778] First commit --- Modules/ExtendedPlayerControl.cs | 15 +++ Modules/Utils.cs | 4 + Patches/DleksPatch.cs | 4 +- Patches/HudSpritePatch.cs | 2 +- Patches/IntroPatch.cs | 13 ++- Patches/PlayerControlPatch.cs | 2 +- Patches/VentSystemPatch.cs | 180 +++++++++++++++++++++++++++++++ Patches/onGameStartedPatch.cs | 7 +- README.md | 1 + main.cs | 2 +- 10 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 Patches/VentSystemPatch.cs diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5b8fc782a4..326917bfb8 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -4,6 +4,7 @@ using System; using System.Text; using TOHE.Modules; +using TOHE.Patches; using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; @@ -409,6 +410,20 @@ public static void RpcSpecificRejectShapeshift(this PlayerControl player, Player } } } + public static List GetVentsFromClosest(this PlayerControl player) + { + Vector2 playerpos = player.transform.position; + List vents = new(); + foreach (var vent in ShipStatus.Instance.AllVents) + vents.Add(vent); + vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); + return vents; + } + + public static void RpcSetVentInteraction(this PlayerControl player) + { + VentSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast(), player); + } public static void RpcSetSpecificScanner(this PlayerControl target, PlayerControl seer, bool IsActive) { if (!AmongUsClient.Instance.AmHost) return; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 7e6ef4cc5c..e761aba11d 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2142,6 +2142,10 @@ public static void SyncAllSettings() PlayerGameOptionsSender.SetDirtyToAll(); GameOptionsSender.SendAllGameOptions(); } + public static void SetAllVentInteractions() + { + VentSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast()); + } public static bool DeathReasonIsEnable(this PlayerState.DeathReason reason, bool checkbanned = false) { static bool BannedReason(PlayerState.DeathReason rso) diff --git a/Patches/DleksPatch.cs b/Patches/DleksPatch.cs index 51d086d613..7d780a7e97 100644 --- a/Patches/DleksPatch.cs +++ b/Patches/DleksPatch.cs @@ -90,7 +90,7 @@ public static class VentSetButtonsPatch private static bool Prefix(Vent __instance, [HarmonyArgument(0)] ref bool enabled) { // if map is Dleks - if (GameStates.DleksIsActive && Main.introDestroyed) + if (GameStates.DleksIsActive && Main.IntroDestroyed) { enabled = false; if (GameStates.IsMeeting) @@ -101,7 +101,7 @@ private static bool Prefix(Vent __instance, [HarmonyArgument(0)] ref bool enable public static void Postfix(Vent __instance, [HarmonyArgument(0)] bool enabled) { if (!GameStates.DleksIsActive) return; - if (enabled || !Main.introDestroyed) return; + if (enabled || !Main.IntroDestroyed) return; var setActive = ShowButtons || !PlayerControl.LocalPlayer.inVent && !GameStates.IsMeeting; switch (__instance.Id) diff --git a/Patches/HudSpritePatch.cs b/Patches/HudSpritePatch.cs index cc0b6ec7eb..3142e4632c 100644 --- a/Patches/HudSpritePatch.cs +++ b/Patches/HudSpritePatch.cs @@ -23,7 +23,7 @@ public static void Postfix(HudManager __instance) if (player == null || !Main.EnableCustomButton.Value || AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameStates.IsHideNSeek || !GameStates.IsModHost) return; if (!SetHudActivePatch.IsActive || !player.IsAlive()) return; - if (!AmongUsClient.Instance.IsGameStarted || !Main.introDestroyed) + if (!AmongUsClient.Instance.IsGameStarted || !Main.IntroDestroyed) { Kill = null; Ability = null; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index f9b0827393..692644cb74 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using TOHE.Modules; +using TOHE.Patches; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using TOHE.Roles.Neutral; @@ -523,7 +524,7 @@ public static void Postfix() { if (!GameStates.IsInGame || RoleBasisChanger.SkipTasksAfterAssignRole) return; - Main.introDestroyed = true; + Main.IntroDestroyed = true; if (!GameStates.AirshipIsActive) { @@ -626,6 +627,16 @@ public static void Postfix() { PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; } + + foreach (var pc in PlayerControl.AllPlayerControls) + { + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + if (VentSystemDeterioratePatch.BlockVentInteraction(pc)) + { + Utils.SetAllVentInteractions(); + break; + } + } } Logger.Info("OnDestroy", "IntroCutscene"); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 663737540c..e870578794 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1556,7 +1556,7 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) { _ = new LateTask(() => __instance?.RpcBootFromVent(id), 0.5f, "Prevent Enter Vents"); } - return false; + //return false; } playerRoleClass?.OnCoEnterVent(__instance, id); diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs new file mode 100644 index 0000000000..7a3668545a --- /dev/null +++ b/Patches/VentSystemPatch.cs @@ -0,0 +1,180 @@ +using AmongUs.GameOptions; +using Hazel; +using System; + +namespace TOHE.Patches; + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Serialize))] +class ShipStatusSerializePatch +{ + public static void Prefix(ShipStatus __instance, /*[HarmonyArgument(0)] MessageWriter writer,*/ [HarmonyArgument(1)] bool initialState) + { + if (!AmongUsClient.Instance.AmHost) return; + if (initialState) return; + bool cancel = false; + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (VentSystemDeterioratePatch.BlockVentInteraction(pc)) + cancel = true; + } + var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); + if (cancel && ventilationSystem != null && ventilationSystem.IsDirty) + { + Utils.SetAllVentInteractions(); + ventilationSystem.IsDirty = false; + } + + } +} + +[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.Deteriorate))] +static class VentSystemDeterioratePatch +{ + public static Dictionary LastClosestVent; + public static void Postfix(VentilationSystem __instance) + { + if (!AmongUsClient.Instance.AmHost) return; + if (!Main.IntroDestroyed) return; + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (BlockVentInteraction(pc)) + { + LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + MessageWriter writer = MessageWriter.Get(SendOption.None); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = 0; + foreach (var vent in ShipStatus.Instance.AllVents) + { + if (!pc.CanUseVent()) + ++vents; + } + List AllPlayers = []; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + } + int maxVents = Math.Min(vents, AllPlayers.Count); + int blockedVents = 0; + writer.WritePacked(maxVents); + foreach (var vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent()) + { + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; + } + if (blockedVents >= maxVents) + break; + } + writer.WritePacked(__instance.PlayersInsideVents.Count); + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } + } + } + public static bool CanUseVent(this PlayerControl player) => player.CanUseImpostorVentButton() || player.Data.Role.Role == RoleTypes.Engineer; + public static bool BlockVentInteraction(PlayerControl pc) + { + if (!pc.AmOwner && !pc.IsModClient() && !pc.Data.IsDead && !pc.CanUseVent()) + { + //foreach (var vent in ShipStatus.Instance.AllVents) + //{ + // if (!(pc.Data.Role.Role == RoleTypes.Engineer || pc.Data.Role.IsImpostor)) + // return true; + //} + + return true; + } + return false; + } + public static void SerializeV2(VentilationSystem __instance, PlayerControl player = null) + { + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (pc.AmOwner) continue; + if (player != null && pc != player) continue; + if (BlockVentInteraction(pc)) + { + MessageWriter writer = MessageWriter.Get(SendOption.None); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = 0; + foreach (var vent in ShipStatus.Instance.AllVents) + { + if (!pc.CanUseVent()) + ++vents; + } + List AllPlayers = []; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + } + int maxVents = Math.Min(vents, AllPlayers.Count); + int blockedVents = 0; + writer.WritePacked(maxVents); + foreach (var vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent()) + { + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; + } + if (blockedVents >= maxVents) + break; + } + writer.WritePacked(__instance.PlayersInsideVents.Count); + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } + else + { + MessageWriter writer = MessageWriter.Get(SendOption.None); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + { + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + { + writer.StartMessage((byte)SystemTypes.Ventilation); + __instance.Serialize(writer, false); + writer.EndMessage(); + } + writer.EndMessage(); + } + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } + } + } +} diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 61ed7fcff6..d5eda159a4 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -2,6 +2,7 @@ using Hazel; using System; using UnityEngine; +using TOHE.Patches; using TOHE.Modules; using TOHE.Modules.ChatManager; using TOHE.Roles.AddOns.Common; @@ -85,12 +86,14 @@ public static void Postfix(AmongUsClient __instance) Main.BardCreations = 0; Main.MeetingsPassed = 0; Main.MeetingIsStarted = false; - Main.introDestroyed = false; + Main.IntroDestroyed = false; GameEndCheckerForNormal.ShouldNotCheck = false; GameEndCheckerForNormal.ForEndGame = false; GameEndCheckerForNormal.ShowAllRolesWhenGameEnd = false; GameStartManagerPatch.GameStartManagerUpdatePatch.AlredyBegin = false; + VentSystemDeterioratePatch.LastClosestVent = []; + ChatManager.ResetHistory(); ReportDeadBodyPatch.CanReport = []; Options.UsedButtonCount = 0; @@ -177,6 +180,8 @@ public static void Postfix(AmongUsClient __instance) ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = 0; + pc.cosmetics.nameText.text = pc.name; Camouflage.PlayerSkins[pc.PlayerId] = new NetworkedPlayerInfo.PlayerOutfit().Set(currentName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId, outfit.NamePlateId); diff --git a/README.md b/README.md index 2ba4b5529e..ca1c268bf1 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ ### :star: [MoreGamemodes](https://github.com/Rabek009/MoreGamemodes) > > - Changer Role Basis (RoleBasisChanger.cs) +> - Disable use vent for vanilla players (VentSystemPatch.cs) ### :star: [Reactor](https://github.com/NuclearPowered/Reactor) > > - Reference: Disable the 5s timeout on custom servers diff --git a/main.cs b/main.cs index c517a838d8..1fb3e9a0dd 100644 --- a/main.cs +++ b/main.cs @@ -171,7 +171,7 @@ public class Main : BasePlugin public static bool VisibleTasksCount = false; public static bool AssignRolesIsStarted = false; public static string HostRealName = ""; - public static bool introDestroyed = false; + public static bool IntroDestroyed = false; public static int DiscussionTime; public static int VotingTime; public static float DefaultCrewmateVision; From a69af175606a3ea6327227a0484648feb7cf957b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 16:20:10 +0800 Subject: [PATCH 386/778] Add parentheses for readability --- Modules/ExtendedPlayerControl.cs | 5 +- Patches/VentSystemPatch.cs | 145 ++++++++++++++++--------------- 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 326917bfb8..574462e582 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -413,9 +413,7 @@ public static void RpcSpecificRejectShapeshift(this PlayerControl player, Player public static List GetVentsFromClosest(this PlayerControl player) { Vector2 playerpos = player.transform.position; - List vents = new(); - foreach (var vent in ShipStatus.Instance.AllVents) - vents.Add(vent); + List vents = [.. ShipStatus.Instance.AllVents]; vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); return vents; } @@ -678,6 +676,7 @@ public static bool HasKillButton(this PlayerControl pc) _ => false }; } + public static bool CanUseVent(this PlayerControl player) => player.CanUseImpostorVentButton() || player.Data.Role.Role == RoleTypes.Engineer; public static bool CanUseImpostorVentButton(this PlayerControl pc) { if (!pc.IsAlive()) return false; diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 7a3668545a..2cd2e9a073 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -44,50 +44,53 @@ public static void Postfix(VentilationSystem __instance) writer.StartMessage(6); writer.Write(AmongUsClient.Instance.GameId); writer.WritePacked(pc.GetClientId()); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = 0; - foreach (var vent in ShipStatus.Instance.AllVents) { - if (!pc.CanUseVent()) - ++vents; - } - List AllPlayers = []; - foreach (var playerInfo in GameData.Instance.AllPlayers) - { - if (playerInfo != null && !playerInfo.Disconnected) - AllPlayers.Add(playerInfo); - } - int maxVents = Math.Min(vents, AllPlayers.Count); - int blockedVents = 0; - writer.WritePacked(maxVents); - foreach (var vent in pc.GetVentsFromClosest()) - { - if (!pc.CanUseVent()) + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); { - writer.Write(AllPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = 0; + foreach (var vent in ShipStatus.Instance.AllVents) + { + if (!pc.CanUseVent()) + ++vents; + } + List AllPlayers = []; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + } + int maxVents = Math.Min(vents, AllPlayers.Count); + int blockedVents = 0; + writer.WritePacked(maxVents); + foreach (var vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent()) + { + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; + } + if (blockedVents >= maxVents) + break; + } + writer.WritePacked(__instance.PlayersInsideVents.Count); + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + writer.EndMessage(); } - if (blockedVents >= maxVents) - break; - } - writer.WritePacked(__instance.PlayersInsideVents.Count); - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) - { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); + writer.EndMessage(); } writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); } } } - public static bool CanUseVent(this PlayerControl player) => player.CanUseImpostorVentButton() || player.Data.Role.Role == RoleTypes.Engineer; public static bool BlockVentInteraction(PlayerControl pc) { if (!pc.AmOwner && !pc.IsModClient() && !pc.Data.IsDead && !pc.CanUseVent()) @@ -114,44 +117,48 @@ public static void SerializeV2(VentilationSystem __instance, PlayerControl playe writer.StartMessage(6); writer.Write(AmongUsClient.Instance.GameId); writer.WritePacked(pc.GetClientId()); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = 0; - foreach (var vent in ShipStatus.Instance.AllVents) { - if (!pc.CanUseVent()) - ++vents; - } - List AllPlayers = []; - foreach (var playerInfo in GameData.Instance.AllPlayers) - { - if (playerInfo != null && !playerInfo.Disconnected) - AllPlayers.Add(playerInfo); - } - int maxVents = Math.Min(vents, AllPlayers.Count); - int blockedVents = 0; - writer.WritePacked(maxVents); - foreach (var vent in pc.GetVentsFromClosest()) - { - if (!pc.CanUseVent()) + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); { - writer.Write(AllPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = 0; + foreach (var vent in ShipStatus.Instance.AllVents) + { + if (!pc.CanUseVent()) + ++vents; + } + List AllPlayers = []; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + } + int maxVents = Math.Min(vents, AllPlayers.Count); + int blockedVents = 0; + writer.WritePacked(maxVents); + foreach (var vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent()) + { + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; + } + if (blockedVents >= maxVents) + break; + } + writer.WritePacked(__instance.PlayersInsideVents.Count); + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + writer.EndMessage(); } - if (blockedVents >= maxVents) - break; - } - writer.WritePacked(__instance.PlayersInsideVents.Count); - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) - { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); + writer.EndMessage(); } writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); } @@ -166,7 +173,9 @@ public static void SerializeV2(VentilationSystem __instance, PlayerControl playe writer.WritePacked(ShipStatus.Instance.NetId); { writer.StartMessage((byte)SystemTypes.Ventilation); - __instance.Serialize(writer, false); + { + __instance.Serialize(writer, false); + } writer.EndMessage(); } writer.EndMessage(); From a55432133adcf803f0837d75385f3a1b106c9237 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 17:01:07 +0800 Subject: [PATCH 387/778] Improve code --- Modules/ExtendedPlayerControl.cs | 2 +- Patches/HudPatch.cs | 2 +- Patches/IntroPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 3 +- Patches/VentSystemPatch.cs | 188 ++++++++++++------------------- 5 files changed, 78 insertions(+), 119 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 574462e582..c1cc52282d 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -681,9 +681,9 @@ public static bool CanUseImpostorVentButton(this PlayerControl pc) { if (!pc.IsAlive()) return false; if (GameStates.IsHideNSeek) return true; + if (pc.Is(CustomRoles.Killer) || pc.Is(CustomRoles.Nimble)) return true; if (DollMaster.IsDoll(pc.PlayerId) || Circumvent.CantUseVent(pc)) return false; if (Necromancer.Killer && !pc.Is(CustomRoles.Necromancer)) return false; - if (pc.Is(CustomRoles.Killer) || pc.Is(CustomRoles.Nimble)) return true; if (Amnesiac.PreviousAmnesiacCanVent(pc)) return true; //this is done because amnesiac has imp basis and if amnesiac remembers a role with different basis then player will not vent as `CanUseImpostorVentButton` is false var playerRoleClass = pc.GetRoleClass(); diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 62b85d144c..d655b3142b 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -119,7 +119,7 @@ public static void Postfix(HudManager __instance) __instance.KillButton.ToggleVisible(false); } - bool CanUseVent = player.CanUseImpostorVentButton(); + bool CanUseVent = player.CanUseVent(); __instance.ImpostorVentButton.ToggleVisible(CanUseVent); player.Data.Role.CanVent = CanUseVent; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 692644cb74..830dd6a372 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -631,7 +631,7 @@ public static void Postfix() foreach (var pc in PlayerControl.AllPlayerControls) { VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - if (VentSystemDeterioratePatch.BlockVentInteraction(pc)) + if (pc.BlockVentInteraction()) { Utils.SetAllVentInteractions(); break; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e870578794..b565bf97c3 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1544,8 +1544,7 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) var playerRoleClass = __instance.myPlayer.GetRoleClass(); // Prevent vanilla players from enter vents if their current role does not allow it - if ((__instance.myPlayer.Data.Role.Role != RoleTypes.Engineer && !__instance.myPlayer.CanUseImpostorVentButton()) - || (playerRoleClass != null && playerRoleClass.CheckBootFromVent(__instance, id)) + if (!__instance.myPlayer.CanUseVent() || (playerRoleClass != null && playerRoleClass.CheckBootFromVent(__instance, id)) ) { try diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 2cd2e9a073..94d0428836 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -14,7 +14,7 @@ public static void Prefix(ShipStatus __instance, /*[HarmonyArgument(0)] MessageW bool cancel = false; foreach (var pc in PlayerControl.AllPlayerControls) { - if (VentSystemDeterioratePatch.BlockVentInteraction(pc)) + if (pc.BlockVentInteraction()) cancel = true; } var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); @@ -37,61 +37,14 @@ public static void Postfix(VentilationSystem __instance) if (!Main.IntroDestroyed) return; foreach (var pc in PlayerControl.AllPlayerControls) { - if (BlockVentInteraction(pc)) + if (pc.BlockVentInteraction()) { LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - MessageWriter writer = MessageWriter.Get(SendOption.None); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.GetClientId()); - { - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - { - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = 0; - foreach (var vent in ShipStatus.Instance.AllVents) - { - if (!pc.CanUseVent()) - ++vents; - } - List AllPlayers = []; - foreach (var playerInfo in GameData.Instance.AllPlayers) - { - if (playerInfo != null && !playerInfo.Disconnected) - AllPlayers.Add(playerInfo); - } - int maxVents = Math.Min(vents, AllPlayers.Count); - int blockedVents = 0; - writer.WritePacked(maxVents); - foreach (var vent in pc.GetVentsFromClosest()) - { - if (!pc.CanUseVent()) - { - writer.Write(AllPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; - } - if (blockedVents >= maxVents) - break; - } - writer.WritePacked(__instance.PlayersInsideVents.Count); - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) - { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); - } - writer.EndMessage(); - } - writer.EndMessage(); - } - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + pc.RpcCloseVent(__instance); } } } - public static bool BlockVentInteraction(PlayerControl pc) + public static bool BlockVentInteraction(this PlayerControl pc) { if (!pc.AmOwner && !pc.IsModClient() && !pc.Data.IsDead && !pc.CanUseVent()) { @@ -109,81 +62,88 @@ public static void SerializeV2(VentilationSystem __instance, PlayerControl playe { foreach (var pc in PlayerControl.AllPlayerControls) { - if (pc.AmOwner) continue; - if (player != null && pc != player) continue; - if (BlockVentInteraction(pc)) + if (pc.AmOwner || (player != null && pc != player)) continue; + if (pc.BlockVentInteraction()) + { + pc.RpcCloseVent(__instance); + } + else + { + pc.RpcSerializeVent(__instance); + } + } + } + private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __instance) + { + MessageWriter writer = MessageWriter.Get(SendOption.None); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + { + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); { - MessageWriter writer = MessageWriter.Get(SendOption.None); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.GetClientId()); + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = 0; + foreach (var vent in ShipStatus.Instance.AllVents) { - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); + if (!pc.CanUseVent()) + ++vents; + } + List AllPlayers = []; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + } + int maxVents = Math.Min(vents, AllPlayers.Count); + int blockedVents = 0; + writer.WritePacked(maxVents); + foreach (var vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent()) { - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = 0; - foreach (var vent in ShipStatus.Instance.AllVents) - { - if (!pc.CanUseVent()) - ++vents; - } - List AllPlayers = []; - foreach (var playerInfo in GameData.Instance.AllPlayers) - { - if (playerInfo != null && !playerInfo.Disconnected) - AllPlayers.Add(playerInfo); - } - int maxVents = Math.Min(vents, AllPlayers.Count); - int blockedVents = 0; - writer.WritePacked(maxVents); - foreach (var vent in pc.GetVentsFromClosest()) - { - if (!pc.CanUseVent()) - { - writer.Write(AllPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; - } - if (blockedVents >= maxVents) - break; - } - writer.WritePacked(__instance.PlayersInsideVents.Count); - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) - { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); - } - writer.EndMessage(); + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; } - writer.EndMessage(); + if (blockedVents >= maxVents) + break; + } + writer.WritePacked(__instance.PlayersInsideVents.Count); + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); } writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); } - else + writer.EndMessage(); + } + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } + private static void RpcSerializeVent(this PlayerControl pc, VentilationSystem __instance) + { + MessageWriter writer = MessageWriter.Get(SendOption.None); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + { + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); { - MessageWriter writer = MessageWriter.Get(SendOption.None); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.GetClientId()); + writer.StartMessage((byte)SystemTypes.Ventilation); { - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - { - writer.StartMessage((byte)SystemTypes.Ventilation); - { - __instance.Serialize(writer, false); - } - writer.EndMessage(); - } - writer.EndMessage(); + __instance.Serialize(writer, false); } writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); } + writer.EndMessage(); } + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); } } From 7cac798b5669221e810205b69fe6f7903793b196 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 17:18:54 +0800 Subject: [PATCH 388/778] Add comments --- Modules/ExtendedPlayerControl.cs | 4 ++++ Patches/PlayerControlPatch.cs | 4 +++- Patches/VentSystemPatch.cs | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index c1cc52282d..f828ae6252 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -418,6 +418,10 @@ public static List GetVentsFromClosest(this PlayerControl player) return vents; } + /// + /// Update vent interaction if player again can use vent + /// Or vice versa if he cannot use it + /// public static void RpcSetVentInteraction(this PlayerControl player) { VentSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast(), player); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b565bf97c3..e9f9a98840 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1555,7 +1555,9 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) { _ = new LateTask(() => __instance?.RpcBootFromVent(id), 0.5f, "Prevent Enter Vents"); } - //return false; + // Returning false causes errors in the logs + // I don’t yet know how to patch the IEnumerator function in Harmony, but need to send false in a certain place + return false; } playerRoleClass?.OnCoEnterVent(__instance, id); diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 94d0428836..bbf7e878bb 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -50,7 +50,7 @@ public static bool BlockVentInteraction(this PlayerControl pc) { //foreach (var vent in ShipStatus.Instance.AllVents) //{ - // if (!(pc.Data.Role.Role == RoleTypes.Engineer || pc.Data.Role.IsImpostor)) + // if () // return true; //} @@ -73,6 +73,9 @@ public static void SerializeV2(VentilationSystem __instance, PlayerControl playe } } } + /// + /// Block specifics vent use or all vents + /// private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __instance) { MessageWriter writer = MessageWriter.Get(SendOption.None); From 1f08cf9a4cd1df5b34a15eefb131af2b25ee7cee Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 17:22:13 +0800 Subject: [PATCH 389/778] Add again --- Patches/VentSystemPatch.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index bbf7e878bb..0d0f1e2e36 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -44,6 +44,9 @@ public static void Postfix(VentilationSystem __instance) } } } + /// + /// Check blocking vents + /// public static bool BlockVentInteraction(this PlayerControl pc) { if (!pc.AmOwner && !pc.IsModClient() && !pc.Data.IsDead && !pc.CanUseVent()) @@ -74,7 +77,7 @@ public static void SerializeV2(VentilationSystem __instance, PlayerControl playe } } /// - /// Block specifics vent use or all vents + /// Send rpc for block specifics vent use or all vents /// private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __instance) { From f28f0caef92a1f18698a8f3f63b11b0aadc07fb3 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 17:29:42 +0800 Subject: [PATCH 390/778] Hmm --- Patches/VentSystemPatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 0d0f1e2e36..3e8c3e3a86 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using Hazel; +using Il2CppSystem.Linq.Expressions; using System; namespace TOHE.Patches; @@ -77,7 +78,7 @@ public static void SerializeV2(VentilationSystem __instance, PlayerControl playe } } /// - /// Send rpc for block specifics vent use or all vents + /// Send rpc for blocking specifics vent use or all vents /// private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __instance) { From 3649002e53115a3ee27cfe1bbd0b4d7face73498 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 17:30:23 +0800 Subject: [PATCH 391/778] What --- Patches/VentSystemPatch.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 3e8c3e3a86..dd5b3cd52d 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -1,6 +1,4 @@ -using AmongUs.GameOptions; -using Hazel; -using Il2CppSystem.Linq.Expressions; +using Hazel; using System; namespace TOHE.Patches; From c57a3bf2337efe48c2f3c716358a55528d467d7a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 17:44:49 +0800 Subject: [PATCH 392/778] Fix Doomsayer ImpostorVision not used --- Roles/Neutral/Doomsayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Roles/Neutral/Doomsayer.cs b/Roles/Neutral/Doomsayer.cs index 8367872739..3fb16a208f 100644 --- a/Roles/Neutral/Doomsayer.cs +++ b/Roles/Neutral/Doomsayer.cs @@ -4,6 +4,7 @@ using static TOHE.Translator; using TOHE.Roles.Core; using InnerNet; +using AmongUs.GameOptions; namespace TOHE.Roles.Neutral; @@ -89,6 +90,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) byte DoomsayerId = reader.ReadByte(); GuessingToWin[DoomsayerId]++; } + public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(ImpostorVision.GetBool()); private (int, int) GuessedPlayerCount(byte doomsayerId) { int GuessesToWin = GuessingToWin[doomsayerId], AmountOfGuessesToWin = DoomsayerAmountOfGuessesToWin.GetInt(); From 24c66529541fa6adced87a6ff81ba0c9f372abd4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 24 Aug 2024 22:31:04 +0800 Subject: [PATCH 393/778] Role Map --- Modules/ExtendedPlayerControl.cs | 84 ++++++++++++++++--------- Patches/HudPatch.cs | 3 +- Patches/IntroPatch.cs | 102 +++++++++++++++++-------------- Patches/onGameStartedPatch.cs | 70 ++++++++++++++++++--- Roles/Core/CustomRoleManager.cs | 2 + 5 files changed, 176 insertions(+), 85 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 780cf5c2fb..959a4e7aeb 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -13,7 +13,7 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static UnityEngine.ParticleSystem.PlaybackState; +using static TOHE.SelectRolesPatch; namespace TOHE; @@ -257,15 +257,15 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, /// The type to change into /// If the player should be desynced from impostor teammates /// Will only function if is active and makes it so you can't kill them, neither can they kill you - public static void RpcRevive(this PlayerControl player, RoleTypes roleTypes, bool IsDesyncImpostor = false, List FellowImps = null) + public static void RpcRevive(this PlayerControl player) { - if (player.Data.IsDead == false || roleTypes is RoleTypes.GuardianAngel or RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) + if (player.Data.IsDead == false) { - Logger.Warn($"Invalid Revive for {player.GetRealName()} of roletype: {roleTypes} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); + Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); return; } - player.RpcChangeRoleBasis(roleTypes, IsDesyncImpostor, FellowImps); + player.RpcChangeRoleBasis(player.GetCustomRole()); Main.PlayerStates[player.PlayerId].IsDead = false; player.SetKillCooldown(); player.SyncGeneralOptions(); @@ -274,41 +274,67 @@ public static void RpcRevive(this PlayerControl player, RoleTypes roleTypes, boo /// /// Changes the Role Basis of player during the game. /// - /// The type to change into - /// If the player should be desynced from impostor teammates - /// Will only function if is active and makes it so you can't kill them, neither can they kill you - public static void RpcChangeRoleBasis(this PlayerControl player, RoleTypes roleTypes, bool IsDesyncImpostor = false, List FellowImps = null) + /// The custom role to change into + public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole) { + var playerRole = player.GetCustomRole(); if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; - FellowImps ??= []; - if (IsDesyncImpostor && roleTypes is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) + var playerId = player.PlayerId; + // When player change desync role to desync role + // Or player change normal role to normal role + if ((playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) || (!playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole())) { + RpcSetRoleReplacer.RoleMap[(playerId, playerId)] = (newCustomRole.GetRoleTypes(), newCustomRole); + } + // When player change desync role to normal role + else if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) + { + var newRoleType = newCustomRole.GetRoleTypes(); foreach (var seer in Main.AllPlayerControls) { - if (seer.PlayerId == player.PlayerId) continue; - RoleTypes Typa = RoleTypes.Scientist; - RoleTypes Typatwo = RoleTypes.Scientist; - - if (seer.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) Typa = RoleTypes.Noisemaker; - else if (FellowImps.Contains(seer) && seer.HasKillButton()) + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); + } + player.RpcSetRole(newRoleType, true); + } + // When player change normal role to desync role + else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) + { + var isModded = player.OwnedByHost() || player.IsModClient(); + foreach (var target in Main.AllPlayerControls) + { + var isSelf = target.PlayerId == player.PlayerId; + if (isSelf) { - Typa = seer.GetCustomRole().GetVNRole().GetRoleTypes(); - Typatwo = seer.GetCustomRole().GetVNRole().GetRoleTypes(); + if (isModded) + { + RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Crewmate, newCustomRole); + player.RpcSetRoleDesync(RoleTypes.Crewmate, player.GetClientId()); + } + else + { + RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Impostor, newCustomRole); + player.RpcSetRoleDesync(RoleTypes.Impostor, player.GetClientId()); + } } - else if (!seer.HasKillButton()) + else { - Typatwo = roleTypes; + var targetRole = target.GetCustomRole(); + if (targetRole is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) + { + RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Noisemaker, targetRole); + target.RpcSetRoleDesync(RoleTypes.Noisemaker, player.GetClientId()); + } + else + { + RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Scientist, targetRole); + target.RpcSetRoleDesync(RoleTypes.Scientist, player.GetClientId()); + } + + RpcSetRoleReplacer.RoleMap[(target.PlayerId, playerId)] = (RoleTypes.Scientist, playerRole); + player.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); } - - seer.RpcSetRoleDesync(Typa, player.GetClientId()); - player.RpcSetRoleDesync(Typatwo, seer.GetClientId()); } - player.RpcSetRoleDesync(roleTypes, player.GetClientId()); - } - else - { - player.RpcSetRole(roleTypes, true); } } diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 62b85d144c..d25eb7e1aa 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -124,8 +124,7 @@ public static void Postfix(HudManager __instance) player.Data.Role.CanVent = CanUseVent; // Sometimes sabotage button was visible for non-host modded clients - if (!AmongUsClient.Instance.AmHost) - __instance.SabotageButton.ToggleVisible(player.CanUseSabotage()); + __instance.SabotageButton.ToggleVisible(player.CanUseSabotage()); } else { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 5b84913e97..6eab87950c 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -83,6 +83,8 @@ public static void Postfix(IntroCutscene __instance) } }, 0.0001f, "Override Role Text"); + __instance.StartCoroutine(CoLoggerGameInfo().WrapToIl2Cpp()); + // Fixed bug where NotifyRoles works on modded clients during loading and it's name set as double // Run this code only for clients if (!AmongUsClient.Instance.AmHost) @@ -98,51 +100,10 @@ public static void Postfix(IntroCutscene __instance) }, 1f, "Reset Name For Modded Client"); } } -} -[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] -class CoBeginPatch -{ - public static void Prefix(IntroCutscene __instance) - { - //if (RoleBasisChanger.IsChangeInProgress) return; - - if (GameStates.IsNormalGame) - { - foreach (var player in Main.AllPlayerControls) - { - Main.PlayerStates[player.PlayerId].InitTask(player); - } - - GameData.Instance.RecomputeTaskCounts(); - TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - } - - __instance.StartCoroutine(CoLoggerGameInfo().WrapToIl2Cpp()); - - GameStates.InGame = true; - RPC.RpcVersionCheck(); - - // Do not move this code, it should be executed at the very end to prevent a visual bug - Utils.DoNotifyRoles(ForceLoop: true); - - if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) - { - RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null, - }; - if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); - } - } - public static byte[] EncryptDES(byte[] data, string key) + private static byte[] EncryptDES(byte[] data, string key) { using SymmetricAlgorithm desAlg = DES.Create(); - + // Incoming key must be 8 bit or will cause error desAlg.Key = Encoding.UTF8.GetBytes(key); desAlg.IV = Encoding.UTF8.GetBytes(key); @@ -256,6 +217,45 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() Logger.Info(sb.ToString(), "GameInfo", multiLine: true); } } +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] +class CoBeginPatch +{ + public static void Prefix(IntroCutscene __instance) + { + //if (RoleBasisChanger.IsChangeInProgress) return; + + if (GameStates.IsNormalGame) + { + foreach (var player in Main.AllPlayerControls) + { + Main.PlayerStates[player.PlayerId].InitTask(player); + } + + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + } + + GameStates.InGame = true; + RPC.RpcVersionCheck(); + + // Do not move this code, it should be executed at the very end to prevent a visual bug + Utils.DoNotifyRoles(ForceLoop: true); + + if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) + { + RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch + { + 0 => new RandomSpawn.SkeldSpawnMap(), + 1 => new RandomSpawn.MiraHQSpawnMap(), + 2 => new RandomSpawn.PolusSpawnMap(), + 3 => new RandomSpawn.DleksSpawnMap(), + 5 => new RandomSpawn.FungleSpawnMap(), + _ => null, + }; + if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); + } + } +} [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] class BeginCrewmatePatch { @@ -610,10 +610,20 @@ public static void Postfix() } - _ = new LateTask(() => { - foreach (var DYpc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && x.GetCustomRole().IsDesyncRole())) + _ = new LateTask(() => { + + foreach (var desyncPC in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && x.GetCustomRole().IsDesyncRole())) { - DYpc.RpcChangeRoleBasis(DYpc.GetCustomRole().GetRoleTypes(), true); + desyncPC.RpcChangeRoleBasis(desyncPC.GetCustomRole()); + } + + foreach (var seer1 in Main.AllPlayerControls) + { + foreach (var target1 in Main.AllPlayerControls) + { + RpcSetRoleReplacer.RoleMap.TryGetValue((seer1.PlayerId, target1.PlayerId), out var map); + Logger.Info($"seer {seer1?.Data?.PlayerName}-{seer1.PlayerId}, target {target1?.Data?.PlayerName}-{target1.PlayerId} => {map.roleType}, {map.customRole}", "Now Role Map"); + } } }, 0.1f, "Assign Impostor desync roles for crewmates"); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 969c8e9217..65de5e61db 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -7,11 +7,10 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; -using static TOHE.Roles.Core.AssignManager.RoleAssign; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; -using System.Linq; +using static TOHE.Roles.Core.AssignManager.RoleAssign; namespace TOHE; @@ -389,7 +388,6 @@ private static void SetRolesAfterSelect() { if (target == null) continue; - foreach (var seer in Main.AllPlayerControls) { CustomRoles ResultRole = RoleAssign.RoleResult[seer]; @@ -412,8 +410,6 @@ private static void SetRolesAfterSelect() Logger.Warn($"Set Role for Target: {target.GetRealName(clientData: true)}|{role} Seer: {seer.GetRealName(clientData: true)}|{ResultRole} of RoleType: {typa}", "SetStoragedPlayerData"); RpcSetRoleReplacer.StoragedPlayerRoleData[(target, seer)] = typa; - - } // Logger.Warn($"Set original role type => {pc.GetRealName()} : {role} => {role.GetRoleTypes()}", "Override Role Select"); @@ -641,6 +637,8 @@ static bool CheckSeerPassive(CustomRoles role) break; } + CreateRoleMap(); + GameOptionsSender.AllSenders.Clear(); foreach (var pc in Main.AllPlayerControls) { @@ -662,7 +660,6 @@ static bool CheckSeerPassive(CustomRoles role) Utils.ThrowException(ex); } } - private static void AssignCustomRole(CustomRoles role, PlayerControl player) { if (player == null) return; @@ -670,6 +667,60 @@ private static void AssignCustomRole(CustomRoles role, PlayerControl player) Main.PlayerStates[player.PlayerId].SetMainRole(role); Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role}", "AssignRoles"); } + private static void CreateRoleMap() + { + foreach (var seer in Main.AllPlayerControls) + { + var isModded = seer.OwnedByHost() || seer.IsModClient(); + var seerRole = seer.GetCustomRole(); + foreach (var target in Main.AllPlayerControls) + { + var isSelf = seer.PlayerId == target.PlayerId; + var targetRole = target.GetCustomRole(); + if (targetRole.IsDesyncRole()) + { + if (isSelf) + { + if (isModded) + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Crewmate, seerRole); + else + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Impostor, seerRole); + } + else + { + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Scientist, targetRole); + } + } + else + { + if (seerRole.IsDesyncRole()) + { + if (target.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) + { + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Noisemaker, targetRole); + } + else + { + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Scientist, targetRole); + } + } + else + { + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (targetRole.GetRoleTypes(), targetRole); + } + } + } + } + + foreach (var seer1 in Main.AllPlayerControls) + { + foreach (var target1 in Main.AllPlayerControls) + { + RpcSetRoleReplacer.RoleMap.TryGetValue((seer1.PlayerId, target1.PlayerId), out var map); + Logger.Info($"seer {seer1?.Data?.PlayerName}-{seer1.PlayerId}, target {target1?.Data?.PlayerName}-{target1.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); + } + } + } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] public static class RpcSetRoleReplacer @@ -677,6 +728,7 @@ public static class RpcSetRoleReplacer public static bool doReplace = false; public static Dictionary senders; public static Dictionary<(PlayerControl target, PlayerControl seer), RoleTypes> StoragedPlayerRoleData = []; + public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; public static bool Prefix() { return !doReplace; @@ -730,8 +782,10 @@ private static void SetSelfRoles() stream.StartMessage(2); stream.WritePacked(pc.NetId); stream.Write((byte)RpcCalls.SetRole); - stream.Write((ushort)roleType); - stream.Write(true); //canOverrideRole + { + stream.Write((ushort)roleType); + stream.Write(true); //canOverrideRole + } stream.EndMessage(); Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index ac222f7e22..ab3fbc39dc 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -9,6 +9,8 @@ using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; using TOHE.Roles.Vanilla; +using static TOHE.SelectRolesPatch; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE.Roles.Core; From bf865ea932d267f39a5ba7eb76d00b651029e527 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:20:32 +0800 Subject: [PATCH 394/778] Also Change Utils.Endgame --- Modules/RPC.cs | 27 +++++++++++++++++++++++---- Modules/Utils.cs | 36 ++++++++++++++++++++++++++++-------- Patches/ControlPatch.cs | 6 +++++- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 5e27053cbb..e15f9e0234 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -228,12 +228,21 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c if (AmongUsClient.Instance.AmHost) { - _ = new LateTask(() => + if (GameStates.IsInGame) { CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); GameManager.Instance.LogicFlow.CheckEndCriteria(); RPC.ForceEndGame(CustomWinner.Error); - }, 5.5f, "RPC Anti-Black End Game As Critical Error"); + } + else + { + _ = new LateTask(() => + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + RPC.ForceEndGame(CustomWinner.Error); + }, 5.5f, "RPC Anti-Black End Game As Critical Error"); + } } } else @@ -245,11 +254,21 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c if (AmongUsClient.Instance.AmHost && __instance != null) { - _ = new LateTask(() => + if (GameStates.IsInGame) { AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutKicked"), __instance?.Data?.PlayerName)); - }, 5.5f, "RPC Anti-Black Ignored Kick Player"); + } + else + { + _ = new LateTask(() => + { + AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); + Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutKicked"), __instance?.Data?.PlayerName)); + }, 5.5f, "RPC Anti-Black Kicked As Critical Error"); + } + + ChatUpdatePatch.DoBlockChat = false; } } } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 3fe71e110b..2d3c40ec4b 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -45,19 +45,36 @@ public static void ErrorEnd(string text) { Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame")); }, 3f, "Anti-Black Msg SendInGame Error During Loading"); - - _ = new LateTask(() => + + if (GameStates.IsShip || !GameStates.IsLobby) + { + _ = new LateTask(() => + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + RPC.ForceEndGame(CustomWinner.Error); + }, 5.5f, "Anti-Black End Game As Critical Error"); + } + else if (GameStartManager.Instance != null) + { + GameStartManager.Instance.ResetStartState(); + AmongUsClient.Instance.RemoveUnownedObjects(); + Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame")); + } + else { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - RPC.ForceEndGame(CustomWinner.Error); - }, 5.5f, "Anti-Black End Game As Critical Error"); + Logger.SendInGame("Host in a unknow antiblack bugged state."); + Logger.Fatal($"Host in a unknow antiblack bugged state.", "Anti-black"); + } } else { MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AntiBlackout, SendOption.Reliable); writer.Write(text); writer.EndMessage(); + + Logger.Fatal($"Error: {text} - I'm triggering critical error", "Anti-black"); + if (Options.EndWhenPlayerBug.GetBool()) { _ = new LateTask(() => @@ -74,8 +91,11 @@ public static void ErrorEnd(string text) _ = new LateTask(() => { - AmongUsClient.Instance.ExitGame(DisconnectReasons.Custom); - Logger.Fatal($"Error: {text} - Disconnected from the game due critical error", "Anti-black"); + if (AmongUsClient.Instance.AmConnected) + { + AmongUsClient.Instance.ExitGame(DisconnectReasons.Custom); + Logger.Fatal($"Error: {text} - Disconnected from the game due critical error", "Anti-black"); + } }, 8f, "Anti-Black Exit Game Due Critical Error"); } } diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index f445d0d18c..776098e868 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -185,7 +185,11 @@ public static void Postfix(/*ControllerManager __instance*/) { HudManager.Instance.Chat.SetVisible(true); } - + + if (GetKeysDown(KeyCode.E, KeyCode.F, KeyCode.LeftControl)) + { + Utils.ErrorEnd("Test AntiBlackout"); + } // Get Position if (Input.GetKeyDown(KeyCode.P) && PlayerControl.LocalPlayer != null) { From 10cad43f629748a4ca0d233dc20768544718e2b5 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:51:13 +0800 Subject: [PATCH 395/778] Add & Use IsCoStartGame --- Modules/GameState.cs | 3 ++- Modules/RPC.cs | 6 +++--- Modules/Utils.cs | 2 +- Patches/GameStartManagerPatch.cs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 3bd002680f..f59534b33a 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -423,7 +423,8 @@ public static class GameStates public static bool DleksIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Dleks; public static bool AirshipIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Airship; public static bool FungleIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Fungle; - public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Joined || LobbyBehaviour.Instance != null; + public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Joined; + public static bool IsCoStartGame => !InGame && !DestroyableSingleton.InstanceExists; public static bool IsInGame => InGame; public static bool IsEnded => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Ended; public static bool IsNotJoined => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.NotJoined; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index e15f9e0234..e06bc6e747 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -213,7 +213,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.AntiBlackout: Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance.PlayerId}): Error: {reader.ReadString()} - end the game according to the setting", "Anti-black"); - if (GameStates.IsShip || !GameStates.IsLobby) + if (GameStates.IsShip || !GameStates.IsLobby || GameStates.IsCoStartGame) { //CoStartGame is running, we are fucked. ChatUpdatePatch.DoBlockChat = true; @@ -228,7 +228,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c if (AmongUsClient.Instance.AmHost) { - if (GameStates.IsInGame) + if (GameStates.IsInGame && !GameStates.IsCoStartGame) { CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); GameManager.Instance.LogicFlow.CheckEndCriteria(); @@ -254,7 +254,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c if (AmongUsClient.Instance.AmHost && __instance != null) { - if (GameStates.IsInGame) + if (GameStates.IsInGame && !GameStates.IsCoStartGame) { AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutKicked"), __instance?.Data?.PlayerName)); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index b19903fa95..35d61e93de 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -46,7 +46,7 @@ public static void ErrorEnd(string text) Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame")); }, 3f, "Anti-Black Msg SendInGame Error During Loading"); - if (GameStates.IsShip || !GameStates.IsLobby) + if (GameStates.IsShip || !GameStates.IsLobby || GameStates.IsCoStartGame) { _ = new LateTask(() => { diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs index fbd54754b7..ffa96daeb8 100644 --- a/Patches/GameStartManagerPatch.cs +++ b/Patches/GameStartManagerPatch.cs @@ -124,7 +124,7 @@ public static class GameStartManagerUpdatePatch private static int minPlayer; public static void Prefix(GameStartManager __instance) { - if (__instance == null) return; + if (__instance == null || LobbyBehaviour.Instance == null) return; minWait = Options.MinWaitAutoStart.GetFloat(); maxWait = Options.MaxWaitAutoStart.GetFloat(); minPlayer = Options.PlayerAutoStart.GetInt(); From f540d5cc2aec7fd52ae038efb04cd119ca230a86 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:53:51 +0800 Subject: [PATCH 396/778] Use GameId instead of GameStartManager --- Patches/DiscordPatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Patches/DiscordPatch.cs b/Patches/DiscordPatch.cs index 37fec8ab93..3719435fae 100644 --- a/Patches/DiscordPatch.cs +++ b/Patches/DiscordPatch.cs @@ -1,5 +1,6 @@ using AmongUs.Data; using Discord; +using InnerNet; using System; namespace TOHE.Patches @@ -26,7 +27,7 @@ public static void Prefix([HarmonyArgument(0)] Activity activity) int maxSize = GameOptionsManager.Instance.CurrentGameOptions.MaxPlayers; if (GameStates.IsLobby) { - lobbycode = GameStartManager.Instance.GameRoomNameCode.text; + lobbycode = GameCode.IntToGameName(AmongUsClient.Instance.GameId); region = Utils.GetRegionName(); } From 81400a15ad6f5abe147a93d76b9873c7900690ec Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 03:00:01 +0800 Subject: [PATCH 397/778] Fix role assign --- Modules/ExtendedPlayerControl.cs | 14 +-- Patches/IntroPatch.cs | 20 +--- Patches/PlayerControlPatch.cs | 2 - Patches/PlayerJoinAndLeftPatch.cs | 3 +- Patches/onGameStartedPatch.cs | 156 +++++++++++------------------- 5 files changed, 68 insertions(+), 127 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 959a4e7aeb..35c03ccc3a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -285,7 +285,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new // Or player change normal role to normal role if ((playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) || (!playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole())) { - RpcSetRoleReplacer.RoleMap[(playerId, playerId)] = (newCustomRole.GetRoleTypes(), newCustomRole); + RpcSetRoleReplacer.RoleMap[(player, player)] = (newCustomRole.GetRoleTypes(), newCustomRole); } // When player change desync role to normal role else if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) @@ -293,7 +293,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var newRoleType = newCustomRole.GetRoleTypes(); foreach (var seer in Main.AllPlayerControls) { - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); + RpcSetRoleReplacer.RoleMap[(seer, player)] = (newRoleType, newCustomRole); } player.RpcSetRole(newRoleType, true); } @@ -308,12 +308,12 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new { if (isModded) { - RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Crewmate, newCustomRole); + RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Crewmate, newCustomRole); player.RpcSetRoleDesync(RoleTypes.Crewmate, player.GetClientId()); } else { - RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Impostor, newCustomRole); + RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Impostor, newCustomRole); player.RpcSetRoleDesync(RoleTypes.Impostor, player.GetClientId()); } } @@ -322,16 +322,16 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var targetRole = target.GetCustomRole(); if (targetRole is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) { - RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Noisemaker, targetRole); + RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Noisemaker, targetRole); target.RpcSetRoleDesync(RoleTypes.Noisemaker, player.GetClientId()); } else { - RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (RoleTypes.Scientist, targetRole); + RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Scientist, targetRole); target.RpcSetRoleDesync(RoleTypes.Scientist, player.GetClientId()); } - RpcSetRoleReplacer.RoleMap[(target.PlayerId, playerId)] = (RoleTypes.Scientist, playerRole); + RpcSetRoleReplacer.RoleMap[(target, player)] = (RoleTypes.Scientist, playerRole); player.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); } } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 6eab87950c..676acdd7c4 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -123,7 +123,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() sb.Append("------------Player Names------------\n"); foreach (var pc in allPlayerControlsArray) { - sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", "")})\n"); + sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text} ({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", string.Empty)})\n"); pc.cosmetics.nameText.text = pc.name; } @@ -609,24 +609,6 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } - - _ = new LateTask(() => { - - foreach (var desyncPC in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsCrewmate() && x.GetCustomRole().IsDesyncRole())) - { - desyncPC.RpcChangeRoleBasis(desyncPC.GetCustomRole()); - } - - foreach (var seer1 in Main.AllPlayerControls) - { - foreach (var target1 in Main.AllPlayerControls) - { - RpcSetRoleReplacer.RoleMap.TryGetValue((seer1.PlayerId, target1.PlayerId), out var map); - Logger.Info($"seer {seer1?.Data?.PlayerName}-{seer1.PlayerId}, target {target1?.Data?.PlayerName}-{target1.PlayerId} => {map.roleType}, {map.customRole}", "Now Role Map"); - } - } - }, 0.1f, "Assign Impostor desync roles for crewmates"); - if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 1195d74630..244bb7bd8a 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1961,8 +1961,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref Ro try { - - if (__runOriginal) { Logger.Info($" {__instance.GetRealName()} => {roleType}", "PlayerControl.RpcSetRole"); diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index c25802f5d3..93cb984a00 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -316,8 +316,7 @@ static void Prefix([HarmonyArgument(0)] ClientData data) { Logger.Warn($"Assign roles not ended, try remove player {data.Character.PlayerId} from role assign", "OnPlayerLeft"); RoleAssign.RoleResult?.Remove(data.Character); - RpcSetRoleReplacer.senders?.Remove(data.Character.PlayerId); - RpcSetRoleReplacer.StoragedPlayerRoleData = RpcSetRoleReplacer.StoragedPlayerRoleData?.Where(x => x.Key.target != data.Character && x.Key.seer != data.Character).ToDictionary(x => x.Key, x => x.Value); + RpcSetRoleReplacer.Senders?.Remove(data.Character.PlayerId); } if (GameStates.IsNormalGame && GameStates.IsInGame) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 65de5e61db..f4773be3da 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -10,7 +10,6 @@ using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; -using static TOHE.Roles.Core.AssignManager.RoleAssign; namespace TOHE; @@ -377,58 +376,17 @@ private static void SetRolesAfterSelect() //Not in use rn, but is gonna make it able to have neutral players of the same team spawn together //Important to remember that all team players need to have all teamplayers in their lists //gonna make a seperate thing making so that it's a rolebasething in another PR. - Dictionary> DesyncImpTeammates = []; + //Dictionary> DesyncImpTeammates = []; - foreach (var blotnik in Main.AllPlayerControls) - { - RoleAssign.RoleResult[blotnik].GetStaticRoleClass().SetDesyncImpostorBuddies(ref DesyncImpTeammates, blotnik); - } - - foreach (var (target, role) in RoleAssign.RoleResult) - { - if (target == null) continue; - - foreach (var seer in Main.AllPlayerControls) - { - CustomRoles ResultRole = RoleAssign.RoleResult[seer]; - - bool isSelf = seer == target; - RoleTypes typa = role.GetRoleTypes(); - - if (role is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) typa = RoleTypes.Noisemaker; - - //Desynced Imps see others as scientist if they are not a teammate - else if (!isSelf && ResultRole.IsDesyncRole() && !ResultRole.IsCrewmate() && - (!DesyncImpTeammates.TryGetValue(seer, out var teammates) || !teammates.Contains(target))) typa = RoleTypes.Scientist; - - //Other see the desynced-imp target as scientist if they are not a teammate or not an crew - else if (!isSelf && role.IsDesyncRole() && !role.IsCrewmate() && !CheckSeerPassive(ResultRole) && - (!DesyncImpTeammates.TryGetValue(target, out var bracy) || !bracy.Contains(seer))) typa = RoleTypes.Scientist; - - //Crewmates are assigned later - else if (role.IsCrewmate() && role.IsDesyncRole()) typa = RoleTypes.Crewmate; + //foreach (var blotnik in Main.AllPlayerControls) + //{ + // RoleAssign.RoleResult[blotnik].GetStaticRoleClass().SetDesyncImpostorBuddies(ref DesyncImpTeammates, blotnik); + //} - Logger.Warn($"Set Role for Target: {target.GetRealName(clientData: true)}|{role} Seer: {seer.GetRealName(clientData: true)}|{ResultRole} of RoleType: {typa}", "SetStoragedPlayerData"); - RpcSetRoleReplacer.StoragedPlayerRoleData[(target, seer)] = typa; - } - - // Logger.Warn($"Set original role type => {pc.GetRealName()} : {role} => {role.GetRoleTypes()}", "Override Role Select"); - } - static bool CheckSeerPassive(CustomRoles role) - { - return role.GetVNRole() is not CustomRoles.Impostor and not CustomRoles.Shapeshifter and not CustomRoles.Phantom; - } + CreateRoleMap(); // Set RoleType by "RpcSetRole" - RpcSetRoleReplacer.Release(); //Write RpcSetRole for all players - RpcSetRoleReplacer.senders.Do(kvp => kvp.Value.SendMessage()); - - // Delete unwanted objects - RpcSetRoleReplacer.senders = null; - RpcSetRoleReplacer.StoragedPlayerRoleData = null; - - //Main.AssignRolesIsStarted = false; - //Utils.ApplySuffix(); + RpcSetRoleReplacer.Release(); foreach (var pc in Main.AllPlayerControls) { @@ -508,8 +466,6 @@ static bool CheckSeerPassive(CustomRoles role) ExtendedPlayerControl.RpcSetCustomRole(pair.Key, subRole); } - - GhostRoleAssign.Add(); foreach (var pc in Main.AllPlayerControls) @@ -637,8 +593,6 @@ static bool CheckSeerPassive(CustomRoles role) break; } - CreateRoleMap(); - GameOptionsSender.AllSenders.Clear(); foreach (var pc in Main.AllPlayerControls) { @@ -672,41 +626,46 @@ private static void CreateRoleMap() foreach (var seer in Main.AllPlayerControls) { var isModded = seer.OwnedByHost() || seer.IsModClient(); - var seerRole = seer.GetCustomRole(); + var seerRole = RoleAssign.RoleResult[seer]; foreach (var target in Main.AllPlayerControls) { var isSelf = seer.PlayerId == target.PlayerId; - var targetRole = target.GetCustomRole(); + var targetRole = RoleAssign.RoleResult[target]; if (targetRole.IsDesyncRole()) { if (isSelf) { if (isModded) - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Crewmate, seerRole); + { + if (targetRole.GetDYRole() == RoleTypes.Shapeshifter) + RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Shapeshifter, seerRole); + else + RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Crewmate, seerRole); + } else - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Impostor, seerRole); + RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Impostor, seerRole); } else { - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Scientist, targetRole); + RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Scientist, targetRole); } } else { - if (seerRole.IsDesyncRole()) + if (seerRole.IsDesyncRole() && !isModded) { if (target.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) { - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Noisemaker, targetRole); + RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Noisemaker, targetRole); } else { - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (RoleTypes.Scientist, targetRole); + RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Scientist, targetRole); } } else { - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (targetRole.GetRoleTypes(), targetRole); + RpcSetRoleReplacer.RoleMap[(seer, target)] = (targetRole.GetRoleTypes(), targetRole); } } } @@ -716,7 +675,7 @@ private static void CreateRoleMap() { foreach (var target1 in Main.AllPlayerControls) { - RpcSetRoleReplacer.RoleMap.TryGetValue((seer1.PlayerId, target1.PlayerId), out var map); + RpcSetRoleReplacer.RoleMap.TryGetValue((seer1, target1), out var map); Logger.Info($"seer {seer1?.Data?.PlayerName}-{seer1.PlayerId}, target {target1?.Data?.PlayerName}-{target1.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); } } @@ -725,39 +684,54 @@ private static void CreateRoleMap() [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] public static class RpcSetRoleReplacer { - public static bool doReplace = false; - public static Dictionary senders; - public static Dictionary<(PlayerControl target, PlayerControl seer), RoleTypes> StoragedPlayerRoleData = []; - public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; + public static bool BlockSetRole = false; + public static Dictionary Senders = []; + public static Dictionary<(PlayerControl seer, PlayerControl target), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; + public static void Initialize() + { + Senders = []; + RoleMap = []; + BlockSetRole = true; + } public static bool Prefix() { - return !doReplace; + return !BlockSetRole; + } + public static void StartReplace() + { + foreach (var pc in Main.AllPlayerControls) + { + Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) + .StartMessage(pc.GetClientId()); + } } public static void Release() { - foreach (var ((target, seer), roleType) in StoragedPlayerRoleData) + foreach (var ((seer, target), (roleType, _)) in RoleMap) { - if (target == seer) continue; + if (seer == target) continue; if (seer.OwnedByHost()) { target.SetRole(roleType); continue; } - var sender = senders[seer.PlayerId]; + var sender = Senders[seer.PlayerId]; sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) .Write((ushort)roleType) .Write(true) .EndRpc(); //Fix host - if (RoleResult[target].IsImpostor() && RoleResult[seer].IsImpostor() && seer.OwnedByHost()) - DestroyableSingleton.Instance.CanBeKilled = false; + //if (RoleResult[target].IsImpostor() && RoleResult[seer].IsImpostor() && seer.OwnedByHost()) + // DestroyableSingleton.Instance.CanBeKilled = false; } SetSelfRoles(); - doReplace = false; + BlockSetRole = false; + Senders.Do(kvp => kvp.Value.SendMessage()); + EndReplace(); } //Self roles set seperately so that we can trick the game into intro-cutsene via disconnecting everyone temporarily for client. @@ -765,38 +739,38 @@ private static void SetSelfRoles() { foreach (var pc in Main.AllPlayerControls) { - var roleType = StoragedPlayerRoleData[(pc, pc)]; + var roleType = RoleMap[(pc, pc)].roleType; var stream = MessageWriter.Get(SendOption.Reliable); stream.StartMessage(6); stream.Write(AmongUsClient.Instance.GameId); stream.WritePacked(pc.GetClientId()); { - SetDisconnectedMessage(stream, true); + RpcSetDisconnect(stream, true); - if (pc.OwnedByHost()) - { - pc.SetRole(roleType); - } + //if (pc.OwnedByHost()) + //{ + // pc.SetRole(roleType); + //} stream.StartMessage(2); stream.WritePacked(pc.NetId); stream.Write((byte)RpcCalls.SetRole); { stream.Write((ushort)roleType); - stream.Write(true); //canOverrideRole + stream.Write(true); //canOverrideRole } stream.EndMessage(); Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); - SetDisconnectedMessage(stream, false); + RpcSetDisconnect(stream, false); } stream.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(stream); stream.Recycle(); } } - private static void SetDisconnectedMessage(MessageWriter stream, bool disconnected) + private static void RpcSetDisconnect(MessageWriter stream, bool disconnected) { foreach (var pc in Main.AllPlayerControls) { @@ -808,21 +782,9 @@ private static void SetDisconnectedMessage(MessageWriter stream, bool disconnect stream.EndMessage(); } } - public static void Initialize() + private static void EndReplace() { - StoragedPlayerRoleData = []; - doReplace = true; - } - public static void StartReplace() - { - Dictionary senders = []; - foreach (var pc in Main.AllPlayerControls) - { - senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) - .StartMessage(pc.GetClientId()); - } - RpcSetRoleReplacer.senders = senders; - doReplace = true; + Senders = null; } } } From b17cba3b493907c8669c684dac68116655f12753 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 03:12:32 +0800 Subject: [PATCH 398/778] Fix RpcChangeRoleBasis --- Modules/ExtendedPlayerControl.cs | 33 ++++++++++++++------------------ Patches/onGameStartedPatch.cs | 8 -------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 35c03ccc3a..b3d2a6374a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -280,7 +280,6 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var playerRole = player.GetCustomRole(); if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; - var playerId = player.PlayerId; // When player change desync role to desync role // Or player change normal role to normal role if ((playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) || (!playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole())) @@ -303,36 +302,32 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var isModded = player.OwnedByHost() || player.IsModClient(); foreach (var target in Main.AllPlayerControls) { - var isSelf = target.PlayerId == player.PlayerId; + var isSelf = player.PlayerId == target.PlayerId; if (isSelf) { if (isModded) { - RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Crewmate, newCustomRole); - player.RpcSetRoleDesync(RoleTypes.Crewmate, player.GetClientId()); + if (newCustomRole.GetDYRole() == RoleTypes.Shapeshifter) + { + RpcSetRoleReplacer.RoleMap[(player, player)] = (RoleTypes.Shapeshifter, newCustomRole); + player.RpcSetRoleDesync(RoleTypes.Shapeshifter, player.GetClientId()); + } + else + { + RpcSetRoleReplacer.RoleMap[(player, player)] = (RoleTypes.Crewmate, newCustomRole); + player.RpcSetRoleDesync(RoleTypes.Crewmate, player.GetClientId()); + } } else { - RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Impostor, newCustomRole); + RpcSetRoleReplacer.RoleMap[(player, player)] = (RoleTypes.Impostor, newCustomRole); player.RpcSetRoleDesync(RoleTypes.Impostor, player.GetClientId()); } } else { - var targetRole = target.GetCustomRole(); - if (targetRole is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) - { - RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Noisemaker, targetRole); - target.RpcSetRoleDesync(RoleTypes.Noisemaker, player.GetClientId()); - } - else - { - RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Scientist, targetRole); - target.RpcSetRoleDesync(RoleTypes.Scientist, player.GetClientId()); - } - - RpcSetRoleReplacer.RoleMap[(target, player)] = (RoleTypes.Scientist, playerRole); - player.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); + RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Scientist, newCustomRole); + target.RpcSetRoleDesync(RoleTypes.Scientist, player.GetClientId()); } } } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index f4773be3da..e71ad0460b 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -359,16 +359,12 @@ private static void SetRolesAfterSelect() ); } - //Utils.CountAlivePlayers(true); - EAC.LogAllRoles(); Utils.SyncAllSettings(); - return; } Logger.Msg("Is Started", "AssignRoles"); - //Main.AssignRolesIsStarted = true; //Initialization of CustomRpcSender and RpcSetRoleReplacer RpcSetRoleReplacer.StartReplace(); @@ -722,10 +718,6 @@ public static void Release() .Write((ushort)roleType) .Write(true) .EndRpc(); - - //Fix host - //if (RoleResult[target].IsImpostor() && RoleResult[seer].IsImpostor() && seer.OwnedByHost()) - // DestroyableSingleton.Instance.CanBeKilled = false; } SetSelfRoles(); From 240a6db38f7b01de7f22007d16076fa2ab4b7e31 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 03:15:17 +0800 Subject: [PATCH 399/778] Chaange code --- Modules/ExtendedPlayerControl.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index b3d2a6374a..7fd8c6552c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -272,7 +272,7 @@ public static void RpcRevive(this PlayerControl player) } /// - /// Changes the Role Basis of player during the game. + /// Changes the Role Basis of player during the game /// /// The custom role to change into public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole) @@ -280,14 +280,8 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var playerRole = player.GetCustomRole(); if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; - // When player change desync role to desync role - // Or player change normal role to normal role - if ((playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) || (!playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole())) - { - RpcSetRoleReplacer.RoleMap[(player, player)] = (newCustomRole.GetRoleTypes(), newCustomRole); - } // When player change desync role to normal role - else if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) + if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) { var newRoleType = newCustomRole.GetRoleTypes(); foreach (var seer in Main.AllPlayerControls) @@ -331,7 +325,12 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new } } } - + // When player change desync role to desync role + // Or player change normal role to normal role + else + { + RpcSetRoleReplacer.RoleMap[(player, player)] = (newCustomRole.GetRoleTypes(), newCustomRole); + } } public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* bool canOverride,*/ int clientId) { From ee8dc04ffbf32724ff1f9a132971f6ca0580ac38 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 03:30:26 +0800 Subject: [PATCH 400/778] Improve code --- Modules/ExtendedPlayerControl.cs | 15 +++++++-------- Patches/onGameStartedPatch.cs | 16 +++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 7fd8c6552c..9a0090d087 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -293,6 +293,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new // When player change normal role to desync role else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) { + RoleTypes targetRoleType; var isModded = player.OwnedByHost() || player.IsModClient(); foreach (var target in Main.AllPlayerControls) { @@ -303,26 +304,24 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new { if (newCustomRole.GetDYRole() == RoleTypes.Shapeshifter) { - RpcSetRoleReplacer.RoleMap[(player, player)] = (RoleTypes.Shapeshifter, newCustomRole); - player.RpcSetRoleDesync(RoleTypes.Shapeshifter, player.GetClientId()); + targetRoleType = RoleTypes.Shapeshifter; } else { - RpcSetRoleReplacer.RoleMap[(player, player)] = (RoleTypes.Crewmate, newCustomRole); - player.RpcSetRoleDesync(RoleTypes.Crewmate, player.GetClientId()); + targetRoleType = RoleTypes.Crewmate; } } else { - RpcSetRoleReplacer.RoleMap[(player, player)] = (RoleTypes.Impostor, newCustomRole); - player.RpcSetRoleDesync(RoleTypes.Impostor, player.GetClientId()); + targetRoleType = RoleTypes.Impostor; } } else { - RpcSetRoleReplacer.RoleMap[(player, target)] = (RoleTypes.Scientist, newCustomRole); - target.RpcSetRoleDesync(RoleTypes.Scientist, player.GetClientId()); + targetRoleType = RoleTypes.Scientist; } + RpcSetRoleReplacer.RoleMap[(player, target)] = (targetRoleType, newCustomRole); + player.RpcSetRoleDesync(targetRoleType, target.GetClientId()); } } // When player change desync role to desync role diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index e71ad0460b..62e40f6e68 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -625,6 +625,7 @@ private static void CreateRoleMap() var seerRole = RoleAssign.RoleResult[seer]; foreach (var target in Main.AllPlayerControls) { + RoleTypes targetRoleType; var isSelf = seer.PlayerId == target.PlayerId; var targetRole = RoleAssign.RoleResult[target]; if (targetRole.IsDesyncRole()) @@ -634,16 +635,16 @@ private static void CreateRoleMap() if (isModded) { if (targetRole.GetDYRole() == RoleTypes.Shapeshifter) - RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Shapeshifter, seerRole); + targetRoleType = RoleTypes.Shapeshifter; else - RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Crewmate, seerRole); + targetRoleType = RoleTypes.Crewmate; } else - RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Impostor, seerRole); + targetRoleType = RoleTypes.Impostor; } else { - RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Scientist, targetRole); + targetRoleType = RoleTypes.Scientist; } } else @@ -652,18 +653,19 @@ private static void CreateRoleMap() { if (target.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) { - RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Noisemaker, targetRole); + targetRoleType = RoleTypes.Noisemaker; } else { - RpcSetRoleReplacer.RoleMap[(seer, target)] = (RoleTypes.Scientist, targetRole); + targetRoleType = RoleTypes.Scientist; } } else { - RpcSetRoleReplacer.RoleMap[(seer, target)] = (targetRole.GetRoleTypes(), targetRole); + targetRoleType = targetRole.GetRoleTypes(); } } + RpcSetRoleReplacer.RoleMap[(seer, target)] = (targetRoleType, targetRole); } } From ea0c0df22cc5600fdde5c05e8812f265c3295e2d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 03:42:31 +0800 Subject: [PATCH 401/778] Move some rpc --- Modules/ExtendedPlayerControl.cs | 509 +++++++++++++++---------------- 1 file changed, 251 insertions(+), 258 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 9a0090d087..bf02ac5ba6 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -19,11 +19,6 @@ namespace TOHE; static class ExtendedPlayerControl { - public static void SetRole(this PlayerControl player, RoleTypes role/*, bool canOverride = false*/) - { - player.StartCoroutine(player.CoSetRole(role, true)); - } - public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role) { if (role < CustomRoles.NotAssigned) @@ -54,22 +49,97 @@ public static void RpcSetCustomRole(byte PlayerId, CustomRoles role) AmongUsClient.Instance.FinishRpcImmediately(writer); } } + public static void SetRole(this PlayerControl player, RoleTypes role/*, bool canOverride = false*/) + { + player.StartCoroutine(player.CoSetRole(role, true)); + } + public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* bool canOverride,*/ int clientId) + { + if (player == null) return; + if (AmongUsClient.Instance.ClientId == clientId) + { + player.SetRole(role); + return; + } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); + writer.Write((ushort)role); + writer.Write(true); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + /// + /// Changes the Role Basis of player during the game + /// + /// The custom role to change into + public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole) + { + var playerRole = player.GetCustomRole(); + if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; + // When player change desync role to normal role + if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) + { + var newRoleType = newCustomRole.GetRoleTypes(); + foreach (var seer in Main.AllPlayerControls) + { + RpcSetRoleReplacer.RoleMap[(seer, player)] = (newRoleType, newCustomRole); + } + player.RpcSetRole(newRoleType, true); + } + // When player change normal role to desync role + else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) + { + RoleTypes targetRoleType; + var isModded = player.OwnedByHost() || player.IsModClient(); + foreach (var target in Main.AllPlayerControls) + { + var isSelf = player.PlayerId == target.PlayerId; + if (isSelf) + { + if (isModded) + { + if (newCustomRole.GetDYRole() == RoleTypes.Shapeshifter) + { + targetRoleType = RoleTypes.Shapeshifter; + } + else + { + targetRoleType = RoleTypes.Crewmate; + } + } + else + { + targetRoleType = RoleTypes.Impostor; + } + } + else + { + targetRoleType = RoleTypes.Scientist; + } + RpcSetRoleReplacer.RoleMap[(player, target)] = (targetRoleType, newCustomRole); + player.RpcSetRoleDesync(targetRoleType, target.GetClientId()); + } + } + // When player change desync role to desync role + // Or player change normal role to normal role + else + { + RpcSetRoleReplacer.RoleMap[(player, player)] = (newCustomRole.GetRoleTypes(), newCustomRole); + } + } public static void RpcExile(this PlayerControl player) { RPC.ExileAsync(player); } - public static ClientData GetClient(this PlayerControl player) + public static void RpcExileV2(this PlayerControl player) { - try - { - var client = AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Character.PlayerId == player.PlayerId); - return client; - } - catch + if (player.Is(CustomRoles.Susceptible)) { - return null; + Susceptible.CallEnabledAndChange(player); } + player.Exiled(); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void RpcCastVote(this PlayerControl player, byte suspectIdx) { @@ -114,60 +184,6 @@ public static void RpcClearVoteDelay(this MeetingHud meeting, int clientId) writer.SendMessage(); }, 0.5f, "Clear Vote"); } - public static int GetClientId(this PlayerControl player) - { - if (player == null) return -1; - var client = player.GetClient(); - return client == null ? -1 : client.Id; - } - public static CustomRoles GetCustomRole(this NetworkedPlayerInfo player) - { - return player == null || player.Object == null ? CustomRoles.Crewmate : player.Object.GetCustomRole(); - } - /// - /// Only roles (no add-ons) - /// - public static CustomRoles GetCustomRole(this PlayerControl player) - { - if (player == null) - { - var caller = new System.Diagnostics.StackFrame(1, false); - var callerMethod = caller.GetMethod(); - string callerMethodName = callerMethod.Name; - string callerClassName = callerMethod.DeclaringType.FullName; - Logger.Warn($"{callerClassName}.{callerMethodName} tried to retrieve CustomRole, but the target was null", "GetCustomRole"); - return CustomRoles.Crewmate; - } - return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.MainRole : CustomRoles.Crewmate; - } - - public static List GetCustomSubRoles(this PlayerControl player) - { - if (player == null) - { - var caller = new System.Diagnostics.StackFrame(1, false); - var callerMethod = caller.GetMethod(); - string callerMethodName = callerMethod.Name; - string callerClassName = callerMethod.DeclaringType.FullName; - Logger.Warn($"{callerClassName}.{callerMethodName} tried to get CustomSubRole, but the target was null", "GetCustomRole"); - return [CustomRoles.NotAssigned]; - } - return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.SubRoles : [CustomRoles.NotAssigned]; - } - public static CountTypes GetCountTypes(this PlayerControl player) - { - if (player == null) - { - var caller = new System.Diagnostics.StackFrame(1, false); - var callerMethod = caller.GetMethod(); - string callerMethodName = callerMethod.Name; - string callerClassName = callerMethod.DeclaringType.FullName; - Logger.Warn($"{callerClassName}.{callerMethodName} tried to get CountTypes, but the target was null", "GetCountTypes"); - return CountTypes.None; - } - - return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.countTypes : CountTypes.None; - } public static void RpcSetNameEx(this PlayerControl player, string name) { foreach (var seer in Main.AllPlayerControls) @@ -254,9 +270,6 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, /// /// Revives the player if the given roletype is alive and player is dead. /// - /// The type to change into - /// If the player should be desynced from impostor teammates - /// Will only function if is active and makes it so you can't kill them, neither can they kill you public static void RpcRevive(this PlayerControl player) { if (player.Data.IsDead == false) @@ -270,81 +283,20 @@ public static void RpcRevive(this PlayerControl player) player.SetKillCooldown(); player.SyncGeneralOptions(); } - /// - /// Changes the Role Basis of player during the game + /// ONLY to be used when killer surely may kill the target, please check with killer.RpcCheckAndMurder(target, check: true) for indirect kill. /// - /// The custom role to change into - public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole) - { - var playerRole = player.GetCustomRole(); - if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; - - // When player change desync role to normal role - if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) - { - var newRoleType = newCustomRole.GetRoleTypes(); - foreach (var seer in Main.AllPlayerControls) - { - RpcSetRoleReplacer.RoleMap[(seer, player)] = (newRoleType, newCustomRole); - } - player.RpcSetRole(newRoleType, true); - } - // When player change normal role to desync role - else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) - { - RoleTypes targetRoleType; - var isModded = player.OwnedByHost() || player.IsModClient(); - foreach (var target in Main.AllPlayerControls) - { - var isSelf = player.PlayerId == target.PlayerId; - if (isSelf) - { - if (isModded) - { - if (newCustomRole.GetDYRole() == RoleTypes.Shapeshifter) - { - targetRoleType = RoleTypes.Shapeshifter; - } - else - { - targetRoleType = RoleTypes.Crewmate; - } - } - else - { - targetRoleType = RoleTypes.Impostor; - } - } - else - { - targetRoleType = RoleTypes.Scientist; - } - RpcSetRoleReplacer.RoleMap[(player, target)] = (targetRoleType, newCustomRole); - player.RpcSetRoleDesync(targetRoleType, target.GetClientId()); - } - } - // When player change desync role to desync role - // Or player change normal role to normal role - else - { - RpcSetRoleReplacer.RoleMap[(player, player)] = (newCustomRole.GetRoleTypes(), newCustomRole); - } - } - public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* bool canOverride,*/ int clientId) + public static void RpcMurderPlayer(this PlayerControl killer, PlayerControl target) { - if (player == null) return; - if (AmongUsClient.Instance.ClientId == clientId) + // If Target is Dollmaster or Possessed Player run Dollmasters kill check instead. + if (DollMaster.SwapPlayerInfo(target) != target) { - player.SetRole(role); + DollMaster.CheckMurderAsPossessed(killer, target); return; } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); - writer.Write((ushort)role); - writer.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + killer.RpcMurderPlayer(target, true); + } public static void RpcGuardAndKill(this PlayerControl killer, PlayerControl target = null, bool forObserver = false, bool fromSetKCD = false) { if (!AmongUsClient.Instance.AmHost) @@ -381,17 +333,6 @@ public static void RpcGuardAndKill(this PlayerControl killer, PlayerControl targ if (!fromSetKCD) killer.SetKillTimer(half: true); } - public static void SetKillCooldownV2(this PlayerControl player, float time = -1f) - { - if (player == null) return; - if (!player.CanUseKillButton()) return; - player.SetKillTimer(CD: time); - if (time >= 0f) Main.AllPlayerKillCooldown[player.PlayerId] = time * 2; - else Main.AllPlayerKillCooldown[player.PlayerId] *= 2; - player.SyncSettings(); - player.RpcGuardAndKill(fromSetKCD: true); - player.ResetKillCooldown(); - } public static void SetKillCooldown(this PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) { if (player == null) return; @@ -431,6 +372,17 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, } player.ResetKillCooldown(); } + public static void SetKillCooldownV2(this PlayerControl player, float time = -1f) + { + if (player == null) return; + if (!player.CanUseKillButton()) return; + player.SetKillTimer(CD: time); + if (time >= 0f) Main.AllPlayerKillCooldown[player.PlayerId] = time * 2; + else Main.AllPlayerKillCooldown[player.PlayerId] *= 2; + player.SyncSettings(); + player.RpcGuardAndKill(fromSetKCD: true); + player.ResetKillCooldown(); + } public static void SetKillCooldownV3(this PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) { if (player == null) return; @@ -596,9 +548,159 @@ public static void RpcDesyncUpdateSystem(this PlayerControl target, SystemTypes AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } + public static void RpcTeleportAllPlayers(Vector2 location) + { + foreach (var pc in Main.AllAlivePlayerControls) + { + pc.RpcTeleport(location); + } + } + public static void RpcDesyncTeleport(this PlayerControl player, Vector2 position, PlayerControl seer) + { + if (player == null) return; + var netTransform = player.NetTransform; + var clientId = seer.GetClientId(); + ushort addSid = GameStates.IsLocalGame ? (ushort)4 : (ushort)40; + if (AmongUsClient.Instance.ClientId == clientId) + { + netTransform.SnapTo(position, (ushort)(netTransform.lastSequenceId + addSid)); + return; + } + ushort newSid = (ushort)(netTransform.lastSequenceId + addSid); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(netTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable, clientId); + NetHelpers.WriteVector2(position, writer); + writer.Write(newSid); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public static void RpcTeleport(this PlayerControl player, Vector2 position, bool isRandomSpawn = false, bool sendInfoInLogs = true) + { + if (sendInfoInLogs) + { + Logger.Info($" {player.GetNameWithRole().RemoveHtmlTags()} => {position}", "RpcTeleport"); + Logger.Info($" Player Id: {player.PlayerId}", "RpcTeleport"); + } + + // Don't check player status during random spawn + if (!isRandomSpawn) + { + var cancelTeleport = false; + + if (player.inVent || player.MyPhysics.Animations.IsPlayingEnterVentAnimation()) + { + Logger.Info($"Target: ({player.GetNameWithRole().RemoveHtmlTags()}) in vent", "RpcTeleport"); + cancelTeleport = true; + } + else if (player.onLadder || player.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) + { + Logger.Warn($"Teleporting canceled - Target: ({player.GetNameWithRole().RemoveHtmlTags()}) is in on Ladder", "RpcTeleport"); + cancelTeleport = true; + } + else if (player.inMovingPlat) + { + Logger.Warn($"Teleporting canceled - Target: ({player.GetNameWithRole().RemoveHtmlTags()}) use moving platform (Airship/Fungle)", "RpcTeleport"); + cancelTeleport = true; + } + + if (cancelTeleport) + { + player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("ErrorTeleport"))); + return; + } + } + + var netTransform = player.NetTransform; + + if (AmongUsClient.Instance.AmClient) + { + // +328 because lastSequenceId has delay between the host and the vanilla client + // And this cannot forced teleport the player + netTransform.SnapTo(position, (ushort)(netTransform.lastSequenceId + 328)); + } + + ushort newSid = (ushort)(netTransform.lastSequenceId + 8); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(netTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable); + NetHelpers.WriteVector2(position, messageWriter); + messageWriter.Write(newSid); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + public static void RpcRandomVentTeleport(this PlayerControl player) + { + var vents = ShipStatus.Instance.AllVents; + var vent = vents.RandomElement(); + + Logger.Info($" {vent.transform.position}", "RpcVentTeleportPosition"); + player.RpcTeleport(new Vector2(vent.transform.position.x, vent.transform.position.y + 0.3636f)); + } + public static bool OwnedByHost(this InnerNetObject innerObject) => innerObject.OwnerId == AmongUsClient.Instance.HostId; + public static ClientData GetClient(this PlayerControl player) + { + try + { + var client = AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Character.PlayerId == player.PlayerId); + return client; + } + catch + { + return null; + } + } + public static int GetClientId(this PlayerControl player) + { + if (player == null) return -1; + var client = player.GetClient(); + return client == null ? -1 : client.Id; + } + /// + /// Only roles (no add-ons) + /// + public static CustomRoles GetCustomRole(this NetworkedPlayerInfo player) => player == null || player.Object == null ? CustomRoles.Crewmate : player.Object.GetCustomRole(); + /// + /// Only roles (no add-ons) + /// + public static CustomRoles GetCustomRole(this PlayerControl player) + { + if (player == null) + { + var caller = new System.Diagnostics.StackFrame(1, false); + var callerMethod = caller.GetMethod(); + string callerMethodName = callerMethod.Name; + string callerClassName = callerMethod.DeclaringType.FullName; + Logger.Warn($"{callerClassName}.{callerMethodName} tried to retrieve CustomRole, but the target was null", "GetCustomRole"); + return CustomRoles.Crewmate; + } + return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.MainRole : CustomRoles.Crewmate; + } + + public static List GetCustomSubRoles(this PlayerControl player) + { + if (player == null) + { + var caller = new System.Diagnostics.StackFrame(1, false); + var callerMethod = caller.GetMethod(); + string callerMethodName = callerMethod.Name; + string callerClassName = callerMethod.DeclaringType.FullName; + Logger.Warn($"{callerClassName}.{callerMethodName} tried to get CustomSubRole, but the target was null", "GetCustomRole"); + return [CustomRoles.NotAssigned]; + } + return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.SubRoles : [CustomRoles.NotAssigned]; + } + public static CountTypes GetCountTypes(this PlayerControl player) + { + if (player == null) + { + var caller = new System.Diagnostics.StackFrame(1, false); + var callerMethod = caller.GetMethod(); + string callerMethodName = callerMethod.Name; + string callerClassName = callerMethod.DeclaringType.FullName; + Logger.Warn($"{callerClassName}.{callerMethodName} tried to get CountTypes, but the target was null", "GetCountTypes"); + return CountTypes.None; + } + + return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.countTypes : CountTypes.None; + } public static void MarkDirtySettings(this PlayerControl player) { PlayerGameOptionsSender.SetDirty(player.PlayerId); @@ -847,31 +949,6 @@ CustomRoles.Lovers and not CustomRoles.Infected and not CustomRoles.Contagious; } - public static void RpcExileV2(this PlayerControl player) - { - if (player.Is(CustomRoles.Susceptible)) - { - Susceptible.CallEnabledAndChange(player); - } - player.Exiled(); - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - /// - /// ONLY to be used when killer surely may kill the target, please check with killer.RpcCheckAndMurder(target, check: true) for indirect kill. - /// - public static void RpcMurderPlayer(this PlayerControl killer, PlayerControl target) - { - // If Target is Dollmaster or Possessed Player run Dollmasters kill check instead. - if (DollMaster.SwapPlayerInfo(target) != target) - { - DollMaster.CheckMurderAsPossessed(killer, target); - return; - } - - killer.RpcMurderPlayer(target, true); - } public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, CustomRoles Addon = CustomRoles.NotAssigned, CustomRoles? IsAddon = CustomRoles.NotAssigned) { @@ -1203,90 +1280,6 @@ public static Vector2 GetBlackRoomPosition() _ => throw new NotImplementedException(), }; } - - public static void RpcTeleportAllPlayers(Vector2 location) - { - foreach (var pc in Main.AllAlivePlayerControls) - { - pc.RpcTeleport(location); - } - } - public static void RpcDesyncTeleport(this PlayerControl player, Vector2 position, PlayerControl seer) - { - if (player == null) return; - var netTransform = player.NetTransform; - var clientId = seer.GetClientId(); - ushort addSid = GameStates.IsLocalGame ? (ushort)4 : (ushort)40; - if (AmongUsClient.Instance.ClientId == clientId) - { - netTransform.SnapTo(position, (ushort)(netTransform.lastSequenceId + addSid)); - return; - } - ushort newSid = (ushort)(netTransform.lastSequenceId + addSid); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(netTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable, clientId); - NetHelpers.WriteVector2(position, writer); - writer.Write(newSid); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - public static void RpcTeleport(this PlayerControl player, Vector2 position, bool isRandomSpawn = false, bool sendInfoInLogs = true) - { - if (sendInfoInLogs) - { - Logger.Info($" {player.GetNameWithRole().RemoveHtmlTags()} => {position}", "RpcTeleport"); - Logger.Info($" Player Id: {player.PlayerId}", "RpcTeleport"); - } - - // Don't check player status during random spawn - if (!isRandomSpawn) - { - var cancelTeleport = false; - - if (player.inVent || player.MyPhysics.Animations.IsPlayingEnterVentAnimation()) - { - Logger.Info($"Target: ({player.GetNameWithRole().RemoveHtmlTags()}) in vent", "RpcTeleport"); - cancelTeleport = true; - } - else if (player.onLadder || player.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) - { - Logger.Warn($"Teleporting canceled - Target: ({player.GetNameWithRole().RemoveHtmlTags()}) is in on Ladder", "RpcTeleport"); - cancelTeleport = true; - } - else if (player.inMovingPlat) - { - Logger.Warn($"Teleporting canceled - Target: ({player.GetNameWithRole().RemoveHtmlTags()}) use moving platform (Airship/Fungle)", "RpcTeleport"); - cancelTeleport = true; - } - - if (cancelTeleport) - { - player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("ErrorTeleport"))); - return; - } - } - - var netTransform = player.NetTransform; - - if (AmongUsClient.Instance.AmClient) - { - // +328 because lastSequenceId has delay between the host and the vanilla client - // And this cannot forced teleport the player - netTransform.SnapTo(position, (ushort)(netTransform.lastSequenceId + 328)); - } - - ushort newSid = (ushort)(netTransform.lastSequenceId + 8); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(netTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable); - NetHelpers.WriteVector2(position, messageWriter); - messageWriter.Write(newSid); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } - public static void RpcRandomVentTeleport(this PlayerControl player) - { - var vents = ShipStatus.Instance.AllVents; - var vent = vents.RandomElement(); - - Logger.Info($" {vent.transform.position}", "RpcVentTeleportPosition"); - player.RpcTeleport(new Vector2(vent.transform.position.x, vent.transform.position.y + 0.3636f)); - } public static int GetPlayerVentId(this PlayerControl player) { if (!(ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Ventilation, out var systemType) && From 2309a6181118490afa4d9238189599d075d552e5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 03:56:35 +0800 Subject: [PATCH 402/778] Some changes --- Patches/IntroPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 676acdd7c4..df0d136145 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -220,7 +220,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] class CoBeginPatch { - public static void Prefix(IntroCutscene __instance) + public static void Prefix() { //if (RoleBasisChanger.IsChangeInProgress) return; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 244bb7bd8a..b7a676d815 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -723,7 +723,7 @@ public static bool Prefix(PlayerControl __instance, bool shouldAnimate) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckVanish))] class CheckVanishPatch { - public static bool Prefix(PlayerControl __instance) + public static bool Prefix(/*PlayerControl __instance*/) { return true; } @@ -733,7 +733,7 @@ public static bool Prefix(PlayerControl __instance) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckAppear))] class CheckAppearPatch { - public static bool Prefix(PlayerControl __instance, bool shouldAnimate) + public static bool Prefix(/*PlayerControl __instance, bool shouldAnimate*/) { return true; } @@ -1749,7 +1749,7 @@ public static void Postfix(PlayerControl __instance, ref string playerName) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckName))] class CmdCheckNameVersionCheckPatch { - public static void Postfix(PlayerControl __instance) + public static void Postfix() { RPC.RpcVersionCheck(); } @@ -1955,7 +1955,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol return true; } - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole, bool __runOriginal) + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes roleType, bool __runOriginal) { if (!AmongUsClient.Instance.AmHost || __instance == null) return; @@ -1976,7 +1976,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] ref Ro [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoSetRole))] class PlayerControlLocalSetRolePatch { - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role, [HarmonyArgument(1)] bool canOverrideRole) + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role) { if (!AmongUsClient.Instance.AmHost && GameStates.IsNormalGame && !GameStates.IsModHost) { From 9149d5e1d707bafba38132db5588716b84895af7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 04:03:09 +0800 Subject: [PATCH 403/778] Clear usings --- Modules/AntiBlackout.cs | 1 - Modules/Camouflague.cs | 1 - Modules/CustomRpcSender.cs | 3 --- Modules/EAC.cs | 1 - Modules/ExtendedPlayerControl.cs | 1 - Modules/RoleBasisChanger.cs | 7 +------ Patches/ChatCommandPatch.cs | 1 - Patches/IntroPatch.cs | 1 - Patches/SabotageSystemPatch.cs | 1 - Patches/ShowHostMeetingPatch.cs | 1 - Roles/AddOns/Common/Glow.cs | 1 - Roles/AddOns/Common/Rebirth.cs | 2 -- Roles/AddOns/Common/Spurt.cs | 1 - Roles/AddOns/Common/Statue.cs | 4 +--- Roles/Core/AssignManager/GhostRoleAssign.cs | 3 +-- Roles/Core/CustomRoleManager.cs | 2 -- Roles/Crewmate/Bodyguard.cs | 1 - Roles/Crewmate/SuperStar.cs | 3 +-- Roles/Impostor/Blackmailer.cs | 1 - Roles/Impostor/Crewpostor.cs | 1 - Roles/Impostor/Lightning.cs | 1 - Roles/Neutral/Berserker.cs | 3 +-- Roles/Neutral/Solsticer.cs | 1 - Roles/Neutral/Werewolf.cs | 1 - 24 files changed, 5 insertions(+), 38 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index aef51154d6..6cce6fc6a9 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -4,7 +4,6 @@ using System.Runtime.CompilerServices; using TOHE.Modules; using TOHE.Roles.Core; -using TOHE.Roles.Core.AssignManager; namespace TOHE; diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index c0e5f56e3b..696c01f9e5 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -1,7 +1,6 @@ using AmongUs.Data; using TOHE.Modules; using TOHE.Roles.Impostor; -using TOHE.Roles.Neutral; namespace TOHE; diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 840b95c9a8..95a8173859 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -1,10 +1,7 @@ -using AmongUs.GameOptions; using Hazel; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; using System; -using TOHE.Roles.Core.AssignManager; -using static TOHE.SelectRolesPatch; namespace TOHE; diff --git a/Modules/EAC.cs b/Modules/EAC.cs index b608abf798..1c95af68ac 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -1,7 +1,6 @@ using Hazel; using System; using InnerNet; -using TOHE.Modules; using static TOHE.Translator; namespace TOHE; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index bf02ac5ba6..5b67d1bd87 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; -using MonoMod.Cil; using System; using System.Text; using TOHE.Modules; diff --git a/Modules/RoleBasisChanger.cs b/Modules/RoleBasisChanger.cs index 064df853b7..b6d26fd27e 100644 --- a/Modules/RoleBasisChanger.cs +++ b/Modules/RoleBasisChanger.cs @@ -1,9 +1,4 @@ -using AmongUs.GameOptions; -using Hazel; -using InnerNet; -using UnityEngine; - -namespace TOHE.Modules; +namespace TOHE.Modules; // https://github.com/Rabek009/MoreGamemodes // https://github.com/Gurge44/EndlessHostRoles diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 28e89c7274..2022d46aa9 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1,4 +1,3 @@ -using AmongUs.GameOptions; using Assets.CoreScripts; using Hazel; using System; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index df0d136145..2143499fb1 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -10,7 +10,6 @@ using TOHE.Roles.Core.AssignManager; using TOHE.Roles.Neutral; using UnityEngine; -using static TOHE.SelectRolesPatch; using static TOHE.Translator; namespace TOHE; diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 97004d2afb..d84bde71ad 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -3,7 +3,6 @@ using TOHE.Roles.Core; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using UnityEngine; namespace TOHE; diff --git a/Patches/ShowHostMeetingPatch.cs b/Patches/ShowHostMeetingPatch.cs index e7354026ef..d091ce6e5d 100644 --- a/Patches/ShowHostMeetingPatch.cs +++ b/Patches/ShowHostMeetingPatch.cs @@ -1,5 +1,4 @@ using TMPro; -using TOHE.Roles.Neutral; using UnityEngine; namespace TOHE.Patches; diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index 9a94932da1..20f4159283 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using UnityEngine; using static TOHE.Options; namespace TOHE.Roles.AddOns.Common; diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 1e9d817d88..67b2f6c13b 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -2,8 +2,6 @@ using static TOHE.Utils; using static TOHE.Translator; using TOHE.Modules; -using static UnityEngine.GraphicsBuffer; -using TOHE.Roles.Neutral; namespace TOHE.Roles.AddOns.Common; diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 55ce7ca600..13b4acf7d4 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -1,7 +1,6 @@ using static TOHE.Options; using UnityEngine; using Hazel; -using TOHE.Roles.Neutral; namespace TOHE.Roles.AddOns.Common { diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index f3a75e9349..30ca0dd02e 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -1,6 +1,4 @@ -using UnityEngine; - -namespace TOHE.Roles.AddOns.Common; +namespace TOHE.Roles.AddOns.Common; public class Statue : IAddon { diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index da9c18ea7e..c3728e28f3 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -1,5 +1,4 @@ -using AmongUs.GameOptions; -using Hazel; +using Hazel; using System.Text; namespace TOHE.Roles.Core.AssignManager; diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index ab3fbc39dc..ac222f7e22 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -9,8 +9,6 @@ using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; using TOHE.Roles.Vanilla; -using static TOHE.SelectRolesPatch; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE.Roles.Core; diff --git a/Roles/Crewmate/Bodyguard.cs b/Roles/Crewmate/Bodyguard.cs index 6b5bf90d4e..8fa4c06756 100644 --- a/Roles/Crewmate/Bodyguard.cs +++ b/Roles/Crewmate/Bodyguard.cs @@ -1,5 +1,4 @@ using TOHE.Roles.Core; -using UnityEngine; using static TOHE.Options; namespace TOHE.Roles.Crewmate; diff --git a/Roles/Crewmate/SuperStar.cs b/Roles/Crewmate/SuperStar.cs index 6f5b6fcefa..acf5a17962 100644 --- a/Roles/Crewmate/SuperStar.cs +++ b/Roles/Crewmate/SuperStar.cs @@ -1,5 +1,4 @@ -using UnityEngine; -using static TOHE.Options; +using static TOHE.Options; using static TOHE.Utils; using static TOHE.Translator; diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 6f6f5d088b..465f59db69 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -1,6 +1,5 @@ using AmongUs.GameOptions; using TOHE.Roles.Core; -using TOHE.Roles.Neutral; using static TOHE.MeetingHudStartPatch; using static TOHE.Translator; diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index c52f86e458..ae6b241f98 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -1,5 +1,4 @@ using Hazel; -using UnityEngine; using static TOHE.Options; using AmongUs.GameOptions; diff --git a/Roles/Impostor/Lightning.cs b/Roles/Impostor/Lightning.cs index 8254c6b005..2c29a60a11 100644 --- a/Roles/Impostor/Lightning.cs +++ b/Roles/Impostor/Lightning.cs @@ -1,7 +1,6 @@ using Hazel; using TOHE.Modules; using TOHE.Roles.Neutral; -using UnityEngine; using static TOHE.Options; diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 8d56b7771c..89629ec999 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -1,5 +1,4 @@ -using UnityEngine; -using TOHE.Modules; +using TOHE.Modules; using TOHE.Roles.Impostor; using static TOHE.Options; using static TOHE.Translator; diff --git a/Roles/Neutral/Solsticer.cs b/Roles/Neutral/Solsticer.cs index 101e8b6bab..ee53ffb6cb 100644 --- a/Roles/Neutral/Solsticer.cs +++ b/Roles/Neutral/Solsticer.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; using Hazel; using TOHE.Roles.Core; -using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.MeetingHudStartPatch; diff --git a/Roles/Neutral/Werewolf.cs b/Roles/Neutral/Werewolf.cs index b4233da647..afb21378c5 100644 --- a/Roles/Neutral/Werewolf.cs +++ b/Roles/Neutral/Werewolf.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; using TOHE.Roles.Double; using static TOHE.Options; -using UnityEngine; namespace TOHE.Roles.Neutral; From 417c71b2de87f231414f63ddc157ec82d876f070 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 04:40:23 +0800 Subject: [PATCH 404/778] Fix? --- Patches/onGameStartedPatch.cs | 1 + Roles/Core/AssignManager/RoleAssign.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 62e40f6e68..6082524217 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -608,6 +608,7 @@ private static void SetRolesAfterSelect() { Utils.ErrorEnd("Set Roles After Select"); Utils.ThrowException(ex); + Logger.Error(ex.ToString(), "SetRolesAfterSelect"); } } private static void AssignCustomRole(CustomRoles role, PlayerControl player) diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index 395e3d0b39..63398a9353 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -183,7 +183,7 @@ public static void StartSelect() Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => x.Role.ToString())), "Selected-Crew-Roles"); Logger.Msg("======================================================", "SelectedRoles"); - var AllPlayers = Main.AllAlivePlayerControls.ToList(); + var AllPlayers = Main.AllPlayerControls.ToList(); // Players on the EAC banned list will be assigned as GM when opening rooms if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid())) From f3a862612e2802c3782aaa924adaaf4e7dffd6e5 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 09:50:11 +0200 Subject: [PATCH 405/778] fix --- Modules/AntiBlackout.cs | 55 +++++++++++++++++++++++++---------------- Patches/ExilePatch.cs | 7 +++--- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 6cce6fc6a9..ccb7dc6724 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using TOHE.Modules; using TOHE.Roles.Core; +using static TOHE.SelectRolesPatch; namespace TOHE; @@ -13,6 +14,11 @@ public static class AntiBlackout /// Check num alive Impostors & Crewmates & NeutralKillers ///
public static bool BlackOutIsActive => false; /*!Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut();*/ + + //this does much less drastic things, + //and exist cuz even with better antiblackout protect, some places people still blackout + //(But it is MUCH less than before, and even if they do, after the next meeting they are back to normal 100%) + public static bool LesserBlackOutActive => CheckBlackOut(); public static int ExilePlayerId = -1; /// @@ -74,8 +80,7 @@ public static bool CheckBlackOut() public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { - TempReviveGuardianAngels(); - SetRole(); + TempRevivePlayers(); logger.Info($"SetIsDead is called from {callerMethodName}"); if (IsCached) { @@ -241,42 +246,50 @@ public static void AfterMeetingTasks() Logger.Error($"{error}", "AntiBlackout.AfterMeetingTasks"); } } - private static void TempReviveGuardianAngels() // FUCK IT WE BALL 🗣💯💯 + public static void ResetPlayerMaps() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) + foreach (var ((seer, target), (roletype, _)) in RpcSetRoleReplacer.RoleMap) { - foreach (var reciever in Main.AllPlayerControls) + if (seer.OwnedByHost()) continue; + + var realtype = roletype; + if (seer.Data.IsDead) { - if (reciever.OwnedByHost()) continue; - pc.RpcSetRoleDesync(RoleTypes.Impostor, reciever.GetClientId()); + realtype = seer.HasKillButton() && seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + if (target == seer && target.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel) realtype = RoleTypes.GuardianAngel; } + target.RpcSetRoleDesync(realtype, seer.GetClientId()); } + _ = new LateTask(() => { + foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) // fix not being able to go trough walls + { + seer.Exiled(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(seer.NetId, (byte)RpcCalls.Exiled, SendOption.None, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); } - private static void SetRole() + private static void TempRevivePlayers() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - List list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != ExilePlayerId && x.HasKillButton()).ToList(); + PlayerControl dummyImp = Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId && x.HasKillButton()) + ?? Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId); - foreach (var pc in Main.AllPlayerControls.Where(x => !x.Data.Disconnected)) + foreach (var pc in Main.AllPlayerControls) { - if (pc.PlayerId == PlayerControl.LocalPlayer.PlayerId) continue; - if (pc.IsAlive() && (pc.GetCustomRole().IsDesyncRole())) continue; - - - foreach (var dummy in list) + foreach (var reciever in Main.AllPlayerControls) { - if (pc.GetCustomRole().IsImpostor() && !pc.IsSameTeammate(dummy, out _) && pc.IsAlive()) continue; - dummy.RpcSetRoleDesync(dummy.GetCustomRole().GetRoleTypes(), pc.GetClientId()); - } + if (reciever.OwnedByHost()) continue; + RoleTypes typa = pc == dummyImp ? RoleTypes.Impostor : RoleTypes.Crewmate; - foreach (var dead in Main.AllPlayerControls.Where(x => !x.Data.Disconnected && x.Data.IsDead)) - { - dead.RpcSetRoleDesync(RoleTypes.CrewmateGhost, pc.GetClientId()); + pc.RpcSetRoleDesync(typa, reciever.GetClientId()); } } + ExilePlayerId = -1; } public static void Reset() diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index dab35bff4d..6ebcee3b26 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -51,6 +51,7 @@ public static void Postfix(AirshipExileController __instance) static void WrapUpPostfix(NetworkedPlayerInfo exiled) { if (AntiBlackout.BlackOutIsActive) exiled = AntiBlackout_LastExiled; + AntiBlackout.ResetPlayerMaps(); // Still not springing up in airships if (!GameStates.AirshipIsActive) @@ -76,7 +77,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) var exiledPC = exiled.Object; // Reset player cam for exiled desync impostor - if (exiledPC.HasDesyncRole()) + if (exiledPC.HasDesyncRole() || AntiBlackout.LesserBlackOutActive) { exiledPC?.ResetPlayerCam(1f); } @@ -192,7 +193,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) //Kill off GAS again - foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) + /*foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { foreach (var reciever in Main.AllPlayerControls) { @@ -202,7 +203,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) } //pc.ResetPlayerCam(); //pc.RpcSetRoleDesync(RoleTypes.GuardianAngel, pc.GetClientId()); - } + }*/ }, 0.8f, "AfterMeetingDeathPlayers Task"); } From cda0d862673f8d2e05627bf8711b49804338f792 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 15:57:25 +0800 Subject: [PATCH 406/778] First commit --- Modules/ExtendedPlayerControl.cs | 78 +++++++++++++++++------ Modules/RPC.cs | 6 -- Patches/ExilePatch.cs | 24 ++++--- Patches/PlayerControlPatch.cs | 86 ++++++++++++++------------ Roles/Core/AssignManager/RoleAssign.cs | 2 +- 5 files changed, 120 insertions(+), 76 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5b67d1bd87..d781113f4b 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -127,7 +127,20 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new } public static void RpcExile(this PlayerControl player) { - RPC.ExileAsync(player); + player.Exiled(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public static void RpcExileDesync(this PlayerControl player, PlayerControl seer) + { + var clientId = seer.GetClientId(); + if (AmongUsClient.Instance.ClientId == clientId) + { + player.Exiled(); + return; + } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None, clientId); + AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void RpcExileV2(this PlayerControl player) { @@ -453,31 +466,56 @@ public static void RpcSetSpecificScanner(this PlayerControl target, PlayerContro messageWriter.Write(cnt); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } - - public static void RpcSpecificVanish(this PlayerControl player, PlayerControl seer) + public static void RpcCheckVanishDesync(this PlayerControl player, PlayerControl seer) { - /* - * Unluckily the vanish animation cannot be disabled - * For vanila client seer side, the player must be with Phantom Role behavior, or the rpc will do nothing - */ - if (!AmongUsClient.Instance.AmHost) return; - + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) + { + player.CheckVanish(); + return; + } + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckVanish, SendOption.None, seer.GetClientId()); + messageWriter.Write(0); // not used, lol + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + public static void RpcStartVanishDesync(this PlayerControl player, PlayerControl seer) + { + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) + { + player.SetRoleInvisibility(true, false, true); + return; + } MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartVanish, SendOption.None, seer.GetClientId()); AmongUsClient.Instance.FinishRpcImmediately(msg); } - - public static void RpcSpecificAppear(this PlayerControl player, PlayerControl seer, bool shouldAnimate) + public static void RpcCheckAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) { - /* - * For vanila client seer side, the player must be with Phantom Role behavior, or the rpc will do nothing - */ - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartAppear, SendOption.None, seer.GetClientId()); - msg.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(msg); + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) + { + player.CheckAppear(shouldAnimate); + return; + } + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None, seer.GetClientId()); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + public static void RpcCheckAppear(this PlayerControl player, bool shouldAnimate) + { + player.CheckAppear(shouldAnimate); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + public static void RpcStartAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) + { + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) + { + player.SetRoleInvisibility(false, shouldAnimate, true); + return; + } + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartAppear, SendOption.None, seer.GetClientId()); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } - public static void RpcSpecificMurderPlayer(this PlayerControl killer, PlayerControl target, PlayerControl seer) { if (seer.AmOwner) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 97daad37e9..92ea0416cc 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -820,12 +820,6 @@ public static void ShowPopUp(this PlayerControl pc, string message, string title writer.Write(title); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static void ExileAsync(PlayerControl player) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - player.Exiled(); - } public static void RpcSetFriendCode(string fc) { MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetFriendCode, SendOption.None); diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index dab35bff4d..dc35e9b809 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -12,6 +12,10 @@ class ExileControllerWrapUpPatch [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { + public static void Prefix() + { + AntiBlackout.SetIsDead(); + } public static void Postfix(ExileController __instance) { try @@ -104,12 +108,12 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) player.GetRoleClass()?.OnPlayerExiled(player, exiled); // Check Anti BlackOut - // if (player.GetCustomRole().IsImpostor() - // && !player.IsAlive() // if player is dead impostor - // && AntiBlackout.BlackOutIsActive) // if Anti BlackOut is activated - // { - // player.ResetPlayerCam(1f); - // } + //if (player.GetCustomRole().IsImpostor() + // && !player.IsAlive() // if player is dead impostor + // && AntiBlackout.BlackOutIsActive) // if Anti BlackOut is activated + //{ + // player.ResetPlayerCam(1f); + //} // Check for remove pet player.RpcRemovePet(); @@ -162,7 +166,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled.Object.RpcExileV2(); } - }, 0.8f, "Restore IsDead Task"); + }, 1f, "Restore IsDead Task"); _ = new LateTask(() => { @@ -181,10 +185,10 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) player?.SetRealKiller(player, true); // Reset player cam for dead desync impostor - // if (player.HasDesyncRole()) + //if (player.HasDesyncRole()) //{ // player?.ResetPlayerCam(1f); - // } + //} MurderPlayerPatch.AfterPlayerDeathTasks(player, player, true); }); @@ -204,7 +208,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) //pc.RpcSetRoleDesync(RoleTypes.GuardianAngel, pc.GetClientId()); } - }, 0.8f, "AfterMeetingDeathPlayers Task"); + }, 1.1f, "AfterMeetingDeathPlayers Task"); } //This should happen shortly after the Exile Controller wrap up finished for clients //For Certain Laggy clients 0.8f delay is still not enough. The finish time can differ. diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b7a676d815..41cf424d5e 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -714,18 +714,30 @@ public static bool Prefix(PlayerControl __instance, bool shouldAnimate) return false; } } -/* - * I have no idea how the check vanish is approved by host & server and how to reject it - * Suggest leaving phantom stuffs after 2.1.0 - * - * Called when Phantom press vanish button when visible - */ +// Called when Phantom press vanish button when visible [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckVanish))] class CheckVanishPatch { - public static bool Prefix(/*PlayerControl __instance*/) + public static void Prefix(PlayerControl __instance) { - return true; + Logger.Info($"Player: {__instance.GetRealName()}", "CheckVanish"); + + var phantom = __instance; + + foreach (var target in Main.AllAlivePlayerControls) + { + if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + + // Set Phantom when his start vanish + phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); + // Check vanish again for desync role + phantom.RpcCheckVanishDesync(target); + + _ = new LateTask(() => + { + phantom.RpcExileDesync(target); + }, 1.2f, "Set Phantom invisible", shoudLog: false); + } } } @@ -733,49 +745,45 @@ public static bool Prefix(/*PlayerControl __instance*/) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckAppear))] class CheckAppearPatch { - public static bool Prefix(/*PlayerControl __instance, bool shouldAnimate*/) + public static void Prefix(PlayerControl __instance, bool shouldAnimate) { - return true; + Logger.Info($"Player: {__instance.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); + + var phantom = __instance; + + if (shouldAnimate) + { + foreach (var target in Main.AllAlivePlayerControls) + { + if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + + // Set Phantom when his end vanish + phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); + + _ = new LateTask(() => + { + // Check appear again for desync role + phantom.RpcCheckAppearDesync(true, target); + }, 0.2f, "Check Appear when vanish is over", shoudLog: false); + + _ = new LateTask(() => + { + phantom.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); + }, 2.2f, "Set Scientist when vanish is over", shoudLog: false); + } + } } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetRoleInvisibility))] class SetRoleInvisibilityPatch { - public static readonly Dictionary PhantomIsInvisibility = []; + //public static readonly Dictionary PhantomIsInvisibility = []; public static void Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) { if (!AmongUsClient.Instance.AmHost) return; Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); - - if (GameStates.IsMeeting) return; - - var phantom = __instance; - var randomVent = ShipStatus.Instance.AllVents.RandomElement(); - - foreach (var target in Main.AllAlivePlayerControls) - { - if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - - if (isActive) - { - var randomVentId = randomVent.Id; - var ventPosition = randomVent.transform.position; - - phantom.RpcDesyncTeleport(ventPosition, target); - phantom.MyPhysics.RpcEnterVentDesync(randomVentId, target); - } - else if (!isActive && shouldAnimate) - { - _ = PhantomIsInvisibility.TryGetValue(phantom.PlayerId, out var vent); - phantom.MyPhysics.RpcExitVentDesync(vent.Id, target); - phantom.RpcDesyncTeleport(phantom.GetCustomPosition(), target); - } - } - - if (isActive) PhantomIsInvisibility.Add(phantom.PlayerId, randomVent); - else PhantomIsInvisibility.Remove(phantom.PlayerId); } } diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index 63398a9353..3e98a5edab 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -8,7 +8,7 @@ namespace TOHE.Roles.Core.AssignManager; public class RoleAssign { public static Dictionary SetRoles = []; - public static Dictionary RoleResult; + public static Dictionary RoleResult = []; public static CustomRoles[] AllRoles => [.. RoleResult.Values]; enum RoleAssignType From 495e7660d8b5e88de4011faba8178213c5fd3b5e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 16:00:57 +0800 Subject: [PATCH 407/778] Remove --- Patches/ExilePatch.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index dc35e9b809..008312d072 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -12,10 +12,6 @@ class ExileControllerWrapUpPatch [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { - public static void Prefix() - { - AntiBlackout.SetIsDead(); - } public static void Postfix(ExileController __instance) { try From 10ca33c40d14d81a289ccb4786de03cd5da590cf Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 16:10:23 +0800 Subject: [PATCH 408/778] Fix --- Patches/PlayerControlPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 41cf424d5e..e5a5553fde 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -764,12 +764,12 @@ public static void Prefix(PlayerControl __instance, bool shouldAnimate) { // Check appear again for desync role phantom.RpcCheckAppearDesync(true, target); - }, 0.2f, "Check Appear when vanish is over", shoudLog: false); + }, 0.5f, "Check Appear when vanish is over", shoudLog: false); _ = new LateTask(() => { phantom.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); - }, 2.2f, "Set Scientist when vanish is over", shoudLog: false); + }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); } } } From d9426ec597288ba6539df2e183494eccedf8ce6d Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 10:12:56 +0200 Subject: [PATCH 409/778] fix --- Modules/AntiBlackout.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index ccb7dc6724..212648a96a 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -255,20 +255,28 @@ public static void ResetPlayerMaps() if (seer.OwnedByHost()) continue; var realtype = roletype; - if (seer.Data.IsDead) + if (target.Data.IsDead) { realtype = seer.HasKillButton() && seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; - if (target == seer && target.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel) realtype = RoleTypes.GuardianAngel; } target.RpcSetRoleDesync(realtype, seer.GetClientId()); } _ = new LateTask(() => { - foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) // fix not being able to go trough walls + foreach (var seer in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { - seer.Exiled(); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(seer.NetId, (byte)RpcCalls.Exiled, SendOption.None, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); + seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); + } // idk why, but they simply have to be set afterwards + + foreach (var target in Main.AllPlayerControls) + { + foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) // fix not being able to go trough walls + { + if (seer.OwnedByHost()) continue; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(seer.NetId, (byte)RpcCalls.Exiled, SendOption.None, seer.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } } + }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); } From 13549278fec32353faa8ff269b08af7226e2f53a Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 10:38:17 +0200 Subject: [PATCH 410/778] fix --- Modules/AntiBlackout.cs | 4 ++-- Patches/PlayerControlPatch.cs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 212648a96a..a483a7b148 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -265,14 +265,14 @@ public static void ResetPlayerMaps() foreach (var seer in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); - } // idk why, but they simply have to be set afterwards + } foreach (var target in Main.AllPlayerControls) { foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) // fix not being able to go trough walls { if (seer.OwnedByHost()) continue; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(seer.NetId, (byte)RpcCalls.Exiled, SendOption.None, seer.GetClientId()); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.Exiled, SendOption.None, seer.GetClientId()); AmongUsClient.Instance.FinishRpcImmediately(writer); } } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b7a676d815..07ca4dc510 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -29,6 +29,9 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC Logger.Info("CheckProtect occurs: " + __instance.GetNameWithRole() + "=>" + target.GetNameWithRole(), "CheckProtect"); var angel = __instance; + if (target.Data.IsDead) // bad protect + return false; + if (!angel.GetRoleClass().OnCheckProtect(angel, target)) return false; From e7629a646f9aee22832ebdbb8e485844483fdd96 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 10:40:51 +0200 Subject: [PATCH 411/778] fix --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index a483a7b148..0da62e7080 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -257,7 +257,7 @@ public static void ResetPlayerMaps() var realtype = roletype; if (target.Data.IsDead) { - realtype = seer.HasKillButton() && seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + realtype = seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; } target.RpcSetRoleDesync(realtype, seer.GetClientId()); } From bf6a4c8a457bcaf4736d0c40df8f275705f0cfe1 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 10:41:10 +0200 Subject: [PATCH 412/778] nothing --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 0da62e7080..6400147998 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -265,7 +265,7 @@ public static void ResetPlayerMaps() foreach (var seer in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); - } + } // for some reason has to be done later foreach (var target in Main.AllPlayerControls) { From 2d21c696bac46ae07664ec92611dc397fc9d6303 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 10:41:36 +0200 Subject: [PATCH 413/778] nothing --- Modules/AntiBlackout.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 6400147998..89a3c71772 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -276,8 +276,6 @@ public static void ResetPlayerMaps() AmongUsClient.Instance.FinishRpcImmediately(writer); } } - - }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); } private static void TempRevivePlayers() From c06c116d9cf625abfc327237257a5b74912a55dc Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 16:52:41 +0800 Subject: [PATCH 414/778] Fix Phantom in meeting --- Patches/PlayerControlPatch.cs | 66 ++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e5a5553fde..3d86e5477b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -718,11 +718,13 @@ public static bool Prefix(PlayerControl __instance, bool shouldAnimate) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckVanish))] class CheckVanishPatch { + public static readonly List PhantomIsInvisibility = []; public static void Prefix(PlayerControl __instance) { Logger.Info($"Player: {__instance.GetRealName()}", "CheckVanish"); var phantom = __instance; + PhantomIsInvisibility.Add(phantom); foreach (var target in Main.AllAlivePlayerControls) { @@ -750,27 +752,25 @@ public static void Prefix(PlayerControl __instance, bool shouldAnimate) Logger.Info($"Player: {__instance.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); var phantom = __instance; + CheckVanishPatch.PhantomIsInvisibility.Remove(phantom); - if (shouldAnimate) + foreach (var target in Main.AllAlivePlayerControls) { - foreach (var target in Main.AllAlivePlayerControls) - { - if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - // Set Phantom when his end vanish - phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); + // Set Phantom when his end vanish + phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); - _ = new LateTask(() => - { - // Check appear again for desync role - phantom.RpcCheckAppearDesync(true, target); - }, 0.5f, "Check Appear when vanish is over", shoudLog: false); + _ = new LateTask(() => + { + // Check appear again for desync role + phantom.RpcCheckAppearDesync(true, target); + }, 0.5f, "Check Appear when vanish is over", shoudLog: false); - _ = new LateTask(() => - { - phantom.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); - }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); - } + _ = new LateTask(() => + { + phantom.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); + }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); } } } @@ -778,7 +778,6 @@ public static void Prefix(PlayerControl __instance, bool shouldAnimate) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetRoleInvisibility))] class SetRoleInvisibilityPatch { - //public static readonly Dictionary PhantomIsInvisibility = []; public static void Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) { if (!AmongUsClient.Instance.AmHost) return; @@ -998,6 +997,39 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta Logger.Info($"Player {pc?.Data?.PlayerName}: Id {pc.PlayerId} - is alive: {pc.IsAlive()}", "CheckIsAlive"); } + foreach (var phantom in CheckVanishPatch.PhantomIsInvisibility.ToArray()) + { + foreach (var pc in Main.AllAlivePlayerControls) + { + if (!phantom.IsAlive() || phantom == pc || pc.AmOwner || !pc.HasDesyncRole()) continue; + + _ = new LateTask(() => + { + // Check appear again for desync role + phantom.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); + }, 0.001f, "Set Scientist in meeting", shoudLog: false); + // Set Scientist when his end vanish + + _ = new LateTask(() => + { + // Check appear again for desync role + phantom.RpcSetRoleDesync(RoleTypes.Phantom, pc.GetClientId()); + }, 0.2f, "Set Phantom in meeting", shoudLog: false); + + _ = new LateTask(() => + { + // Check appear again for desync role + phantom.RpcCheckAppearDesync(false, pc); + }, 1.4f, "Check Appear in meeting", shoudLog: false); + + _ = new LateTask(() => + { + phantom.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); + }, 2.3f, "Set Scientist in meeting", shoudLog: false); + } + } + CheckVanishPatch.PhantomIsInvisibility.Clear(); + // Set meeting time MeetingTimeManager.OnReportDeadBody(); From 13b641bb6a668d2e191153175c945cb23ab5e3ff Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 10:52:49 +0200 Subject: [PATCH 415/778] BRUH --- Modules/AntiBlackout.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 89a3c71772..eb75c3ef38 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -267,9 +267,9 @@ public static void ResetPlayerMaps() seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); } // for some reason has to be done later - foreach (var target in Main.AllPlayerControls) + foreach (var target in Main.AllPlayerControls.Where(x => x.Data.IsDead)) { - foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) // fix not being able to go trough walls + foreach (var seer in Main.AllPlayerControls) // fix not being able to go trough walls { if (seer.OwnedByHost()) continue; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.Exiled, SendOption.None, seer.GetClientId()); From 4d397e5d94d3dc83fe2bd5ed8502bb8e91b3170f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 16:54:03 +0800 Subject: [PATCH 416/778] Change --- Patches/PlayerControlPatch.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 3d86e5477b..516a189d87 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1005,27 +1005,23 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta _ = new LateTask(() => { - // Check appear again for desync role phantom.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); }, 0.001f, "Set Scientist in meeting", shoudLog: false); - // Set Scientist when his end vanish _ = new LateTask(() => { - // Check appear again for desync role phantom.RpcSetRoleDesync(RoleTypes.Phantom, pc.GetClientId()); }, 0.2f, "Set Phantom in meeting", shoudLog: false); _ = new LateTask(() => { - // Check appear again for desync role phantom.RpcCheckAppearDesync(false, pc); }, 1.4f, "Check Appear in meeting", shoudLog: false); _ = new LateTask(() => { phantom.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); - }, 2.3f, "Set Scientist in meeting", shoudLog: false); + }, 2.3f, "Set Scientist in meeting after reset", shoudLog: false); } } CheckVanishPatch.PhantomIsInvisibility.Clear(); From 925509db765f84ff9a7995e14b977134999ef226 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:01:12 +0200 Subject: [PATCH 417/778] BRUH --- Modules/AntiBlackout.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index eb75c3ef38..760bd4d2de 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -15,9 +15,7 @@ public static class AntiBlackout /// public static bool BlackOutIsActive => false; /*!Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut();*/ - //this does much less drastic things, - //and exist cuz even with better antiblackout protect, some places people still blackout - //(But it is MUCH less than before, and even if they do, after the next meeting they are back to normal 100%) + //this is simply just called in less places, because antiblackout with role-basis changing is OP public static bool LesserBlackOutActive => CheckBlackOut(); public static int ExilePlayerId = -1; From 9a6a7acc948a350ac1cbbfd6fe6e8790d1814222 Mon Sep 17 00:00:00 2001 From: Ultradragon005 <143632280+Ultradragon005@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:27:49 +0200 Subject: [PATCH 418/778] change --- Patches/ExilePatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 6ebcee3b26..de12b55862 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -51,7 +51,6 @@ public static void Postfix(AirshipExileController __instance) static void WrapUpPostfix(NetworkedPlayerInfo exiled) { if (AntiBlackout.BlackOutIsActive) exiled = AntiBlackout_LastExiled; - AntiBlackout.ResetPlayerMaps(); // Still not springing up in airships if (!GameStates.AirshipIsActive) @@ -65,6 +64,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) bool DecidedWinner = false; if (!AmongUsClient.Instance.AmHost) return; AntiBlackout.RestoreIsDead(doSend: false); + AntiBlackout.ResetPlayerMaps(); List collectorCL = Utils.GetRoleBasesByType()?.ToList(); From ac91a392e82c26f05c6d851bf30500f8d7a18cb1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 17:44:30 +0800 Subject: [PATCH 419/778] Fix phantom again --- Modules/ExtendedPlayerControl.cs | 14 +++++----- Patches/PlayerControlPatch.cs | 36 ++++++++++++++------------ Patches/onGameStartedPatch.cs | 4 ++- Roles/Core/AssignManager/RoleAssign.cs | 4 +-- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index d781113f4b..3da9ce9d6a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -498,13 +498,6 @@ public static void RpcCheckAppearDesync(this PlayerControl player, bool shouldAn messageWriter.Write(shouldAnimate); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } - public static void RpcCheckAppear(this PlayerControl player, bool shouldAnimate) - { - player.CheckAppear(shouldAnimate); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } public static void RpcStartAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) { if (AmongUsClient.Instance.ClientId == seer.GetClientId()) @@ -516,6 +509,13 @@ public static void RpcStartAppearDesync(this PlayerControl player, bool shouldAn messageWriter.Write(shouldAnimate); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } + public static void RpcCheckAppear(this PlayerControl player, bool shouldAnimate) + { + player.CheckAppear(shouldAnimate); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } public static void RpcSpecificMurderPlayer(this PlayerControl killer, PlayerControl target, PlayerControl seer) { if (seer.AmOwner) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 516a189d87..718df0f107 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -737,7 +737,7 @@ public static void Prefix(PlayerControl __instance) _ = new LateTask(() => { - phantom.RpcExileDesync(target); + phantom?.RpcExileDesync(target); }, 1.2f, "Set Phantom invisible", shoudLog: false); } } @@ -752,7 +752,11 @@ public static void Prefix(PlayerControl __instance, bool shouldAnimate) Logger.Info($"Player: {__instance.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); var phantom = __instance; - CheckVanishPatch.PhantomIsInvisibility.Remove(phantom); + + if (!GameStates.IsMeeting) + { + CheckVanishPatch.PhantomIsInvisibility.Remove(phantom); + } foreach (var target in Main.AllAlivePlayerControls) { @@ -764,12 +768,12 @@ public static void Prefix(PlayerControl __instance, bool shouldAnimate) _ = new LateTask(() => { // Check appear again for desync role - phantom.RpcCheckAppearDesync(true, target); + phantom?.RpcCheckAppearDesync(true, target); }, 0.5f, "Check Appear when vanish is over", shoudLog: false); _ = new LateTask(() => { - phantom.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); } } @@ -996,35 +1000,35 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta Logger.Info($"Player {pc?.Data?.PlayerName}: Id {pc.PlayerId} - is alive: {pc.IsAlive()}", "CheckIsAlive"); } - foreach (var phantom in CheckVanishPatch.PhantomIsInvisibility.ToArray()) { - foreach (var pc in Main.AllAlivePlayerControls) + foreach (var pc in Main.AllPlayerControls) { - if (!phantom.IsAlive() || phantom == pc || pc.AmOwner || !pc.HasDesyncRole()) continue; + if (!phantom.IsAlive() || !pc.IsAlive() || phantom == pc || pc.AmOwner || !pc.HasDesyncRole()) continue; _ = new LateTask(() => { - phantom.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); - }, 0.001f, "Set Scientist in meeting", shoudLog: false); + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); + }, 0.01f, "Set Scientist in meeting", shoudLog: false); _ = new LateTask(() => { - phantom.RpcSetRoleDesync(RoleTypes.Phantom, pc.GetClientId()); - }, 0.2f, "Set Phantom in meeting", shoudLog: false); + phantom?.RpcSetRoleDesync(RoleTypes.Phantom, pc.GetClientId()); + }, 1f, "Set Phantom in meeting", shoudLog: false); _ = new LateTask(() => { - phantom.RpcCheckAppearDesync(false, pc); - }, 1.4f, "Check Appear in meeting", shoudLog: false); + phantom?.RpcStartAppearDesync(false, pc); + }, 1.5f, "Check Appear in meeting", shoudLog: false); _ = new LateTask(() => { - phantom.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); - }, 2.3f, "Set Scientist in meeting after reset", shoudLog: false); + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); + + CheckVanishPatch.PhantomIsInvisibility.Clear(); + }, 10f, "Set Scientist in meeting after reset", shoudLog: false); } } - CheckVanishPatch.PhantomIsInvisibility.Clear(); // Set meeting time MeetingTimeManager.OnReportDeadBody(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 6082524217..dd16b5c409 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -48,7 +48,7 @@ public static void Postfix(AmongUsClient __instance) } Main.PlayerStates = []; - + RoleAssign.RoleResult = []; KillTimerManager.Initializate(); Main.AllPlayerKillCooldown.Clear(); Main.AllPlayerSpeed.Clear(); @@ -173,6 +173,8 @@ public static void Postfix(AmongUsClient __instance) if (GameStates.IsNormalGame) Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + RoleAssign.RoleResult[pc] = CustomRoles.NotAssigned; + ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; pc.cosmetics.nameText.text = pc.name; diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index 3e98a5edab..919cbf864c 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -53,7 +53,6 @@ public static void StartSelect() switch (Options.CurrentGameMode) { case CustomGameMode.FFA: - RoleResult = []; foreach (PlayerControl pc in Main.AllAlivePlayerControls) { RoleResult.Add(pc, CustomRoles.Killer); @@ -61,7 +60,6 @@ public static void StartSelect() return; } - RoleResult = []; var rd = IRandom.Instance; int playerCount = Main.AllAlivePlayerControls.Length; int optImpNum = Main.RealOptionsData.GetInt(Int32OptionNames.NumImpostors); @@ -201,7 +199,7 @@ public static void StartSelect() // Pre-Assigned Roles By Host Are Selected First foreach (var item in SetRoles) { - PlayerControl pc = AllPlayers.FirstOrDefault(x => x.PlayerId == item.Key); + PlayerControl pc = Utils.GetPlayerById(item.Key); if (pc == null) continue; RoleResult[pc] = item.Value; From 795b6536568c5c5d10293e1d13d12a6d0e2affe7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 18:46:56 +0800 Subject: [PATCH 420/778] PhantomRolePatch --- Patches/PhantomRolePatch.cs | 143 ++++++++++++++++++++++++++++++++ Patches/PlayerControlPatch.cs | 148 +--------------------------------- 2 files changed, 146 insertions(+), 145 deletions(-) create mode 100644 Patches/PhantomRolePatch.cs diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs new file mode 100644 index 0000000000..c3c82a5f1b --- /dev/null +++ b/Patches/PhantomRolePatch.cs @@ -0,0 +1,143 @@ +using AmongUs.GameOptions; +using Hazel; +using TOHE.Roles.Core; + +namespace TOHE.Patches; + +[HarmonyPatch(typeof(PlayerControl))] +public static class PhantomRolePatch +{ + private static readonly Il2CppSystem.Collections.Generic.List InvisibilityList = new(); + + /* + * InnerSloth is doing careless stuffs. They didnt put amModdedHost check in cmd check vanish appear + * We temporary need to patch the whole cmd function and wait for the next hotfix from them + */ + [HarmonyPatch(nameof(PlayerControl.CmdCheckVanish)), HarmonyPrefix] + private static bool CmdCheckVanish_Prefix(PlayerControl __instance, float maxDuration) + { + if (AmongUsClient.Instance.AmHost) + { + __instance.CheckVanish(); + return false; + } + __instance.SetRoleInvisibility(true, true, false); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckVanish, SendOption.Reliable, AmongUsClient.Instance.HostId); + messageWriter.Write(maxDuration); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + return false; + } + + [HarmonyPatch(nameof(PlayerControl.CmdCheckAppear)), HarmonyPrefix] + private static bool CmdCheckAppear_Prefix(PlayerControl __instance, bool shouldAnimate) + { + if (AmongUsClient.Instance.AmHost) + { + __instance.CheckAppear(shouldAnimate); + return false; + } + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckAppear, SendOption.Reliable, AmongUsClient.Instance.HostId); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + return false; + } + + // Called when Phantom press vanish button when visible + [HarmonyPatch(nameof(PlayerControl.CheckVanish)), HarmonyPrefix] + private static void CheckVanish_Prefix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost) return; + + var phantom = __instance; + Logger.Info($"Player: {phantom.GetRealName()}", "CheckVanish"); + + foreach (var target in Main.AllPlayerControls) + { + if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + + // Set Phantom when his start vanish + phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); + // Check vanish again for desync role + phantom.RpcCheckVanishDesync(target); + + _ = new LateTask(() => + { + phantom?.RpcExileDesync(target); + }, 1.2f, "Set Phantom invisible", shoudLog: false); + } + InvisibilityList.Add(phantom); + } + // Called when Phantom press appear button when is invisible + [HarmonyPatch(nameof(PlayerControl.CheckAppear)), HarmonyPrefix] + private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnimate) + { + if (!AmongUsClient.Instance.AmHost) return; + + var phantom = __instance; + Logger.Info($"Player: {phantom.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); + + foreach (var target in Main.AllAlivePlayerControls) + { + if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + + var clientId = target.GetClientId(); + + // Set Phantom when his end vanish + phantom.RpcSetRoleDesync(RoleTypes.Phantom, clientId); + + _ = new LateTask(() => + { + // Check appear again for desync role + if (target != null) + phantom?.RpcCheckAppearDesync(shouldAnimate, target); + }, 0.5f, "Check Appear when vanish is over", shoudLog: false); + + _ = new LateTask(() => + { + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); + } + InvisibilityList.Remove(phantom); + } + [HarmonyPatch(nameof(PlayerControl.SetRoleInvisibility)), HarmonyPrefix] + private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) + { + if (!AmongUsClient.Instance.AmHost) return; + + Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); + } + + public static void OnReportBody(PlayerControl seer) + { + if (!seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; + + foreach (var phantom in InvisibilityList.GetFastEnumerator()) + { + if (!phantom.IsAlive()) continue; + + var clientId = seer.GetClientId(); + _ = new LateTask(() => + { + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + }, 0.01f, "Set Scientist in meeting", shoudLog: false); + + _ = new LateTask(() => + { + phantom?.RpcSetRoleDesync(RoleTypes.Phantom, clientId); + }, 1f, "Set Phantom in meeting", shoudLog: false); + + _ = new LateTask(() => + { + if (seer != null) + phantom?.RpcStartAppearDesync(false, seer); + }, 1.5f, "Check Appear in meeting", shoudLog: false); + + _ = new LateTask(() => + { + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + + InvisibilityList.Clear(); + }, 4f, "Set Scientist in meeting after reset", shoudLog: false); + } + } +} diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 718df0f107..dcb45ecf92 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -17,6 +17,7 @@ using TOHE.Roles.Neutral; using TOHE.Roles.Core; using static TOHE.Translator; +using TOHE.Patches; namespace TOHE; @@ -674,122 +675,6 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC } } -/* - * InnerSloth is doing careless stuffs. They didnt put amModdedHost check in cmd check vanish appear - * We temporary need to patch the whole cmd function and wait for the next hotfix from them - */ -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckVanish))] -class CmdCheckVanishPatch -{ - public static bool Prefix(PlayerControl __instance, float maxDuration) - { - if (AmongUsClient.Instance.AmHost) - { - __instance.CheckVanish(); - return false; - } - __instance.SetRoleInvisibility(true, true, false); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckVanish, SendOption.Reliable, AmongUsClient.Instance.HostId); - messageWriter.Write(maxDuration); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - - return false; - } -} - -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckAppear))] -class CmdCheckAppearPatch -{ - public static bool Prefix(PlayerControl __instance, bool shouldAnimate) - { - if (AmongUsClient.Instance.AmHost) - { - __instance.CheckAppear(shouldAnimate); - return false; - } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckAppear, SendOption.Reliable, AmongUsClient.Instance.HostId); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - - return false; - } -} -// Called when Phantom press vanish button when visible -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckVanish))] -class CheckVanishPatch -{ - public static readonly List PhantomIsInvisibility = []; - public static void Prefix(PlayerControl __instance) - { - Logger.Info($"Player: {__instance.GetRealName()}", "CheckVanish"); - - var phantom = __instance; - PhantomIsInvisibility.Add(phantom); - - foreach (var target in Main.AllAlivePlayerControls) - { - if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - - // Set Phantom when his start vanish - phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); - // Check vanish again for desync role - phantom.RpcCheckVanishDesync(target); - - _ = new LateTask(() => - { - phantom?.RpcExileDesync(target); - }, 1.2f, "Set Phantom invisible", shoudLog: false); - } - } -} - -// Called when Phantom press appear button when is invisible -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckAppear))] -class CheckAppearPatch -{ - public static void Prefix(PlayerControl __instance, bool shouldAnimate) - { - Logger.Info($"Player: {__instance.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); - - var phantom = __instance; - - if (!GameStates.IsMeeting) - { - CheckVanishPatch.PhantomIsInvisibility.Remove(phantom); - } - - foreach (var target in Main.AllAlivePlayerControls) - { - if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - - // Set Phantom when his end vanish - phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); - - _ = new LateTask(() => - { - // Check appear again for desync role - phantom?.RpcCheckAppearDesync(true, target); - }, 0.5f, "Check Appear when vanish is over", shoudLog: false); - - _ = new LateTask(() => - { - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, target.GetClientId()); - }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); - } - } -} - -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetRoleInvisibility))] -class SetRoleInvisibilityPatch -{ - public static void Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) - { - if (!AmongUsClient.Instance.AmHost) return; - - Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); - } -} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] class ReportDeadBodyPatch { @@ -998,36 +883,9 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta pc.FixMixedUpOutfit(); } - Logger.Info($"Player {pc?.Data?.PlayerName}: Id {pc.PlayerId} - is alive: {pc.IsAlive()}", "CheckIsAlive"); - } - foreach (var phantom in CheckVanishPatch.PhantomIsInvisibility.ToArray()) - { - foreach (var pc in Main.AllPlayerControls) - { - if (!phantom.IsAlive() || !pc.IsAlive() || phantom == pc || pc.AmOwner || !pc.HasDesyncRole()) continue; - - _ = new LateTask(() => - { - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); - }, 0.01f, "Set Scientist in meeting", shoudLog: false); + PhantomRolePatch.OnReportBody(pc); - _ = new LateTask(() => - { - phantom?.RpcSetRoleDesync(RoleTypes.Phantom, pc.GetClientId()); - }, 1f, "Set Phantom in meeting", shoudLog: false); - - _ = new LateTask(() => - { - phantom?.RpcStartAppearDesync(false, pc); - }, 1.5f, "Check Appear in meeting", shoudLog: false); - - _ = new LateTask(() => - { - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, pc.GetClientId()); - - CheckVanishPatch.PhantomIsInvisibility.Clear(); - }, 10f, "Set Scientist in meeting after reset", shoudLog: false); - } + Logger.Info($"Player {pc?.Data?.PlayerName}: Id {pc.PlayerId} - is alive: {pc.IsAlive()}", "CheckIsAlive"); } // Set meeting time From d858df27dafcbf95c3926947987d2c4c7386a453 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 18:50:16 +0800 Subject: [PATCH 421/778] Set Min Invis Cooldown to 0 --- Roles/Vanilla/PhantomTOHE.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Vanilla/PhantomTOHE.cs b/Roles/Vanilla/PhantomTOHE.cs index 7d0aad15e3..22c339667c 100644 --- a/Roles/Vanilla/PhantomTOHE.cs +++ b/Roles/Vanilla/PhantomTOHE.cs @@ -19,7 +19,7 @@ internal class PhantomTOHE : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.PhantomTOHE); - InvisCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.PhantomBase_InvisCooldown, new(5, 180, 5), 15, TabGroup.ImpostorRoles, false) + InvisCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.PhantomBase_InvisCooldown, new(0, 180, 5), 15, TabGroup.ImpostorRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PhantomTOHE]) .SetValueFormat(OptionFormat.Seconds); InvisDuration = IntegerOptionItem.Create(Id + 3, GeneralOption.PhantomBase_InvisDuration, new(5, 180, 5), 30, TabGroup.ImpostorRoles, false) From f87f2908ef1b77b54aa07a4a46077a8f0347233c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 19:48:05 +0800 Subject: [PATCH 422/778] Some changes & Fixes --- Modules/ExtendedPlayerControl.cs | 7 ++-- Patches/PhantomRolePatch.cs | 4 +-- Patches/PlayerJoinAndLeftPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 50 +++++++++++++------------- Roles/Core/AssignManager/RoleAssign.cs | 12 +++---- Roles/Vanilla/EngineerTOHE.cs | 2 +- Roles/Vanilla/PhantomTOHE.cs | 2 +- Roles/Vanilla/TrackerTOHE.cs | 2 +- 8 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 3da9ce9d6a..2eaca93210 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -74,13 +74,14 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var playerRole = player.GetCustomRole(); if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; + var playerId = player.PlayerId; // When player change desync role to normal role if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) { var newRoleType = newCustomRole.GetRoleTypes(); foreach (var seer in Main.AllPlayerControls) { - RpcSetRoleReplacer.RoleMap[(seer, player)] = (newRoleType, newCustomRole); + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); } player.RpcSetRole(newRoleType, true); } @@ -114,7 +115,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new { targetRoleType = RoleTypes.Scientist; } - RpcSetRoleReplacer.RoleMap[(player, target)] = (targetRoleType, newCustomRole); + RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (targetRoleType, newCustomRole); player.RpcSetRoleDesync(targetRoleType, target.GetClientId()); } } @@ -122,7 +123,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new // Or player change normal role to normal role else { - RpcSetRoleReplacer.RoleMap[(player, player)] = (newCustomRole.GetRoleTypes(), newCustomRole); + RpcSetRoleReplacer.RoleMap[(playerId, playerId)] = (newCustomRole.GetRoleTypes(), newCustomRole); } } public static void RpcExile(this PlayerControl player) diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index c3c82a5f1b..8b1bdb6bf1 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -76,9 +76,9 @@ private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnim var phantom = __instance; Logger.Info($"Player: {phantom.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); - foreach (var target in Main.AllAlivePlayerControls) + foreach (var target in Main.AllPlayerControls) { - if (phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; var clientId = target.GetClientId(); diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 93cb984a00..78111f7239 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -315,7 +315,7 @@ static void Prefix([HarmonyArgument(0)] ClientData data) if (Main.AssignRolesIsStarted) { Logger.Warn($"Assign roles not ended, try remove player {data.Character.PlayerId} from role assign", "OnPlayerLeft"); - RoleAssign.RoleResult?.Remove(data.Character); + RoleAssign.RoleResult?.Remove(data.Character.PlayerId); RpcSetRoleReplacer.Senders?.Remove(data.Character.PlayerId); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index dd16b5c409..163b801215 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -173,7 +173,7 @@ public static void Postfix(AmongUsClient __instance) if (GameStates.IsNormalGame) Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - RoleAssign.RoleResult[pc] = CustomRoles.NotAssigned; + RoleAssign.RoleResult[pc.PlayerId] = CustomRoles.NotAssigned; ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; @@ -440,7 +440,7 @@ private static void SetRolesAfterSelect() foreach (var kv in RoleAssign.RoleResult) { - AssignCustomRole(kv.Value, kv.Key); + AssignCustomRole(kv.Value, Utils.GetPlayerById(kv.Key)); } try @@ -622,15 +622,15 @@ private static void AssignCustomRole(CustomRoles role, PlayerControl player) } private static void CreateRoleMap() { - foreach (var seer in Main.AllPlayerControls) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { var isModded = seer.OwnedByHost() || seer.IsModClient(); - var seerRole = RoleAssign.RoleResult[seer]; - foreach (var target in Main.AllPlayerControls) + var seerRole = RoleAssign.RoleResult[seer.PlayerId]; + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { RoleTypes targetRoleType; var isSelf = seer.PlayerId == target.PlayerId; - var targetRole = RoleAssign.RoleResult[target]; + var targetRole = RoleAssign.RoleResult[target.PlayerId]; if (targetRole.IsDesyncRole()) { if (isSelf) @@ -668,16 +668,8 @@ private static void CreateRoleMap() targetRoleType = targetRole.GetRoleTypes(); } } - RpcSetRoleReplacer.RoleMap[(seer, target)] = (targetRoleType, targetRole); - } - } - - foreach (var seer1 in Main.AllPlayerControls) - { - foreach (var target1 in Main.AllPlayerControls) - { - RpcSetRoleReplacer.RoleMap.TryGetValue((seer1, target1), out var map); - Logger.Info($"seer {seer1?.Data?.PlayerName}-{seer1.PlayerId}, target {target1?.Data?.PlayerName}-{target1.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (targetRoleType, targetRole); + Logger.Info($"seer {seer?.Data?.PlayerName}-{target.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {targetRoleType}, {targetRole}", "Role Map"); } } } @@ -687,7 +679,7 @@ public static class RpcSetRoleReplacer { public static bool BlockSetRole = false; public static Dictionary Senders = []; - public static Dictionary<(PlayerControl seer, PlayerControl target), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; + public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; public static void Initialize() { Senders = []; @@ -708,9 +700,13 @@ public static void StartReplace() } public static void Release() { - foreach (var ((seer, target), (roleType, _)) in RoleMap) + foreach (var ((seerId, targetId), (roleType, _)) in RoleMap) { - if (seer == target) continue; + if (seerId == targetId) continue; + + var seer = Utils.GetPlayerById(seerId); + var target = Utils.GetPlayerById(targetId); + if (seer == null || target == null) continue; if (seer.OwnedByHost()) { @@ -718,11 +714,15 @@ public static void Release() continue; } - var sender = Senders[seer.PlayerId]; - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) - .Write((ushort)roleType) - .Write(true) - .EndRpc(); + try + { + var sender = Senders[targetId]; + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) + .Write((ushort)roleType) + .Write(true) + .EndRpc(); + } + catch { } } SetSelfRoles(); @@ -736,7 +736,7 @@ private static void SetSelfRoles() { foreach (var pc in Main.AllPlayerControls) { - var roleType = RoleMap[(pc, pc)].roleType; + var roleType = RoleMap[(pc.PlayerId, pc.PlayerId)].roleType; var stream = MessageWriter.Get(SendOption.Reliable); stream.StartMessage(6); diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index 919cbf864c..eb38f98979 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -8,7 +8,7 @@ namespace TOHE.Roles.Core.AssignManager; public class RoleAssign { public static Dictionary SetRoles = []; - public static Dictionary RoleResult = []; + public static Dictionary RoleResult = []; public static CustomRoles[] AllRoles => [.. RoleResult.Values]; enum RoleAssignType @@ -55,7 +55,7 @@ public static void StartSelect() case CustomGameMode.FFA: foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - RoleResult.Add(pc, CustomRoles.Killer); + RoleResult.Add(pc.PlayerId, CustomRoles.Killer); } return; } @@ -187,12 +187,12 @@ public static void StartSelect() if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid())) { Main.EnableGM.Value = true; - RoleResult[PlayerControl.LocalPlayer] = CustomRoles.GM; + RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; AllPlayers.Remove(PlayerControl.LocalPlayer); } if (Main.EnableGM.Value) { - RoleResult[PlayerControl.LocalPlayer] = CustomRoles.GM; + RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; AllPlayers.Remove(PlayerControl.LocalPlayer); SetRoles.Remove(PlayerControl.LocalPlayer.PlayerId); } @@ -202,7 +202,7 @@ public static void StartSelect() PlayerControl pc = Utils.GetPlayerById(item.Key); if (pc == null) continue; - RoleResult[pc] = item.Value; + RoleResult[item.Key] = item.Value; AllPlayers.Remove(pc); if (item.Value.IsImpostor()) @@ -772,7 +772,7 @@ public static void StartSelect() var assignedRole = FinalRolesList.RandomElement(); // Assign random role for random player - RoleResult[randomPlayer] = assignedRole; + RoleResult[randomPlayer.PlayerId] = assignedRole; Logger.Info($"Player:{randomPlayer.GetRealName()} => {assignedRole}", "RoleAssign"); // Remove random role and player from list diff --git a/Roles/Vanilla/EngineerTOHE.cs b/Roles/Vanilla/EngineerTOHE.cs index cd1c9c157a..6e60dab07d 100644 --- a/Roles/Vanilla/EngineerTOHE.cs +++ b/Roles/Vanilla/EngineerTOHE.cs @@ -20,7 +20,7 @@ internal class EngineerTOHE : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.EngineerTOHE); - VentUseCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.EngineerBase_VentCooldown, new(0, 250, 5), 15, TabGroup.CrewmateRoles, false) + VentUseCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.EngineerBase_VentCooldown, new(1, 250, 5), 15, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.EngineerTOHE]) .SetValueFormat(OptionFormat.Seconds); InVentMaxTime = IntegerOptionItem.Create(Id + 3, GeneralOption.EngineerBase_InVentMaxTime, new(0, 250, 5), 15, TabGroup.CrewmateRoles, false) diff --git a/Roles/Vanilla/PhantomTOHE.cs b/Roles/Vanilla/PhantomTOHE.cs index 22c339667c..66e371b884 100644 --- a/Roles/Vanilla/PhantomTOHE.cs +++ b/Roles/Vanilla/PhantomTOHE.cs @@ -19,7 +19,7 @@ internal class PhantomTOHE : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.PhantomTOHE); - InvisCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.PhantomBase_InvisCooldown, new(0, 180, 5), 15, TabGroup.ImpostorRoles, false) + InvisCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.PhantomBase_InvisCooldown, new(1, 180, 1), 15, TabGroup.ImpostorRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PhantomTOHE]) .SetValueFormat(OptionFormat.Seconds); InvisDuration = IntegerOptionItem.Create(Id + 3, GeneralOption.PhantomBase_InvisDuration, new(5, 180, 5), 30, TabGroup.ImpostorRoles, false) diff --git a/Roles/Vanilla/TrackerTOHE.cs b/Roles/Vanilla/TrackerTOHE.cs index f5334e568a..0cc184f718 100644 --- a/Roles/Vanilla/TrackerTOHE.cs +++ b/Roles/Vanilla/TrackerTOHE.cs @@ -20,7 +20,7 @@ internal class TrackerTOHE : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.TrackerTOHE); - TrackCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.TrackerBase_TrackingCooldown, new(5, 120, 5), 15, TabGroup.CrewmateRoles, false) + TrackCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.TrackerBase_TrackingCooldown, new(1, 120, 1), 15, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TrackerTOHE]) .SetValueFormat(OptionFormat.Seconds); TrackDuration = IntegerOptionItem.Create(Id + 3, GeneralOption.TrackerBase_TrackingDuration, new(5, 120, 5), 30, TabGroup.CrewmateRoles, false) From 2eeb57fcf2941186ccbb049a44e52fe41b5672fa Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 19:49:30 +0800 Subject: [PATCH 423/778] Check count --- Patches/PhantomRolePatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index 8b1bdb6bf1..3db908a584 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -109,7 +109,7 @@ private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool is public static void OnReportBody(PlayerControl seer) { - if (!seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; + if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; foreach (var phantom in InvisibilityList.GetFastEnumerator()) { From 12487ce1aa7e4f5087f2300be452b5af7c04d1ff Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 19:50:54 +0800 Subject: [PATCH 424/778] Forgot --- Roles/Vanilla/EngineerTOHE.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Vanilla/EngineerTOHE.cs b/Roles/Vanilla/EngineerTOHE.cs index 6e60dab07d..aa3d9fade4 100644 --- a/Roles/Vanilla/EngineerTOHE.cs +++ b/Roles/Vanilla/EngineerTOHE.cs @@ -20,7 +20,7 @@ internal class EngineerTOHE : RoleBase public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.EngineerTOHE); - VentUseCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.EngineerBase_VentCooldown, new(1, 250, 5), 15, TabGroup.CrewmateRoles, false) + VentUseCooldown = IntegerOptionItem.Create(Id + 2, GeneralOption.EngineerBase_VentCooldown, new(1, 250, 1), 15, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.EngineerTOHE]) .SetValueFormat(OptionFormat.Seconds); InVentMaxTime = IntegerOptionItem.Create(Id + 3, GeneralOption.EngineerBase_InVentMaxTime, new(0, 250, 5), 15, TabGroup.CrewmateRoles, false) From d3fed1f831592527a92f6402b9eb4cc598268ceb Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 20:07:12 +0800 Subject: [PATCH 425/778] Fix errors --- Modules/AntiBlackout.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 760bd4d2de..36f0fccbfd 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -248,9 +248,14 @@ public static void ResetPlayerMaps() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - foreach (var ((seer, target), (roletype, _)) in RpcSetRoleReplacer.RoleMap) + foreach (var ((seerId, targetId), (roletype, _)) in RpcSetRoleReplacer.RoleMap) { - if (seer.OwnedByHost()) continue; + // skip host + if (seerId == 0) continue; + + var seer = Utils.GetPlayerById(seerId); + var target = Utils.GetPlayerById(targetId); + if (seer == null || target == null) continue; var realtype = roletype; if (target.Data.IsDead) @@ -270,8 +275,8 @@ public static void ResetPlayerMaps() foreach (var seer in Main.AllPlayerControls) // fix not being able to go trough walls { if (seer.OwnedByHost()) continue; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.Exiled, SendOption.None, seer.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(writer); + + target.RpcExileDesync(seer); } } }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); From afe2a843faa36d2a95fd74393dcb78dbf4dc8444 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 20:49:02 +0800 Subject: [PATCH 426/778] Fix BlackOut --- Modules/AntiBlackout.cs | 105 ++++++++++++++++++---------------------- Patches/ExilePatch.cs | 6 ++- 2 files changed, 51 insertions(+), 60 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 36f0fccbfd..efd3915896 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -16,7 +16,6 @@ public static class AntiBlackout public static bool BlackOutIsActive => false; /*!Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut();*/ //this is simply just called in less places, because antiblackout with role-basis changing is OP - public static bool LesserBlackOutActive => CheckBlackOut(); public static int ExilePlayerId = -1; /// @@ -96,9 +95,26 @@ public static void SetIsDead(bool doSend = true, [CallerMemberName] string calle IsCached = true; if (doSend) SendGameData(); } - public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + private static void TempRevivePlayers() { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + + PlayerControl dummyImp = Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId && x.HasKillButton()) + ?? Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId); + foreach (var seer in Main.AllPlayerControls) + { + if (seer.OwnedByHost() || seer.IsModClient()) continue; + foreach (var target in Main.AllPlayerControls) + { + RoleTypes typa = target == dummyImp ? RoleTypes.Impostor : RoleTypes.Crewmate; + + target.RpcSetRoleDesync(typa, seer.GetClientId()); + } + } + } + public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + { logger.Info($"RestoreIsDead is called from {callerMethodName}"); foreach (var info in GameData.Instance.AllPlayers) { @@ -149,27 +165,27 @@ public static void OnDisconnect(NetworkedPlayerInfo player) ///Execute the code with IsDead temporarily set back to what it should be ///Execution details /// - public static void TempRestore(Action action) - { - logger.Info("==Temp Restore=="); - // Whether TempRestore was executed with IsDead overwritten - bool before_IsCached = IsCached; - try - { - if (before_IsCached) RestoreIsDead(doSend: false); - action(); - } - catch (Exception ex) - { - logger.Warn("An exception occurred within AntiBlackout.TempRestore"); - logger.Exception(ex); - } - finally - { - if (before_IsCached) SetIsDead(doSend: false); - logger.Info("==/Temp Restore=="); - } - } + //public static void TempRestore(Action action) + //{ + // logger.Info("==Temp Restore=="); + // // Whether TempRestore was executed with IsDead overwritten + // bool before_IsCached = IsCached; + // try + // { + // if (before_IsCached) RestoreIsDead(doSend: false); + // action(); + // } + // catch (Exception ex) + // { + // logger.Warn("An exception occurred within AntiBlackout.TempRestore"); + // logger.Exception(ex); + // } + // finally + // { + // if (before_IsCached) SetIsDead(doSend: false); + // logger.Info("==/Temp Restore=="); + // } + //} public static void AntiBlackRpcVotingComplete(this MeetingHud __instance, MeetingHud.VoterState[] states, NetworkedPlayerInfo exiled, bool tie) { if (AmongUsClient.Instance.AmClient) @@ -252,55 +268,28 @@ public static void ResetPlayerMaps() { // skip host if (seerId == 0) continue; - + var seer = Utils.GetPlayerById(seerId); var target = Utils.GetPlayerById(targetId); + if (seer == null || target == null) continue; + if (seer.IsModClient()) continue; - var realtype = roletype; + var realRoleType = roletype; if (target.Data.IsDead) { - realtype = seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + realRoleType = seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; } - target.RpcSetRoleDesync(realtype, seer.GetClientId()); + target.RpcSetRoleDesync(realRoleType, seer.GetClientId()); } - _ = new LateTask(() => { + _ = new LateTask(() => + { foreach (var seer in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) { seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); } // for some reason has to be done later - - foreach (var target in Main.AllPlayerControls.Where(x => x.Data.IsDead)) - { - foreach (var seer in Main.AllPlayerControls) // fix not being able to go trough walls - { - if (seer.OwnedByHost()) continue; - - target.RpcExileDesync(seer); - } - } }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); } - private static void TempRevivePlayers() - { - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - - PlayerControl dummyImp = Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId && x.HasKillButton()) - ?? Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId); - - foreach (var pc in Main.AllPlayerControls) - { - foreach (var reciever in Main.AllPlayerControls) - { - if (reciever.OwnedByHost()) continue; - RoleTypes typa = pc == dummyImp ? RoleTypes.Impostor : RoleTypes.Crewmate; - - pc.RpcSetRoleDesync(typa, reciever.GetClientId()); - } - } - - ExilePlayerId = -1; - } public static void Reset() { logger.Info("==Reset=="); diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index b6cf3da1fd..7fc18dd905 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -64,7 +64,6 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) bool DecidedWinner = false; if (!AmongUsClient.Instance.AmHost) return; AntiBlackout.RestoreIsDead(doSend: false); - AntiBlackout.ResetPlayerMaps(); List collectorCL = Utils.GetRoleBasesByType()?.ToList(); @@ -77,7 +76,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) var exiledPC = exiled.Object; // Reset player cam for exiled desync impostor - if (exiledPC.HasDesyncRole() || AntiBlackout.LesserBlackOutActive) + if (exiledPC.HasDesyncRole()) { exiledPC?.ResetPlayerCam(1f); } @@ -157,6 +156,9 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled = AntiBlackout_LastExiled; AntiBlackout.SendGameData(); + AntiBlackout.ResetPlayerMaps(); + AntiBlackout.ExilePlayerId = -1; + if (AntiBlackout.BlackOutIsActive && // State in which the expulsion target is overwritten (need not be executed if the expulsion target is not overwritten) exiled != null && // exiled is not null exiled.Object != null) //exiled.Object is not null From 09739823ca3d2c4c45b78532544a4ea96a5506f9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:10:36 +0800 Subject: [PATCH 427/778] Fix bugs --- Modules/AntiBlackout.cs | 14 +++++++++----- Patches/ExilePatch.cs | 22 ++++++---------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index efd3915896..73ccb119a5 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -99,17 +99,16 @@ private static void TempRevivePlayers() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - PlayerControl dummyImp = Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId && x.HasKillButton()) - ?? Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId); + PlayerControl dummyImp = Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId); foreach (var seer in Main.AllPlayerControls) { if (seer.OwnedByHost() || seer.IsModClient()) continue; foreach (var target in Main.AllPlayerControls) { - RoleTypes typa = target == dummyImp ? RoleTypes.Impostor : RoleTypes.Crewmate; + RoleTypes targetRoleType = target.PlayerId == dummyImp.PlayerId ? RoleTypes.Impostor : RoleTypes.Crewmate; - target.RpcSetRoleDesync(typa, seer.GetClientId()); + target.RpcSetRoleDesync(targetRoleType, seer.GetClientId()); } } } @@ -278,10 +277,15 @@ public static void ResetPlayerMaps() var realRoleType = roletype; if (target.Data.IsDead) { - realRoleType = seer.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + realRoleType = target.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; } target.RpcSetRoleDesync(realRoleType, seer.GetClientId()); } + foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) + { + if (seer.OwnedByHost() || seer.IsModClient()) continue; + seer.RpcExile(); + } _ = new LateTask(() => { foreach (var seer in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 7fc18dd905..1b56bb9794 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -12,6 +12,10 @@ class ExileControllerWrapUpPatch [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { + public static void Prefix(ExileController __instance) + { + AntiBlackout.SetIsDead(); + } public static void Postfix(ExileController __instance) { try @@ -165,7 +169,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled.Object.RpcExileV2(); } - }, 1f, "Restore IsDead Task"); + }, 1.5f, "Restore IsDead Task"); _ = new LateTask(() => { @@ -193,21 +197,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) }); Main.AfterMeetingDeathPlayers.Clear(); - - //Kill off GAS again - /*foreach (var pc in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) - { - foreach (var reciever in Main.AllPlayerControls) - { - if (reciever.OwnedByHost()) continue; - RoleTypes typa = pc == reciever ? RoleTypes.GuardianAngel : RoleTypes.CrewmateGhost; - pc.RpcSetRoleDesync(typa, reciever.GetClientId()); - } - //pc.ResetPlayerCam(); - //pc.RpcSetRoleDesync(RoleTypes.GuardianAngel, pc.GetClientId()); - }*/ - - }, 1.1f, "AfterMeetingDeathPlayers Task"); + }, 1.6f, "AfterMeetingDeathPlayers Task"); } //This should happen shortly after the Exile Controller wrap up finished for clients //For Certain Laggy clients 0.8f delay is still not enough. The finish time can differ. From 4be8f405f54dd40ad82925e9b49f315cce398640 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:17:45 +0800 Subject: [PATCH 428/778] Rename --- Modules/AntiBlackout.cs | 13 ++++++++----- Patches/ExilePatch.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 73ccb119a5..c64a671104 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -77,11 +77,10 @@ public static bool CheckBlackOut() public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { - TempRevivePlayers(); + RevivePlayersAndSetDummyImp(); logger.Info($"SetIsDead is called from {callerMethodName}"); if (IsCached) { - logger.Info("Please run RestoreIsDead before running SetIsDead again."); return; } isDeadCache.Clear(); @@ -95,7 +94,7 @@ public static void SetIsDead(bool doSend = true, [CallerMemberName] string calle IsCached = true; if (doSend) SendGameData(); } - private static void TempRevivePlayers() + private static void RevivePlayersAndSetDummyImp() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; @@ -259,7 +258,7 @@ public static void AfterMeetingTasks() Logger.Error($"{error}", "AntiBlackout.AfterMeetingTasks"); } } - public static void ResetPlayerMaps() + public static void SerRealPlayerRoles() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; @@ -281,6 +280,10 @@ public static void ResetPlayerMaps() } target.RpcSetRoleDesync(realRoleType, seer.GetClientId()); } + SetDeadPlayersAsExiled(); + } + public static void SetDeadPlayersAsExiled() + { foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) { if (seer.OwnedByHost() || seer.IsModClient()) continue; @@ -292,7 +295,7 @@ public static void ResetPlayerMaps() { seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); } // for some reason has to be done later - }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); + }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); } public static void Reset() { diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 1b56bb9794..6c34c48298 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -160,7 +160,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled = AntiBlackout_LastExiled; AntiBlackout.SendGameData(); - AntiBlackout.ResetPlayerMaps(); + AntiBlackout.SerRealPlayerRoles(); AntiBlackout.ExilePlayerId = -1; if (AntiBlackout.BlackOutIsActive && // State in which the expulsion target is overwritten (need not be executed if the expulsion target is not overwritten) From e4217daa7f84ac6da196fb45bfdd995da0dca3d6 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:23:04 +0800 Subject: [PATCH 429/778] Lol --- Modules/AntiBlackout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index c64a671104..df342115d0 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -258,7 +258,7 @@ public static void AfterMeetingTasks() Logger.Error($"{error}", "AntiBlackout.AfterMeetingTasks"); } } - public static void SerRealPlayerRoles() + public static void SetRealPlayerRoles() { if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; From f60b39b81199e1e5560e681c91de00ff24f3f68e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:31:55 +0800 Subject: [PATCH 430/778] Fix twise couting Voting Data --- Patches/MeetingHudPatch.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 51211a33a2..5809927379 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -244,14 +244,17 @@ public static bool Prefix(MeetingHud __instance) } } - var VotingData = __instance.CustomCalculateVotes(); //Influenced vote mun isnt counted here + Dictionary VotingData = []; if (CustomRoles.Influenced.RoleExist()) { Influenced.ChangeVotingData(VotingData); VotingData = __instance.CustomCalculateVotes(true); } - //Change voting data for influenced, vote num counted here + else + { + VotingData = __instance.CustomCalculateVotes(); + } for (int i = 0; i < statesList.Count; i++) { From 6763abdcd1c130ca1292a373ad1ccaeb699efcac Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:38:52 +0800 Subject: [PATCH 431/778] Add Skill Limit Times --- Modules/RPC.cs | 3 +++ Patches/MeetingHudPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 5 +++++ Roles/AddOns/Common/Evader.cs | 20 ++++++++++++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 368d1ee053..0ee97ddb18 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -960,6 +960,9 @@ public static void SetCustomRole(byte targetId, CustomRoles role) case CustomRoles.Statue: Statue.Add(targetId); break; + case CustomRoles.Evader: + Evader.Add(targetId); + break; } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index b3891ccdf1..99b71f8c2f 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -809,7 +809,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta if (target.Is(CustomRoles.Evader)) { - Evader.CheckExile(ref VoteNum); + Evader.CheckExile(ps.VotedFor, ref VoteNum); } //Add 1 vote If key is not defined, overwrite with 1 and define diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 61ed7fcff6..46eca25926 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -10,6 +10,7 @@ using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -223,6 +224,7 @@ public static void Postfix(AmongUsClient __instance) Ghoul.Init(); Rainbow.Init(); Rebirth.Init(); + Evader.Init(); //FFA FFAManager.Init(); @@ -581,6 +583,9 @@ private static void SetRolesAfterSelect() case CustomRoles.Rebirth: Rebirth.Add(pc.PlayerId); break; + case CustomRoles.Evader: + Evader.Add(pc.PlayerId); + break; default: break; } diff --git a/Roles/AddOns/Common/Evader.cs b/Roles/AddOns/Common/Evader.cs index 220f744b53..f64c1c8f95 100644 --- a/Roles/AddOns/Common/Evader.cs +++ b/Roles/AddOns/Common/Evader.cs @@ -6,20 +6,36 @@ public class Evader : IAddon private const int Id = 29600; public AddonTypes Type => AddonTypes.Helpful; + private static OptionItem SkillLimitTimes; private static OptionItem ChanceNotExiled; + private static readonly Dictionary SkillLimit = []; + public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Evader, canSetNum: true, teamSpawnOptions: true); + SkillLimitTimes = IntegerOptionItem.Create(Id + 10, "SkillLimitTimes", new(0, 10, 1), 2, TabGroup.Addons, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evader]) + .SetValueFormat(OptionFormat.Times); ChanceNotExiled = IntegerOptionItem.Create(Id + 10, "Evader_ChanceNotExiled", new(0, 100, 5), 25, TabGroup.Addons, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evader]) .SetValueFormat(OptionFormat.Percent); } - - public static void CheckExile(ref int VoteNum) + public static void Init() + { + SkillLimit.Clear(); + } + public static void Add(byte playerId) + { + SkillLimit[playerId] = SkillLimitTimes.GetInt(); + } + public static void CheckExile(byte evaderId, ref int VoteNum) { + if (SkillLimit[evaderId] <= 0) return; + if (IRandom.Instance.Next(1, 100) < ChanceNotExiled.GetInt()) { + SkillLimit[evaderId]--; VoteNum = 0; } } From 9e5f657c79a7e7e036762fd9f2bf33ed3f56c200 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:39:17 +0800 Subject: [PATCH 432/778] Fix id --- Roles/AddOns/Common/Evader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Evader.cs b/Roles/AddOns/Common/Evader.cs index f64c1c8f95..bb87f27128 100644 --- a/Roles/AddOns/Common/Evader.cs +++ b/Roles/AddOns/Common/Evader.cs @@ -17,7 +17,7 @@ public void SetupCustomOption() SkillLimitTimes = IntegerOptionItem.Create(Id + 10, "SkillLimitTimes", new(0, 10, 1), 2, TabGroup.Addons, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evader]) .SetValueFormat(OptionFormat.Times); - ChanceNotExiled = IntegerOptionItem.Create(Id + 10, "Evader_ChanceNotExiled", new(0, 100, 5), 25, TabGroup.Addons, false) + ChanceNotExiled = IntegerOptionItem.Create(Id + 11, "Evader_ChanceNotExiled", new(0, 100, 5), 25, TabGroup.Addons, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evader]) .SetValueFormat(OptionFormat.Percent); } From 3aba3f3a5fde5857a90fc41c4dd6aac854f955c4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 21:45:10 +0800 Subject: [PATCH 433/778] Alpha 7 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index c052b1e8fc..2510b14d0b 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0820.210.00060"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 6"; + public const string PluginVersion = "2024.0825.210.00070"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 7"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 6 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 7 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From a40e43d5d5d289475806bc8d9803329f0b24202c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 25 Aug 2024 22:07:39 +0800 Subject: [PATCH 434/778] Fix error --- Patches/ExilePatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 6c34c48298..89b08675b7 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -12,7 +12,7 @@ class ExileControllerWrapUpPatch [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { - public static void Prefix(ExileController __instance) + public static void Prefix() { AntiBlackout.SetIsDead(); } @@ -160,7 +160,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled = AntiBlackout_LastExiled; AntiBlackout.SendGameData(); - AntiBlackout.SerRealPlayerRoles(); + AntiBlackout.SetRealPlayerRoles(); AntiBlackout.ExilePlayerId = -1; if (AntiBlackout.BlackOutIsActive && // State in which the expulsion target is overwritten (need not be executed if the expulsion target is not overwritten) From 7a23232ec0ba76b33c639e0240cb8eb7d4fb260f Mon Sep 17 00:00:00 2001 From: Pyro <141536178+NotPyro404@users.noreply.github.com> Date: Sun, 25 Aug 2024 18:34:39 -0400 Subject: [PATCH 435/778] Update en_US.json --- Resources/Lang/en_US.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ac2c1bdfb3..0f7c35e551 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -692,9 +692,9 @@ "SilentInfo": "Vote like a Ghost!", "SusceptibleInfo": "Death-reason lotto!", "TrickyInfo": "Tricky slays, in mysterious ways.", - "TiredInfo": "Labor makes you rest Zzz..", - "StatueInfo": "You're still as a rock nearby people", - "EvaderInfo": "Get chance not be exiled!", + "TiredInfo": "Labor makes you sleepy.. Zzz..", + "StatueInfo": "You're still as a rock around people", + "EvaderInfo": "You have a chance to not be exiled!", "GMInfo": "Spectate the chaos!", "NotAssignedInfo": "No assigned role", "SunnyboyInfo": "Shine, shine my sunshine!", @@ -996,7 +996,7 @@ "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", - "EvaderInfoLong": "(Add-ons):\nYou have a definite chance of not be exiled", + "EvaderInfoLong": "(Add-ons):\nWhen the Evader is voted out, there is chance the Evader will not be ejected. (Chance set by host.)", "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", From 7f0823995e223c7d35f0092e9fa501fa90d34b15 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 27 Aug 2024 00:53:16 +0800 Subject: [PATCH 436/778] Fix Chronomancer bar not changing for mod clients --- Roles/Impostor/Chronomancer.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index f8709c8fc3..d85760be6f 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -4,6 +4,8 @@ using static TOHE.Options; using static TOHE.Translator; using AmongUs.GameOptions; +using Hazel; +using InnerNet; namespace TOHE.Roles.Impostor; @@ -131,6 +133,7 @@ public override void OnFixedUpdate(PlayerControl pc) { if (GameStates.IsMeeting) return; + var oldChargedTime = ChargedTime; if (LastCD != GetCharge()) { LastCD = GetCharge(); @@ -155,8 +158,29 @@ public override void OnFixedUpdate(PlayerControl pc) pc.MarkDirtySettings(); } + if (oldChargedTime != ChargedTime) + { + SendChargedTimeRPC(); + } + countnowF += Time.deltaTime; } + + public void SendChargedTimeRPC() + { + // Cant directly write Ability Limit, create another method to send it + // Only send to the target to prevent logging in other's + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.None, _Player.OwnerId); + writer.WriteNetObject(_Player); + writer.Write(ChargedTime); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + ChargedTime = reader.ReadInt32(); + } + public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { bool ismeeting = GameStates.IsMeeting || isForMeeting; From 60edc08d967fee52ad50a7c7e7c69feac8fc0213 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:01:15 +0800 Subject: [PATCH 437/778] Fix Investigator RPC not working --- Roles/Crewmate/Investigator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Roles/Crewmate/Investigator.cs b/Roles/Crewmate/Investigator.cs index b87674b9d4..a2845fe449 100644 --- a/Roles/Crewmate/Investigator.cs +++ b/Roles/Crewmate/Investigator.cs @@ -67,7 +67,6 @@ private static void SendRPC(int operate, byte playerId = byte.MaxValue, byte tar writer.Write(targetId); writer.Write(MaxInvestigateLimit[playerId]); writer.Write(RoundInvestigateLimit[playerId]); - return; } AmongUsClient.Instance.FinishRpcImmediately(writer); } From d27508920c6c64cc937315c13085bbc387f04bec Mon Sep 17 00:00:00 2001 From: TommyXL <104814436+Tommy-XL@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:31:23 +0800 Subject: [PATCH 438/778] Update EvaderInfoLong --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 0f7c35e551..24294dbcd5 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -996,7 +996,7 @@ "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", - "EvaderInfoLong": "(Add-ons):\nWhen the Evader is voted out, there is chance the Evader will not be ejected. (Chance set by host.)", + "EvaderInfoLong": "(Add-ons):\nWhen the Evader gets voted out, there is a chance the Evader will not get ejected. (Chance set by host.)", "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", From ad623620e83ae04e1ad64bee09d5c68a37fa8590 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 27 Aug 2024 22:58:04 +0800 Subject: [PATCH 439/778] Lot of fixes for AntiBlackOut --- Modules/AntiBlackout.cs | 78 ++++++++++++++++++----------------- Patches/ExilePatch.cs | 10 +---- Patches/MeetingHudPatch.cs | 1 + Patches/PlayerControlPatch.cs | 22 +++++----- 4 files changed, 55 insertions(+), 56 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index df342115d0..a265e185b7 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -17,6 +17,7 @@ public static class AntiBlackout //this is simply just called in less places, because antiblackout with role-basis changing is OP public static int ExilePlayerId = -1; + public static bool SkipTasks = false; /// /// Count alive players and check black out @@ -77,6 +78,7 @@ public static bool CheckBlackOut() public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") { + SkipTasks = true; RevivePlayersAndSetDummyImp(); logger.Info($"SetIsDead is called from {callerMethodName}"); if (IsCached) @@ -155,35 +157,10 @@ public static void OnDisconnect(NetworkedPlayerInfo player) // Execution conditions: Client is the host, IsDead is overridden, player is already disconnected if (!AmongUsClient.Instance.AmHost || !IsCached || !player.Disconnected) return; isDeadCache[player.PlayerId] = (true, true); + RevivePlayersAndSetDummyImp(); player.IsDead = player.Disconnected = false; SendGameData(); } - - /// - ///Execute the code with IsDead temporarily set back to what it should be - ///Execution details - /// - //public static void TempRestore(Action action) - //{ - // logger.Info("==Temp Restore=="); - // // Whether TempRestore was executed with IsDead overwritten - // bool before_IsCached = IsCached; - // try - // { - // if (before_IsCached) RestoreIsDead(doSend: false); - // action(); - // } - // catch (Exception ex) - // { - // logger.Warn("An exception occurred within AntiBlackout.TempRestore"); - // logger.Exception(ex); - // } - // finally - // { - // if (before_IsCached) SetIsDead(doSend: false); - // logger.Info("==/Temp Restore=="); - // } - //} public static void AntiBlackRpcVotingComplete(this MeetingHud __instance, MeetingHud.VoterState[] states, NetworkedPlayerInfo exiled, bool tie) { if (AmongUsClient.Instance.AmClient) @@ -269,16 +246,44 @@ public static void SetRealPlayerRoles() var seer = Utils.GetPlayerById(seerId); var target = Utils.GetPlayerById(targetId); - + if (seer == null || target == null) continue; if (seer.IsModClient()) continue; - var realRoleType = roletype; + var isSelf = seerId == targetId; + var changedRoleType = roletype; if (target.Data.IsDead) { - realRoleType = target.CanUseSabotage() ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + if (isSelf) + { + var isGuardianAngel = target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole()); + if (isGuardianAngel) + { + changedRoleType = RoleTypes.GuardianAngel; + } + else if (target.Is(Custom_Team.Impostor) || target.HasDesyncRole()) + { + changedRoleType = RoleTypes.ImpostorGhost; + } + else + { + changedRoleType = RoleTypes.CrewmateGhost; + } + } + else + { + var seerIsKiller = seer.Is(Custom_Team.Impostor) || seer.HasDesyncRole(); + if (!seerIsKiller && target.Is(Custom_Team.Impostor)) + { + changedRoleType = RoleTypes.ImpostorGhost; + } + else + { + changedRoleType = RoleTypes.CrewmateGhost; + } + } } - target.RpcSetRoleDesync(realRoleType, seer.GetClientId()); + target.RpcSetRoleDesync(changedRoleType, seer.GetClientId()); } SetDeadPlayersAsExiled(); } @@ -286,16 +291,11 @@ public static void SetDeadPlayersAsExiled() { foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) { - if (seer.OwnedByHost() || seer.IsModClient()) continue; + // RpcExile is already sets dead role types seer.RpcExile(); } - _ = new LateTask(() => - { - foreach (var seer in Main.AllPlayerControls.Where(x => x.GetRoleClass().ThisRoleBase == CustomRoles.GuardianAngel)) - { - seer.RpcSetRoleDesync(RoleTypes.GuardianAngel, seer.GetClientId()); - } // for some reason has to be done later - }, 0.5f, "AntiBlackout - Fix Movement For Ghosts"); + SkipTasks = false; + ExilePlayerId = -1; } public static void Reset() { @@ -305,6 +305,8 @@ public static void Reset() IsCached = false; ShowExiledInfo = false; StoreExiledMessage = ""; + ExilePlayerId = -1; + SkipTasks = false; } public static bool ShowExiledInfo = false; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 89b08675b7..6368d34c3c 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -1,5 +1,4 @@ using AmongUs.Data; -using AmongUs.GameOptions; using System; using TOHE.Roles.Core; using TOHE.Roles.Neutral; @@ -12,10 +11,6 @@ class ExileControllerWrapUpPatch [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { - public static void Prefix() - { - AntiBlackout.SetIsDead(); - } public static void Postfix(ExileController __instance) { try @@ -161,7 +156,6 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) exiled = AntiBlackout_LastExiled; AntiBlackout.SendGameData(); AntiBlackout.SetRealPlayerRoles(); - AntiBlackout.ExilePlayerId = -1; if (AntiBlackout.BlackOutIsActive && // State in which the expulsion target is overwritten (need not be executed if the expulsion target is not overwritten) exiled != null && // exiled is not null @@ -169,7 +163,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled.Object.RpcExileV2(); } - }, 1.5f, "Restore IsDead Task"); + }, 1.1f, "Restore IsDead Task"); _ = new LateTask(() => { @@ -197,7 +191,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) }); Main.AfterMeetingDeathPlayers.Clear(); - }, 1.6f, "AfterMeetingDeathPlayers Task"); + }, 1.2f, "AfterMeetingDeathPlayers Task"); } //This should happen shortly after the Exile Controller wrap up finished for clients //For Certain Laggy clients 0.8f delay is still not enough. The finish time can differ. diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 4715436292..406b6febee 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -73,6 +73,7 @@ public static bool Prefix(MeetingHud __instance) ExileControllerWrapUpPatch.AntiBlackout_LastExiled = exiled; Main.LastVotedPlayerInfo = exiled; + AntiBlackout.ExilePlayerId = exiled.PlayerId; if (AntiBlackout.BlackOutIsActive) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 2c51758959..ac5707837f 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1736,7 +1736,9 @@ public static class PlayerControlDiePatch { public static void Postfix(PlayerControl __instance) { - if (!AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost || __instance == null) return; + // Skip Tasks while Anti Blackout but not for real exiled + if (AntiBlackout.SkipTasks && AntiBlackout.ExilePlayerId != __instance.PlayerId) return; try { @@ -1770,7 +1772,7 @@ public static void Postfix(PlayerControl __instance) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] class PlayerControlSetRolePatch { - private static readonly Dictionary ghostRoles = []; + private static readonly Dictionary GhostRoles = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) { canOverrideRole = true; @@ -1797,7 +1799,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol } var targetIsKiller = target.Is(Custom_Team.Impostor) || target.HasDesyncRole(); - ghostRoles.Clear(); + GhostRoles.Clear(); foreach (var seer in Main.AllPlayerControls) { @@ -1806,19 +1808,19 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol if (target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole())) { - ghostRoles[seer] = RoleTypes.GuardianAngel; + GhostRoles[seer] = RoleTypes.GuardianAngel; } else if ((self && targetIsKiller) || (!seerIsKiller && target.Is(Custom_Team.Impostor))) { - ghostRoles[seer] = RoleTypes.ImpostorGhost; + GhostRoles[seer] = RoleTypes.ImpostorGhost; } else { - ghostRoles[seer] = RoleTypes.CrewmateGhost; + GhostRoles[seer] = RoleTypes.CrewmateGhost; } } // If all players see player as Guardian Angel - if (ghostRoles.All(kvp => kvp.Value == RoleTypes.GuardianAngel)) + if (GhostRoles.All(kvp => kvp.Value == RoleTypes.GuardianAngel)) { roleType = RoleTypes.GuardianAngel; __instance.RpcSetRoleDesync(RoleTypes.GuardianAngel, __instance.GetClientId()); @@ -1831,20 +1833,20 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol return false; } // If all players see player as Crewmate Ghost - else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.CrewmateGhost)) + else if (GhostRoles.All(kvp => kvp.Value == RoleTypes.CrewmateGhost)) { roleType = RoleTypes.CrewmateGhost; return true; } // If all players see player as Impostor Ghost - else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.ImpostorGhost)) + else if (GhostRoles.All(kvp => kvp.Value == RoleTypes.ImpostorGhost)) { roleType = RoleTypes.ImpostorGhost; return true; } else { - foreach ((var seer, var role) in ghostRoles) + foreach ((var seer, var role) in GhostRoles) { if (seer == null || target == null) continue; Logger.Info($"Desync {targetName} => {role} for {seer.GetNameWithRole().RemoveHtmlTags()}", "PlayerControl.RpcSetRole"); From 902dc50a0b25acdf8480d5f6b327ffac1cf81198 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 27 Aug 2024 22:58:58 +0800 Subject: [PATCH 440/778] Remove --- Modules/AntiBlackout.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index a265e185b7..c638eebd71 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -291,7 +291,6 @@ public static void SetDeadPlayersAsExiled() { foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) { - // RpcExile is already sets dead role types seer.RpcExile(); } SkipTasks = false; From e69c5d2aa2b802c67b07d827d29d42e480bb8047 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 27 Aug 2024 23:07:26 +0800 Subject: [PATCH 441/778] Fix bugs --- Modules/AntiBlackout.cs | 3 +++ Patches/ExilePatch.cs | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index c638eebd71..8924570280 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -293,6 +293,9 @@ public static void SetDeadPlayersAsExiled() { seer.RpcExile(); } + } + public static void ResetAfterMeeting() + { SkipTasks = false; ExilePlayerId = -1; } diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 6368d34c3c..c80a39422a 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -75,10 +75,10 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) var exiledPC = exiled.Object; // Reset player cam for exiled desync impostor - if (exiledPC.HasDesyncRole()) - { - exiledPC?.ResetPlayerCam(1f); - } + //if (exiledPC.HasDesyncRole()) + //{ + // exiledPC?.ResetPlayerCam(1f); + //} exiled.IsDead = true; exiled.PlayerId.SetDeathReason(PlayerState.DeathReason.Vote); @@ -189,8 +189,9 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) MurderPlayerPatch.AfterPlayerDeathTasks(player, player, true); }); - Main.AfterMeetingDeathPlayers.Clear(); + Main.AfterMeetingDeathPlayers.Clear(); + AntiBlackout.ResetAfterMeeting(); }, 1.2f, "AfterMeetingDeathPlayers Task"); } //This should happen shortly after the Exile Controller wrap up finished for clients From 46a94a139812ee6163504addefc0759322aa2059 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:39:37 +0800 Subject: [PATCH 442/778] Make ShipStatus patches more clear --- Modules/ExtendedPlayerControl.cs | 2 +- Patches/IntroPatch.cs | 11 +++- Patches/ShipStatusPatch.cs | 97 +++++++++++++++++++++++++++++++- Patches/VentSystemPatch.cs | 31 +--------- 4 files changed, 107 insertions(+), 34 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f828ae6252..59dbf66bc1 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -680,7 +680,7 @@ public static bool HasKillButton(this PlayerControl pc) _ => false }; } - public static bool CanUseVent(this PlayerControl player) => player.CanUseImpostorVentButton() || player.Data.Role.Role == RoleTypes.Engineer; + public static bool CanUseVent(this PlayerControl player) => player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer; public static bool CanUseImpostorVentButton(this PlayerControl pc) { if (!pc.IsAlive()) return false; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 830dd6a372..5bbd312bc7 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -628,15 +628,20 @@ public static void Postfix() PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; } + bool shouldPerformVentInteractions = false; foreach (var pc in PlayerControl.AllPlayerControls) { - VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; if (pc.BlockVentInteraction()) { - Utils.SetAllVentInteractions(); - break; + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + shouldPerformVentInteractions = true; } } + + if (shouldPerformVentInteractions) + { + Utils.SetAllVentInteractions(); + } } Logger.Info("OnDestroy", "IntroCutscene"); } diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index b3750c20ff..b93ee861e5 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -5,6 +5,7 @@ using TOHE.Roles.Neutral; using TOHE.Roles.Core; using static TOHE.Translator; +using TOHE.Patches; namespace TOHE; @@ -29,12 +30,13 @@ public static void Postfix(/*ShipStatus __instance*/) } } } + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] public static class MessageReaderUpdateSystemPatch { public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) { - if (systemType is + if (systemType is SystemTypes.Ventilation or SystemTypes.Security or SystemTypes.Decontamination @@ -68,6 +70,7 @@ or SystemTypes.Decontamination3 UpdateSystemPatch.Postfix(__instance, systemType, player, MessageReader.Get(reader).ReadByte()); } } + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(byte))] class UpdateSystemPatch { @@ -102,7 +105,7 @@ public static bool Prefix(ShipStatus __instance, if (player.Is(CustomRoles.Unlucky) && player.IsAlive() && (systemType is SystemTypes.Doors)) { - if (Unlucky.SuicideRand(player, Unlucky.StateSuicide.OpenDoor)) + if (Unlucky.SuicideRand(player, Unlucky.StateSuicide.OpenDoor)) return false; } @@ -149,6 +152,7 @@ private static void CheckAndOpenDoors(ShipStatus __instance, int amount, params } } } + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CloseDoorsOfType))] class ShipStatusCloseDoorsPatch { @@ -167,6 +171,7 @@ public static bool Prefix(/*ShipStatus __instance,*/ SystemTypes room) return allow; } } + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] class StartPatch { @@ -209,6 +214,7 @@ public static void Postfix() } } } + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.StartMeeting))] class StartMeetingPatch { @@ -227,6 +233,7 @@ public static void Postfix() } } } + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] class BeginPatch { @@ -237,6 +244,7 @@ public static void Postfix() //Should the initial setup of the host's position be done here? } } + [HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] class CheckTaskCompletionPatch { @@ -250,3 +258,88 @@ public static bool Prefix(ref bool __result) return true; } } + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Serialize))] +class ShipStatusSerializePatch +{ + // Patch the global way of Serializing ShipStatus + // If we are patching any other systemTypes, just add below like Ventilation. + public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] MessageWriter writer, [HarmonyArgument(1)] bool initialState, ref bool __result) + { + __result = false; + if (!AmongUsClient.Instance.AmHost) return true; + if (initialState) return true; + + // Original methods + short num = 0; + while ((int)num < SystemTypeHelpers.AllTypes.Length) + { + SystemTypes systemTypes = SystemTypeHelpers.AllTypes[(int)num]; + + if (systemTypes is SystemTypes.Ventilation) + { + // Skip Ventilation here + // Further new systems should skip original methods here and add new patches below. + num += 1; + continue; + } + + ISystemType systemType; + if (__instance.Systems.TryGetValue(systemTypes, out systemType) && systemType.IsDirty) // initialState used here in vanilla code. Removed it. + { + __result = true; + writer.StartMessage((byte)systemTypes); + systemType.Serialize(writer, initialState); + writer.EndMessage(); + } + num += 1; + } + + // Ventilation part + { + Logger.Info("doing Ventilation Serialize", "ShipStatusSerializePatch"); + // Serialize Ventilation with our own patches to clients specifically if needed + bool customVentilation = false; + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (pc.BlockVentInteraction()) + { + customVentilation = true; + } + } + + Logger.Info("customVentilation: " + customVentilation, "ShipStatusSerializePatch"); + var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); + if (ventilationSystem != null && ventilationSystem.IsDirty) + { + if (customVentilation) + { + Utils.SetAllVentInteractions(); + } + else + { + Logger.Info("vanilla update vents", "ShipStatusSerializePatch"); + var subwriter = MessageWriter.Get(SendOption.Reliable); + subwriter.StartMessage(5); + { + subwriter.Write(AmongUsClient.Instance.GameId); + subwriter.StartMessage(1); + { + subwriter.WritePacked(__instance.NetId); + subwriter.StartMessage((byte)SystemTypes.Ventilation); + ventilationSystem.Serialize(subwriter, false); + subwriter.EndMessage(); + } + subwriter.EndMessage(); + } + subwriter.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(subwriter); + subwriter.Recycle(); + } + ventilationSystem.IsDirty = false; + } + } + + return false; + } +} diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index dd5b3cd52d..68f599726d 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -3,28 +3,8 @@ namespace TOHE.Patches; -[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Serialize))] -class ShipStatusSerializePatch -{ - public static void Prefix(ShipStatus __instance, /*[HarmonyArgument(0)] MessageWriter writer,*/ [HarmonyArgument(1)] bool initialState) - { - if (!AmongUsClient.Instance.AmHost) return; - if (initialState) return; - bool cancel = false; - foreach (var pc in PlayerControl.AllPlayerControls) - { - if (pc.BlockVentInteraction()) - cancel = true; - } - var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); - if (cancel && ventilationSystem != null && ventilationSystem.IsDirty) - { - Utils.SetAllVentInteractions(); - ventilationSystem.IsDirty = false; - } - - } -} +// Patches here is also activated from ShipStatus.Serialize and IntroCutScene +// through Utils.SetAllVentInteractions [HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.Deteriorate))] static class VentSystemDeterioratePatch @@ -50,16 +30,11 @@ public static bool BlockVentInteraction(this PlayerControl pc) { if (!pc.AmOwner && !pc.IsModClient() && !pc.Data.IsDead && !pc.CanUseVent()) { - //foreach (var vent in ShipStatus.Instance.AllVents) - //{ - // if () - // return true; - //} - return true; } return false; } + public static void SerializeV2(VentilationSystem __instance, PlayerControl player = null) { foreach (var pc in PlayerControl.AllPlayerControls) From e6e883c82a1f8a695019de7237caef5450c97a9d Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:54:21 +0800 Subject: [PATCH 443/778] Add InGame check so custom vents is not triggered before role get assigned Add InGame check so custom vents is not triggered before role get assigned --- Patches/ShipStatusPatch.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index b93ee861e5..8f7fb387a5 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -300,18 +300,22 @@ public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] MessageWri Logger.Info("doing Ventilation Serialize", "ShipStatusSerializePatch"); // Serialize Ventilation with our own patches to clients specifically if needed bool customVentilation = false; - foreach (var pc in PlayerControl.AllPlayerControls) + + if (GameStates.IsInGame) { - if (pc.BlockVentInteraction()) + foreach (var pc in PlayerControl.AllPlayerControls) { - customVentilation = true; + if (pc.BlockVentInteraction()) + { + customVentilation = true; + } } } - Logger.Info("customVentilation: " + customVentilation, "ShipStatusSerializePatch"); var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); if (ventilationSystem != null && ventilationSystem.IsDirty) { + Logger.Info("customVentilation: " + customVentilation, "ShipStatusSerializePatch"); if (customVentilation) { Utils.SetAllVentInteractions(); From 84ddcaa7dd403cefa4f95f6b8e400a80e6d6c353 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:55:42 +0800 Subject: [PATCH 444/778] clear debug logging --- Patches/ShipStatusPatch.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 8f7fb387a5..866b3458b0 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -297,7 +297,7 @@ public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] MessageWri // Ventilation part { - Logger.Info("doing Ventilation Serialize", "ShipStatusSerializePatch"); + // Logger.Info("doing Ventilation Serialize", "ShipStatusSerializePatch"); // Serialize Ventilation with our own patches to clients specifically if needed bool customVentilation = false; @@ -315,14 +315,14 @@ public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] MessageWri var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); if (ventilationSystem != null && ventilationSystem.IsDirty) { - Logger.Info("customVentilation: " + customVentilation, "ShipStatusSerializePatch"); + // Logger.Info("customVentilation: " + customVentilation, "ShipStatusSerializePatch"); if (customVentilation) { Utils.SetAllVentInteractions(); } else { - Logger.Info("vanilla update vents", "ShipStatusSerializePatch"); + // Logger.Info("vanilla update vents", "ShipStatusSerializePatch"); var subwriter = MessageWriter.Get(SendOption.Reliable); subwriter.StartMessage(5); { From 33ae479f410455a5fa88978092215d7cecfc632e Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 28 Aug 2024 01:10:50 +0800 Subject: [PATCH 445/778] Add a blank line here so I have more commits --- Patches/VentSystemPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 68f599726d..427f3fdd88 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -104,6 +104,7 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); } + private static void RpcSerializeVent(this PlayerControl pc, VentilationSystem __instance) { MessageWriter writer = MessageWriter.Get(SendOption.None); From e0849fde7e671f5c935e745a55fa6fc66f008682 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 28 Aug 2024 19:58:30 +0800 Subject: [PATCH 446/778] Fix bugs --- Modules/AntiBlackout.cs | 23 +++++++++--- Modules/Utils.cs | 2 +- Patches/ExilePatch.cs | 26 ------------- Patches/IntroPatch.cs | 35 +++++++----------- Patches/ShipStatusPatch.cs | 21 ++++++++++- Patches/onGameStartedPatch.cs | 70 +++++++++++++++++++++-------------- 6 files changed, 94 insertions(+), 83 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 8924570280..bb7a29a862 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -5,6 +5,7 @@ using TOHE.Modules; using TOHE.Roles.Core; using static TOHE.SelectRolesPatch; +using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -256,8 +257,9 @@ public static void SetRealPlayerRoles() { if (isSelf) { - var isGuardianAngel = target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole()); - if (isGuardianAngel) + target.RpcExile(); + + if (target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole())) { changedRoleType = RoleTypes.GuardianAngel; } @@ -283,15 +285,24 @@ public static void SetRealPlayerRoles() } } } + target.RpcSetRoleDesync(changedRoleType, seer.GetClientId()); } - SetDeadPlayersAsExiled(); + ResetAllCooldown(); } - public static void SetDeadPlayersAsExiled() + private static void ResetAllCooldown() { - foreach (var seer in Main.AllPlayerControls.Where(x => x.Data.IsDead)) + foreach (var seer in Main.AllPlayerControls) { - seer.RpcExile(); + if (seer.IsAlive()) + { + seer.SetKillCooldown(); + seer.RpcResetAbilityCooldown(); + } + else if (seer.GetCustomRole().IsGhostRole() || seer.IsAnySubRole(x => x.IsGhostRole())) + { + seer.RpcResetAbilityCooldown(); + } } } public static void ResetAfterMeeting() diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 40ccb253cb..6dfddab1c7 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -563,7 +563,7 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = return false; } - if (playerData.Disconnected) return false; + if (States.Disconnected) return false; //if (playerData.Role.IsImpostor) // hasTasks = false; //Tasks are determined based on CustomRole diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index c80a39422a..2e944e3281 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -72,14 +72,6 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) if (CLThingy && exiled != null) { - var exiledPC = exiled.Object; - - // Reset player cam for exiled desync impostor - //if (exiledPC.HasDesyncRole()) - //{ - // exiledPC?.ResetPlayerCam(1f); - //} - exiled.IsDead = true; exiled.PlayerId.SetDeathReason(PlayerState.DeathReason.Vote); @@ -102,20 +94,8 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) { player.GetRoleClass()?.OnPlayerExiled(player, exiled); - // Check Anti BlackOut - //if (player.GetCustomRole().IsImpostor() - // && !player.IsAlive() // if player is dead impostor - // && AntiBlackout.BlackOutIsActive) // if Anti BlackOut is activated - //{ - // player.ResetPlayerCam(1f); - //} - // Check for remove pet player.RpcRemovePet(); - - // Reset Kill/Ability cooldown - player.ResetKillCooldown(); - player.RpcResetAbilityCooldown(); } Main.MeetingIsStarted = false; @@ -181,12 +161,6 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) if (x.Value == PlayerState.DeathReason.Suicide) player?.SetRealKiller(player, true); - // Reset player cam for dead desync impostor - //if (player.HasDesyncRole()) - //{ - // player?.ResetPlayerCam(1f); - //} - MurderPlayerPatch.AfterPlayerDeathTasks(player, player, true); }); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 2143499fb1..5587eab1f9 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -221,25 +221,9 @@ class CoBeginPatch { public static void Prefix() { - //if (RoleBasisChanger.IsChangeInProgress) return; - - if (GameStates.IsNormalGame) - { - foreach (var player in Main.AllPlayerControls) - { - Main.PlayerStates[player.PlayerId].InitTask(player); - } - - GameData.Instance.RecomputeTaskCounts(); - TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - } - GameStates.InGame = true; RPC.RpcVersionCheck(); - // Do not move this code, it should be executed at the very end to prevent a visual bug - Utils.DoNotifyRoles(ForceLoop: true); - if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch @@ -542,19 +526,26 @@ public static void Postfix() { if (!GameStates.AirshipIsActive) { - Main.AllPlayerControls.Do(pc => pc.RpcResetAbilityCooldown()); + foreach (var pc in Main.AllPlayerControls) + { + pc.RpcResetAbilityCooldown(); + } if (Options.FixFirstKillCooldown.GetBool() && Options.CurrentGameMode != CustomGameMode.FFA) { _ = new LateTask(() => { - Main.AllPlayerControls.Do(x => x.ResetKillCooldown()); - Main.AllPlayerControls.Where(x => (Main.AllPlayerKillCooldown[x.PlayerId] - 2f) > 0f).Do(pc => pc.SetKillCooldown(Options.FixKillCooldownValue.GetFloat() - 2f)); + foreach (var pc in Main.AllPlayerControls) + { + pc.ResetKillCooldown(); + + if ((Main.AllPlayerKillCooldown[pc.PlayerId] - 2f) > 0f) + { + pc.SetKillCooldown(Options.FixKillCooldownValue.GetFloat() - 2f); + } + } }, 2f, "Fix Kill Cooldown Task"); } } - - // Not entirely sure if this is really necessary - //_ = new LateTask(() => Main.AllPlayerControls.Do(pc => pc.RpcSetRoleDesync(RoleTypes.Shapeshifter, false, -3)), 2f, "Set Impostor For Server"); } if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) // Incase user has /up access diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index b3750c20ff..4ea73c0daa 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -228,12 +228,31 @@ public static void Postfix() } } [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] -class BeginPatch +class ShipStatusBeginPatch { + //Prevent code from running twice as it gets activated later in LateTask + public static bool RolesIsAssigned = false; + public static bool Prefix() + { + return RolesIsAssigned; + } public static void Postfix() { Logger.CurrentMethod(); + if (RolesIsAssigned && !Main.introDestroyed) + { + foreach (var player in Main.AllPlayerControls) + { + Main.PlayerStates[player.PlayerId].InitTask(player); + } + + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + + Utils.DoNotifyRoles(ForceLoop: true); + } + //Should the initial setup of the host's position be done here? } } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 0c2d848ca5..144572233c 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -10,7 +10,6 @@ using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -93,6 +92,7 @@ public static void Postfix(AmongUsClient __instance) ChatManager.ResetHistory(); ReportDeadBodyPatch.CanReport = []; Options.UsedButtonCount = 0; + ShipStatusBeginPatch.RolesIsAssigned = false; Main.RealOptionsData = new OptionBackupData(GameOptionsManager.Instance.CurrentGameOptions); @@ -331,7 +331,8 @@ public static void Postfix() { if (!AmongUsClient.Instance.AmHost) return; - //There is a delay of 0.8 seconds because after the player exits during the assign of desync roles, either a black screen will occur or the Scientist role will be set + // There is a delay of 1 seconds because after the player exits during the assign of desync roles, + // Either a black screen will occur or the Scientist role will be set _ = new LateTask(() => { try @@ -339,7 +340,7 @@ public static void Postfix() // Set roles SetRolesAfterSelect(); - // Assign tasks again + // Assign tasks ShipStatus.Instance.Begin(); } catch (Exception ex) @@ -347,7 +348,23 @@ public static void Postfix() Utils.ErrorEnd("Set Roles After Select In LateTask"); Utils.ThrowException(ex); } - }, 1f, "Set Role Types After Select"); + }, 1f, "Set Roles After Select"); + + // There is a delay of 1.5 seconds because after assign roles player data "Disconnected" does not allow assigning tasks due AU code side + _ = new LateTask(() => { + + try + { + ShipStatusBeginPatch.RolesIsAssigned = true; + // Assign tasks + ShipStatus.Instance.Begin(); + } + catch (Exception ex) + { + Utils.ErrorEnd("Set Tasks In LateTask"); + Utils.ThrowException(ex); + } + }, 1.5f, "Set Tasks For All Players"); } private static void SetRolesAfterSelect() { @@ -741,35 +758,34 @@ private static void SetSelfRoles() { foreach (var pc in Main.AllPlayerControls) { - var roleType = RoleMap[(pc.PlayerId, pc.PlayerId)].roleType; - - var stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(6); - stream.Write(AmongUsClient.Instance.GameId); - stream.WritePacked(pc.GetClientId()); + try { - RpcSetDisconnect(stream, true); - - //if (pc.OwnedByHost()) - //{ - // pc.SetRole(roleType); - //} + var roleType = RoleMap[(pc.PlayerId, pc.PlayerId)].roleType; - stream.StartMessage(2); - stream.WritePacked(pc.NetId); - stream.Write((byte)RpcCalls.SetRole); + var stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(6); + stream.Write(AmongUsClient.Instance.GameId); + stream.WritePacked(pc.GetClientId()); { - stream.Write((ushort)roleType); - stream.Write(true); //canOverrideRole + RpcSetDisconnect(stream, true); + + stream.StartMessage(2); + stream.WritePacked(pc.NetId); + stream.Write((byte)RpcCalls.SetRole); + { + stream.Write((ushort)roleType); + stream.Write(true); //canOverrideRole + } + stream.EndMessage(); + //Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); + + RpcSetDisconnect(stream, false); } stream.EndMessage(); - Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); - - RpcSetDisconnect(stream, false); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); } - stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); + catch { } } } private static void RpcSetDisconnect(MessageWriter stream, bool disconnected) From b0dcd77ccee6c6142b0909a4fb3f31a3bde89e41 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 28 Aug 2024 20:07:37 +0800 Subject: [PATCH 447/778] Some fix --- Modules/CustomRolesHelper.cs | 4 ++++ Roles/AddOns/Common/Rebirth.cs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 33d2da56dc..3189d943ec 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -737,6 +737,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; break; + case CustomRoles.Rebirth: + if (pc.Is(CustomRoles.Doppelganger)) return false; + break; + case CustomRoles.Youtuber: if (pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.NiceMini) diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index 6aab4d8e0b..d76422b80c 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -61,13 +61,13 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled var ViablePlayer = list.Where(x => x != pc).Shuffle(IRandom.Instance) .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && -/*All converters */ !x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer) && - !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.GetCustomRole().IsImpostor()); +/*All converters */ !x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer) && + !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.Is(CustomRoles.Doppelganger) && !x.GetCustomRole().IsImpostor()); if (ViablePlayer == null) { var tytyl = ColorString(GetRoleColor(CustomRoles.Rebirth), GetString("Rebirth").ToUpper()); - Utils.SendMessage(GetString("RebirthFailed"), pc.PlayerId, title: tytyl); + SendMessage(GetString("RebirthFailed"), pc.PlayerId, title: tytyl); return false; } Rebirths[pc.PlayerId]--; From 458a2061247ef82248a9df797c0ca5a144502915 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 28 Aug 2024 20:25:20 +0800 Subject: [PATCH 448/778] Fix Desync Shapeshifter --- Patches/onGameStartedPatch.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 144572233c..674dd326aa 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -658,14 +658,13 @@ private static void CreateRoleMap() if (isSelf) { if (isModded) - { - if (targetRole.GetDYRole() == RoleTypes.Shapeshifter) - targetRoleType = RoleTypes.Shapeshifter; - else - targetRoleType = RoleTypes.Crewmate; - } + targetRoleType = RoleTypes.Crewmate; else targetRoleType = RoleTypes.Impostor; + + // For Desync Shapeshifter + if (targetRole.GetDYRole() == RoleTypes.Shapeshifter) + targetRoleType = RoleTypes.Shapeshifter; } else { From 5d7e2921fe0d0df843b15d72b2b6307fc3277607 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 28 Aug 2024 20:29:33 +0800 Subject: [PATCH 449/778] Fix bug --- Patches/onGameStartedPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 674dd326aa..6481e939be 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -675,7 +675,7 @@ private static void CreateRoleMap() { if (seerRole.IsDesyncRole() && !isModded) { - if (target.GetCustomRole() is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) + if (targetRole is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) { targetRoleType = RoleTypes.Noisemaker; } From a475e445082cfebad8fcf9faa7e05ae198d97ca9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 28 Aug 2024 22:44:49 +0800 Subject: [PATCH 450/778] Fix bugs & some changes --- Modules/ExtendedPlayerControl.cs | 43 +++++++------ Modules/RoleBasisChanger.cs | 104 ------------------------------- Patches/onGameStartedPatch.cs | 13 +--- 3 files changed, 27 insertions(+), 133 deletions(-) delete mode 100644 Modules/RoleBasisChanger.cs diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 2eaca93210..1d2e667343 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -79,44 +79,49 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) { var newRoleType = newCustomRole.GetRoleTypes(); - foreach (var seer in Main.AllPlayerControls) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { + var isSelf = player.PlayerId == seer.PlayerId; + if (!isSelf && seer.HasDesyncRole() && !(seer.AmOwner || seer.IsModClient())) + { + newRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + } RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); + player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); } - player.RpcSetRole(newRoleType, true); } // When player change normal role to desync role else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) { - RoleTypes targetRoleType; + RoleTypes newRoleType; var isModded = player.OwnedByHost() || player.IsModClient(); - foreach (var target in Main.AllPlayerControls) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - var isSelf = player.PlayerId == target.PlayerId; + var isSelf = player.PlayerId == seer.PlayerId; if (isSelf) { if (isModded) - { - if (newCustomRole.GetDYRole() == RoleTypes.Shapeshifter) - { - targetRoleType = RoleTypes.Shapeshifter; - } - else - { - targetRoleType = RoleTypes.Crewmate; - } - } + newRoleType = RoleTypes.Crewmate; else + newRoleType = RoleTypes.Impostor; + + // For Desync Shapeshifter + if (newCustomRole.GetDYRole() is RoleTypes.Shapeshifter) { - targetRoleType = RoleTypes.Impostor; + newRoleType = RoleTypes.Shapeshifter; } } else { - targetRoleType = RoleTypes.Scientist; + if (!isModded && seer.HasDesyncRole()) + { + newRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + } + else + newRoleType = newCustomRole.GetRoleTypes(); } - RpcSetRoleReplacer.RoleMap[(playerId, target.PlayerId)] = (targetRoleType, newCustomRole); - player.RpcSetRoleDesync(targetRoleType, target.GetClientId()); + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); + player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); } } // When player change desync role to desync role diff --git a/Modules/RoleBasisChanger.cs b/Modules/RoleBasisChanger.cs deleted file mode 100644 index b6d26fd27e..0000000000 --- a/Modules/RoleBasisChanger.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace TOHE.Modules; - -// https://github.com/Rabek009/MoreGamemodes -// https://github.com/Gurge44/EndlessHostRoles - -//Not in use ever since the 2024.6.18 update break it - -//internal static class RoleBasisChanger -//{ -//public static bool IsChangeInProgress; -//public static bool SkipTasksAfterAssignRole; - -//public static void ChangeRoleBasis(this PlayerControl player, RoleTypes targetVNRole) -//{ -//if (!AmongUsClient.Instance.AmHost) return; -// -//if (player.OwnedByHost()) -//{ -//DestroyableSingleton.Instance.SetRole(player, targetVNRole); -//player.RpcSetRole(targetVNRole); -// player.SyncSettings(); -// -// Utils.NotifyRoles(SpecifySeer: player); -// Utils.NotifyRoles(SpecifyTarget: player); -// -// HudManager.Instance.SetHudActive(player, player.Data.Role, !GameStates.IsMeeting); -// -// return; -//} -// -//IsChangeInProgress = true; -//SkipTasksAfterAssignRole = true; -// -//Vector2 position = player.GetTruePosition(); -//PlayerControl PlayerPrefab = AmongUsClient.Instance.PlayerPrefab; -//PlayerControl newplayer = Object.Instantiate(PlayerPrefab, position, Quaternion.identity); -// -//newplayer.PlayerId = player.PlayerId; -//newplayer.FriendCode = player.FriendCode; -//newplayer.Puid = player.Puid; -// -//ClientData pclient = player.GetClient(); -// -//player.RpcTeleport(ExtendedPlayerControl.GetBlackRoomPosition()); -//AmongUsClient.Instance.Despawn(player); -//AmongUsClient.Instance.Spawn(newplayer, player.OwnerId); -//pclient.Character = newplayer; -// -//newplayer.OwnerId = player.OwnerId; -// -//pclient.InScene = true; -//pclient.IsReady = true; -// -//newplayer.MyPhysics.ResetMoveState(); -// -//GameData.Instance.RemovePlayer(player.PlayerId); -//GameData.Instance.AddPlayer(newplayer, newplayer.GetClient()); -// -//newplayer.RpcSetRoleDesync(targetVNRole, true, newplayer.GetClientId()); -//newplayer.RpcSetRole(targetVNRole, true); -// -//Used instead of GameData.Instance.DirtyAllData(); -// foreach (var innerNetObject in GameData.Instance.AllPlayers) -// { -// innerNetObject.SetDirtyBit(uint.MaxValue); -// } -// -// newplayer.ReactorFlash(0.2f); -// newplayer.RpcTeleport(position); -// -// _ = new LateTask(() => { IsChangeInProgress = false; }, 5f, "Desync Role Basis"); -// _ = new LateTask(() => { SkipTasksAfterAssignRole = false; }, 8f, "Wait End Intro", shoudLog: false); -// } -// -// [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] -// public static class InnerNetClientSpawnPatch -// { -// public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] InnerNetObject netObjParent, [HarmonyArgument(1)] int ownerId, [HarmonyArgument(2)] SpawnFlags flags) -// { -// if (!IsChangeInProgress) return true; -// -// ownerId = (ownerId == -3) ? __instance.ClientId : ownerId; -// MessageWriter messageWriter = __instance.Streams[0]; -// __instance.WriteSpawnMessage(netObjParent, ownerId, flags, messageWriter); -// return false; -// } -// } -// -// [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Despawn))] -// public static class InnerNetClientDespawnPatch -// { -// public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] InnerNetObject objToDespawn) -// { -// if (!IsChangeInProgress) return true; -// -// MessageWriter messageWriter = __instance.Streams[0]; -// messageWriter.StartMessage(5); -// messageWriter.WritePacked(objToDespawn.NetId); -// messageWriter.EndMessage(); -// __instance.RemoveNetObject(objToDespawn); -// return false; -// } -// } -//} \ No newline at end of file diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 6481e939be..2da9c37a45 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -663,7 +663,7 @@ private static void CreateRoleMap() targetRoleType = RoleTypes.Impostor; // For Desync Shapeshifter - if (targetRole.GetDYRole() == RoleTypes.Shapeshifter) + if (targetRole.GetDYRole() is RoleTypes.Shapeshifter) targetRoleType = RoleTypes.Shapeshifter; } else @@ -673,16 +673,9 @@ private static void CreateRoleMap() } else { - if (seerRole.IsDesyncRole() && !isModded) + if (!isModded && seerRole.IsDesyncRole()) { - if (targetRole is CustomRoles.Noisemaker or CustomRoles.NoisemakerTOHE) - { - targetRoleType = RoleTypes.Noisemaker; - } - else - { - targetRoleType = RoleTypes.Scientist; - } + targetRoleType = targetRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } else { From 9eeef6595e6d944885d45ca80964018ce7a96b7f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 28 Aug 2024 22:52:19 +0800 Subject: [PATCH 451/778] Change --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 1d2e667343..895ecc339f 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -68,7 +68,7 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* /// /// Changes the Role Basis of player during the game /// - /// The custom role to change into + /// The custom role to change and auto set role type for others public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole) { var playerRole = player.GetCustomRole(); From 66ac11dc7db3e486e61a84110edd8c98feaba0eb Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 30 Aug 2024 17:47:55 +0800 Subject: [PATCH 452/778] Fix bugs --- Modules/ExtendedPlayerControl.cs | 37 +++++-- Patches/onGameStartedPatch.cs | 170 +++++++++++++++---------------- 2 files changed, 115 insertions(+), 92 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 895ecc339f..222976816d 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -12,7 +12,6 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static TOHE.SelectRolesPatch; namespace TOHE; @@ -75,10 +74,10 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; var playerId = player.PlayerId; + var newRoleType = newCustomRole.GetRoleTypes(); // When player change desync role to normal role if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) { - var newRoleType = newCustomRole.GetRoleTypes(); foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { var isSelf = player.PlayerId == seer.PlayerId; @@ -93,7 +92,6 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new // When player change normal role to desync role else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) { - RoleTypes newRoleType; var isModded = player.OwnedByHost() || player.IsModClient(); foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { @@ -117,8 +115,6 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new { newRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } - else - newRoleType = newCustomRole.GetRoleTypes(); } RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); @@ -128,7 +124,32 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new // Or player change normal role to normal role else { - RpcSetRoleReplacer.RoleMap[(playerId, playerId)] = (newCustomRole.GetRoleTypes(), newCustomRole); + RpcSetRoleReplacer.RoleMap[(playerId, playerId)] = (newRoleType, newCustomRole); + + // player change basic role + if (playerRole.GetRoleTypes() != newRoleType) + { + // if desync role change Impostor to Shapeshift + if (newCustomRole.IsDesyncRole()) + { + player.RpcSetRoleDesync(newRoleType, player.GetClientId()); + return; + } + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + // Not change for desync role and player not modded except role map + var isModded = seer.AmOwner || seer.IsModClient(); + if (seer.HasDesyncRole() && !isModded) + { + var rememberRoleMapType = RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)].roleType; + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (rememberRoleMapType, newCustomRole); + continue; + } + + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); + player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); + } + } } } public static void RpcExile(this PlayerControl player) @@ -290,14 +311,16 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, /// public static void RpcRevive(this PlayerControl player) { + if (player == null) return; if (player.Data.IsDead == false) { Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); return; } - player.RpcChangeRoleBasis(player.GetCustomRole()); + player.RpcSetRoleDesync(RpcSetRoleReplacer.RoleMap[(player.PlayerId, player.PlayerId)].roleType, player.GetClientId()); Main.PlayerStates[player.PlayerId].IsDead = false; + player.SetDeathReason(PlayerState.DeathReason.etc); player.SetKillCooldown(); player.SyncGeneralOptions(); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 2da9c37a45..367f69f7aa 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -687,114 +687,114 @@ private static void CreateRoleMap() } } } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] - public static class RpcSetRoleReplacer +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole)), HarmonyPriority(Priority.High)] +public static class RpcSetRoleReplacer +{ + public static bool BlockSetRole = false; + public static Dictionary Senders = []; + public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; + public static void Initialize() { - public static bool BlockSetRole = false; - public static Dictionary Senders = []; - public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; - public static void Initialize() - { - Senders = []; - RoleMap = []; - BlockSetRole = true; - } - public static bool Prefix() - { - return !BlockSetRole; - } - public static void StartReplace() + Senders = []; + RoleMap = []; + BlockSetRole = true; + } + public static bool Prefix() + { + return !BlockSetRole; + } + public static void StartReplace() + { + foreach (var pc in Main.AllPlayerControls) { - foreach (var pc in Main.AllPlayerControls) - { - Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) - .StartMessage(pc.GetClientId()); - } + Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) + .StartMessage(pc.GetClientId()); } - public static void Release() + } + public static void Release() + { + foreach (var ((seerId, targetId), (roleType, _)) in RoleMap) { - foreach (var ((seerId, targetId), (roleType, _)) in RoleMap) - { - if (seerId == targetId) continue; + if (seerId == targetId) continue; - var seer = Utils.GetPlayerById(seerId); - var target = Utils.GetPlayerById(targetId); - if (seer == null || target == null) continue; - - if (seer.OwnedByHost()) - { - target.SetRole(roleType); - continue; - } + var seer = Utils.GetPlayerById(seerId); + var target = Utils.GetPlayerById(targetId); + if (seer == null || target == null) continue; - try - { - var sender = Senders[targetId]; - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) - .Write((ushort)roleType) - .Write(true) - .EndRpc(); - } - catch { } + if (seer.OwnedByHost()) + { + target.SetRole(roleType); + continue; } - SetSelfRoles(); - BlockSetRole = false; - Senders.Do(kvp => kvp.Value.SendMessage()); - EndReplace(); + try + { + var sender = Senders[targetId]; + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) + .Write((ushort)roleType) + .Write(true) + .EndRpc(); + } + catch { } } + SetSelfRoles(); + + BlockSetRole = false; + Senders.Do(kvp => kvp.Value.SendMessage()); + EndReplace(); + } - //Self roles set seperately so that we can trick the game into intro-cutsene via disconnecting everyone temporarily for client. - private static void SetSelfRoles() + //Self roles set seperately so that we can trick the game into intro-cutsene via disconnecting everyone temporarily for client. + private static void SetSelfRoles() + { + foreach (var pc in Main.AllPlayerControls) { - foreach (var pc in Main.AllPlayerControls) + try { - try + var roleType = RoleMap[(pc.PlayerId, pc.PlayerId)].roleType; + + var stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(6); + stream.Write(AmongUsClient.Instance.GameId); + stream.WritePacked(pc.GetClientId()); { - var roleType = RoleMap[(pc.PlayerId, pc.PlayerId)].roleType; + RpcSetDisconnect(stream, true); - var stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(6); - stream.Write(AmongUsClient.Instance.GameId); - stream.WritePacked(pc.GetClientId()); + stream.StartMessage(2); + stream.WritePacked(pc.NetId); + stream.Write((byte)RpcCalls.SetRole); { - RpcSetDisconnect(stream, true); - - stream.StartMessage(2); - stream.WritePacked(pc.NetId); - stream.Write((byte)RpcCalls.SetRole); - { - stream.Write((ushort)roleType); - stream.Write(true); //canOverrideRole - } - stream.EndMessage(); - //Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); - - RpcSetDisconnect(stream, false); + stream.Write((ushort)roleType); + stream.Write(true); //canOverrideRole } stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); - } - catch { } - } - } - private static void RpcSetDisconnect(MessageWriter stream, bool disconnected) - { - foreach (var pc in Main.AllPlayerControls) - { - pc.Data.Disconnected = disconnected; + //Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); - stream.StartMessage(1); - stream.WritePacked(pc.Data.NetId); - pc.Data.Serialize(stream, false); + RpcSetDisconnect(stream, false); + } stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); } + catch { } } - private static void EndReplace() + } + private static void RpcSetDisconnect(MessageWriter stream, bool disconnected) + { + foreach (var pc in Main.AllPlayerControls) { - Senders = null; + pc.Data.Disconnected = disconnected; + + stream.StartMessage(1); + stream.WritePacked(pc.Data.NetId); + pc.Data.Serialize(stream, false); + stream.EndMessage(); } } + private static void EndReplace() + { + Senders = null; + } } From 36273470172a4ed592bbf9a6ba251d263109cc9d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 30 Aug 2024 21:42:39 +0800 Subject: [PATCH 453/778] New role: Altruist --- Modules/ExtendedPlayerControl.cs | 34 ++++------ Modules/GameState.cs | 1 + Modules/OptionHolder.cs | 2 +- Modules/RPC.cs | 7 +- Modules/TargetArrow.cs | 2 +- Modules/Utils.cs | 15 +++-- Patches/ShipStatusPatch.cs | 4 +- Patches/onGameStartedPatch.cs | 4 +- Resources/Lang/en_US.json | 6 ++ Resources/roleColor.json | 1 + Roles/Core/RoleBase.cs | 1 + Roles/Crewmate/Altruist.cs | 109 +++++++++++++++++++++++++++++++ main.cs | 1 + 13 files changed, 152 insertions(+), 35 deletions(-) create mode 100644 Roles/Crewmate/Altruist.cs diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 222976816d..186e513d67 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -124,31 +124,14 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new // Or player change normal role to normal role else { - RpcSetRoleReplacer.RoleMap[(playerId, playerId)] = (newRoleType, newCustomRole); - - // player change basic role - if (playerRole.GetRoleTypes() != newRoleType) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - // if desync role change Impostor to Shapeshift - if (newCustomRole.IsDesyncRole()) + if (seer.HasDesyncRole()) { - player.RpcSetRoleDesync(newRoleType, player.GetClientId()); - return; - } - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - // Not change for desync role and player not modded except role map - var isModded = seer.AmOwner || seer.IsModClient(); - if (seer.HasDesyncRole() && !isModded) - { - var rememberRoleMapType = RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)].roleType; - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (rememberRoleMapType, newCustomRole); - continue; - } - - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); - player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); + newRoleType = RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)].roleType; } + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); + player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); } } } @@ -318,10 +301,11 @@ public static void RpcRevive(this PlayerControl player) return; } - player.RpcSetRoleDesync(RpcSetRoleReplacer.RoleMap[(player.PlayerId, player.PlayerId)].roleType, player.GetClientId()); Main.PlayerStates[player.PlayerId].IsDead = false; player.SetDeathReason(PlayerState.DeathReason.etc); + player.RpcChangeRoleBasis(Utils.GetRoleMap(player.PlayerId).Item2); player.SetKillCooldown(); + player.RpcResetAbilityCooldown(); player.SyncGeneralOptions(); } /// @@ -767,6 +751,10 @@ public static CountTypes GetCountTypes(this PlayerControl player) return Main.PlayerStates.TryGetValue(player.PlayerId, out var State) ? State.countTypes : CountTypes.None; } + public static DeadBody GetDeadBody(this NetworkedPlayerInfo playerData) + { + return UnityEngine.Object.FindObjectsOfType().FirstOrDefault(bead => bead.ParentId == playerData.PlayerId); + } public static void MarkDirtySettings(this PlayerControl player) { PlayerGameOptionsSender.SetDirty(player.PlayerId); diff --git a/Modules/GameState.cs b/Modules/GameState.cs index f59534b33a..39e51f8d1b 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -297,6 +297,7 @@ public enum DeathReason WrongAnswer, Starved, Armageddon, + Sacrificed, //Please add all new roles with deathreason & new deathreason in Utils.DeathReasonIsEnable(); etc = -1, diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c459b3d35c..345bd33083 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -622,7 +622,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29700 last id for roles/add-ons (Next use 29800) + // 29800 last id for roles/add-ons (Next use 29900) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 5cfb8a0c24..cba41d8ab4 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -134,6 +134,7 @@ public enum Sounds TaskComplete, TaskUpdateSound, ImpTransform, + SabotageSound, Test, } @@ -676,7 +677,8 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c break; case CustomRPC.KillFlash: Utils.FlashColor(new(1f, 0f, 0f, 0.3f)); - if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, Sounds.KillSound); + var playKillSound = reader.ReadBoolean(); + if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, playKillSound ? Sounds.KillSound : Sounds.SabotageSound); break; case CustomRPC.DumpLog: var target = Utils.GetPlayerById(reader.ReadByte()); @@ -979,6 +981,9 @@ public static void PlaySound(byte playerID, Sounds sound) case Sounds.ImpTransform: SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx, false, 0.8f); break; + case Sounds.SabotageSound: + SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 0.8f); + break; } } } diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index f0f8537322..aa944e5d7b 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -82,7 +82,7 @@ public static void RemoveAllTarget(byte seer) /// public static string GetArrows(PlayerControl seer, params byte[] targets) { - var arrows = ""; + var arrows = string.Empty; foreach (var arrowInfo in TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId && targets.Contains(ai.To)).ToArray()) { arrows += TargetArrows[arrowInfo]; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index bef041acce..58291282a0 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -285,7 +285,7 @@ public static bool KillFlashCheck(PlayerControl killer, PlayerControl target, Pl } return false; } - public static void KillFlash(this PlayerControl player) + public static void KillFlash(this PlayerControl player, bool playKillSound = true) { // Kill flash (blackout flash + reactor flash) bool ReactorCheck = IsActive(GetCriticalSabotageSystemType()); @@ -298,11 +298,12 @@ public static void KillFlash(this PlayerControl player) if (player.AmOwner) { FlashColor(new(1f, 0f, 0f, 0.3f)); - if (Constants.ShouldPlaySfx()) RPC.PlaySound(player.PlayerId, Sounds.KillSound); + if (Constants.ShouldPlaySfx()) RPC.PlaySound(player.PlayerId, playKillSound ? Sounds.KillSound : Sounds.SabotageSound); } else if (player.IsModClient()) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.KillFlash, SendOption.Reliable, player.GetClientId()); + writer.Write(playKillSound); AmongUsClient.Instance.FinishRpcImmediately(writer); } else if (!ReactorCheck) player.ReactorFlash(0f); //Reactor flash for vanilla @@ -567,7 +568,12 @@ public static string GetVitalText(byte playerId, bool RealKillerColor = false) return deathReason; } + public static (RoleTypes, CustomRoles) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) + { + if (targetId == byte.MaxValue) targetId = seerId; + return RpcSetRoleReplacer.RoleMap[(seerId, targetId)]; + } public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = true) { if (GameStates.IsLobby) return false; @@ -1523,7 +1529,7 @@ public static string GradientColorText(string startColorHex, string endColorHex, return gradientText; } - private static Color HexToColor(string hex) + public static Color HexToColor(string hex) { _ = ColorUtility.TryParseHtmlString("#" + hex, out var color); return color; @@ -1812,7 +1818,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl foreach (var seer in seerList) { // Do nothing when the seer is not present in the game - if (seer == null || seer.Data.Disconnected) continue; + if (seer == null) continue; // Only non-modded players if (seer.IsModClient()) continue; @@ -2255,6 +2261,7 @@ var Breason when BannedReason(Breason) => false, PlayerState.DeathReason.Slice => CustomRoles.Hawk.IsEnable(), PlayerState.DeathReason.BloodLet => CustomRoles.Bloodmoon.IsEnable(), PlayerState.DeathReason.Starved => CustomRoles.Baker.IsEnable(), + PlayerState.DeathReason.Sacrificed => CustomRoles.Altruist.IsEnable(), PlayerState.DeathReason.Kill => true, _ => true, }; diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 4ea73c0daa..9ad7d1d942 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -250,10 +250,8 @@ public static void Postfix() GameData.Instance.RecomputeTaskCounts(); TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - Utils.DoNotifyRoles(ForceLoop: true); + Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); } - - //Should the initial setup of the host's position be done here? } } [HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 367f69f7aa..da8f30dd22 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -783,13 +783,13 @@ private static void SetSelfRoles() } private static void RpcSetDisconnect(MessageWriter stream, bool disconnected) { - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { pc.Data.Disconnected = disconnected; stream.StartMessage(1); stream.WritePacked(pc.Data.NetId); - pc.Data.Serialize(stream, false); + pc.Data.Serialize(stream, true); stream.EndMessage(); } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 28426fd735..04d3d2ca93 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -225,6 +225,7 @@ "Admirer": "Admirer", "TimeMaster": "Time Master", "Crusader": "Crusader", + "Altruist": "Altruist", "Reverie": "Reverie", "Lookout": "Lookout", "Telecommunication": "Telecommunication", @@ -537,6 +538,7 @@ "AdmirerInfo": "Choose a player to side with you", "TimeMasterInfo": "Rewind time!", "CrusaderInfo": "Kill a player's attacker", + "AltruistInfo": "Revive a player", "ReverieInfo": "With each kill, your cooldown decreases", "LookoutInfo": "See through disguises", "TelecommunicationInfo": "Track device usage", @@ -841,6 +843,7 @@ "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", + "AltruistInfoLong": "(Crewmates):\nAs the Altruist, you can sacrifice yourself to revive a dead body using the «Report» button.\nNote: If a dead player has left the game, you report that body normally", "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", @@ -1451,6 +1454,7 @@ "CanVent": "Can Vent", "ImpostorVision": "Has Impostor Vision", "CanUseSabotage": "Can Sabotage", + "CanHaveAccessToVitals": "Can Have Access To Vitals", "CanKillImpostors": "Can Kill Impostors", "CanGuess": "Can Guess in Guesser Mode or as Guesser", "HideVote": "Hide Vote", @@ -1697,6 +1701,7 @@ "MadmateCountMode.Imp": "Impostors", "MadmateCountMode.Original": "Original Team", + "Altruist_DeadPlayerHasBeenRevived": "A Dead Player Has Been Revived!", "SnatchesWin": "Snatches victory", "DemonKillCooldown": "Attack Cooldown", "DemonHealthMax": "Player max health", @@ -1939,6 +1944,7 @@ "DeathReason.Armageddon": "Armageddon", "DeathReason.Starved": "Starved", "DeathReason.Equilibrium": "Equilibrium", + "DeathReason.Sacrificed": "Sacrificed", "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", "Alive": "Alive", "Disconnected": "Disconnected", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index bbeb4b1575..19ca862d51 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -37,6 +37,7 @@ "Transporter": "#42D1FF", "TimeManager": "#6495ed", "Veteran": "#a77738", + "Altruist": "#9b0202", "Bodyguard": "#185abd", "Deceiver": "#BE29EC", "Witness": "#e70052", diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 8103a2d0bd..19d37424a6 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -485,6 +485,7 @@ public enum GeneralOption CanVent, ImpostorVision, CanUseSabotage, + CanHaveAccessToVitals, // General settings CanKillImpostors, diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs new file mode 100644 index 0000000000..570ad632ce --- /dev/null +++ b/Roles/Crewmate/Altruist.cs @@ -0,0 +1,109 @@ +using AmongUs.GameOptions; +using TOHE.Roles.Core; + +namespace TOHE.Roles.Crewmate; + +internal class Altruist : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 29800; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Altruist); + + public override CustomRoles ThisRoleBase => CanHaveAccessToVitals.GetBool() ? CustomRoles.Scientist : CustomRoles.Crewmate; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; + //==================================================================\\ + + private static OptionItem CanHaveAccessToVitals; + private static OptionItem BatteryCooldown; + private static OptionItem BatteryDuration; + + private byte reviverPlayerId = byte.MaxValue; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Altruist); + CanHaveAccessToVitals = BooleanOptionItem.Create(Id + 10, GeneralOption.CanHaveAccessToVitals, true, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); + BatteryCooldown = IntegerOptionItem.Create(Id + 11, GeneralOption.ScientistBase_BatteryCooldown, new(1, 250, 1), 15, TabGroup.CrewmateRoles, false) + .SetParent(CanHaveAccessToVitals) + .SetValueFormat(OptionFormat.Seconds); + BatteryDuration = IntegerOptionItem.Create(Id + 12, GeneralOption.ScientistBase_BatteryDuration, new(1, 250, 1), 5, TabGroup.CrewmateRoles, false) + .SetParent(CanHaveAccessToVitals) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + reviverPlayerId = byte.MaxValue; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ScientistCooldown = BatteryCooldown.GetInt(); + AURoleOptions.ScientistBatteryCharge = BatteryDuration.GetInt(); + } + + public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) + { + if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId && deadBody != null && deadBody.Object != null) + { + var deadPlayer = deadBody.Object; + var deadPlayerId = deadPlayer.PlayerId; + var deadBodyObject = deadBody.GetDeadBody(); + reviverPlayerId = deadPlayerId; + + deadPlayer.RpcTeleport(deadBodyObject.transform.position); + deadPlayer.RpcRevive(); + + if (deadPlayer.GetCustomRole().IsGhostRole() || deadPlayer.IsAnySubRole(sub => sub.IsGhostRole())) + { + deadPlayer.GetRoleClass().Remove(deadPlayerId); + deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).Item2); + deadPlayer.GetRoleClass().Add(deadPlayerId); + } + + _Player.SetDeathReason(PlayerState.DeathReason.Sacrificed); + _Player.Data.IsDead = true; + _Player.RpcExileV2(); + Main.PlayerStates[_Player.PlayerId].SetDead(); + + _ = new LateTask(() => + { + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(Custom_Team.Impostor)) + { + TargetArrow.Add(pc.PlayerId, deadPlayerId); + pc.KillFlash(playKillSound: false); + pc.Notify(Translator.GetString("Altruist_DeadPlayerHasBeenRevived"), time: 2f); + } + } + Utils.NotifyRoles(); + }, 1f, "Notify Impostor about revive"); + return false; + } + + return true; + } + + public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (reviverPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; + return Utils.ColorString(Utils.HexToColor("#9b0202"), TargetArrow.GetArrows(seer)); + } + + //public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + //{ + // if (reviverPlayerId != byte.MaxValue) + // { + // foreach (var pc in Main.AllPlayerControls) + // { + // if (pc.Is(Custom_Team.Impostor)) + // { + // TargetArrow.Remove(pc.PlayerId, reviverPlayerId); + // continue; + // } + // } + // } + //} +} diff --git a/main.cs b/main.cs index 2510b14d0b..a9444d06e2 100644 --- a/main.cs +++ b/main.cs @@ -708,6 +708,7 @@ public enum CustomRoles Addict, Admirer, Alchemist, + Altruist, Bastion, Benefactor, Bodyguard, From 8fdddd229762cacbaa3d8d30c7b8e79093d29ef1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 30 Aug 2024 22:13:29 +0800 Subject: [PATCH 454/778] Fix intro not work for vanilla --- Patches/ShipStatusPatch.cs | 4 ---- Patches/onGameStartedPatch.cs | 11 ++++------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 9ad7d1d942..4069bdf514 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -232,10 +232,6 @@ class ShipStatusBeginPatch { //Prevent code from running twice as it gets activated later in LateTask public static bool RolesIsAssigned = false; - public static bool Prefix() - { - return RolesIsAssigned; - } public static void Postfix() { Logger.CurrentMethod(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index da8f30dd22..3313366d6e 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -339,9 +339,6 @@ public static void Postfix() { // Set roles SetRolesAfterSelect(); - - // Assign tasks - ShipStatus.Instance.Begin(); } catch (Exception ex) { @@ -350,9 +347,9 @@ public static void Postfix() } }, 1f, "Set Roles After Select"); - // There is a delay of 1.5 seconds because after assign roles player data "Disconnected" does not allow assigning tasks due AU code side - _ = new LateTask(() => { - + // There is a delay of 3 seconds because after assign roles player data "Disconnected" does not allow assigning tasks due AU code side + _ = new LateTask(() => + { try { ShipStatusBeginPatch.RolesIsAssigned = true; @@ -364,7 +361,7 @@ public static void Postfix() Utils.ErrorEnd("Set Tasks In LateTask"); Utils.ThrowException(ex); } - }, 1.5f, "Set Tasks For All Players"); + }, 3f, "Set Tasks For All Players"); } private static void SetRolesAfterSelect() { From c62c56a186b74da00cfcbcda577b865c741db3d4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 30 Aug 2024 22:15:30 +0800 Subject: [PATCH 455/778] Return --- Patches/ShipStatusPatch.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 4069bdf514..9ad7d1d942 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -232,6 +232,10 @@ class ShipStatusBeginPatch { //Prevent code from running twice as it gets activated later in LateTask public static bool RolesIsAssigned = false; + public static bool Prefix() + { + return RolesIsAssigned; + } public static void Postfix() { Logger.CurrentMethod(); From 4759be0be37ceeeec52949f311d05ecfb12aef30 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 01:14:33 +0800 Subject: [PATCH 456/778] Fix bugs --- Modules/ExtendedPlayerControl.cs | 55 ++++++++++++++++------- Modules/NameNotifyManager.cs | 27 +++++------ Modules/Utils.cs | 9 ++-- Roles/Crewmate/Altruist.cs | 77 +++++++++++++++++--------------- 4 files changed, 99 insertions(+), 69 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 186e513d67..eb2769b3d0 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -68,13 +68,15 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* /// Changes the Role Basis of player during the game /// /// The custom role to change and auto set role type for others - public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole) + public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false) { - var playerRole = player.GetCustomRole(); if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; var playerId = player.PlayerId; + var playerRole = Utils.GetRoleMap(playerId).customRole; var newRoleType = newCustomRole.GetRoleTypes(); + RoleTypes remeberRoleType; + // When player change desync role to normal role if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) { @@ -83,10 +85,13 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var isSelf = player.PlayerId == seer.PlayerId; if (!isSelf && seer.HasDesyncRole() && !(seer.AmOwner || seer.IsModClient())) { - newRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); - player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); + else + remeberRoleType = newRoleType; + + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seer.GetClientId()); } } // When player change normal role to desync role @@ -99,39 +104,57 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new if (isSelf) { if (isModded) - newRoleType = RoleTypes.Crewmate; + remeberRoleType = RoleTypes.Crewmate; else - newRoleType = RoleTypes.Impostor; + remeberRoleType = RoleTypes.Impostor; // For Desync Shapeshifter if (newCustomRole.GetDYRole() is RoleTypes.Shapeshifter) { - newRoleType = RoleTypes.Shapeshifter; + remeberRoleType = RoleTypes.Shapeshifter; } } else { if (!isModded && seer.HasDesyncRole()) { - newRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } + else + remeberRoleType = newRoleType; } - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); - player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seer.GetClientId()); } } // When player change desync role to desync role // Or player change normal role to normal role else { + var playerIsDesync = player.HasDesyncRole(); foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (seer.HasDesyncRole()) + if ((playerIsDesync && seer.PlayerId != playerId) || seer.HasDesyncRole()) + { + remeberRoleType = RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)].roleType; + } + else + remeberRoleType = newRoleType; + + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seer.GetClientId()); + } + } + + if (loggerRoleMap) + { + foreach (var seer in Main.AllPlayerControls) + { + foreach (var target in Main.AllPlayerControls) { - newRoleType = RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)].roleType; + RpcSetRoleReplacer.RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map); + Logger.Info($"seer {seer?.Data?.PlayerName}-{seer.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); } - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (newRoleType, newCustomRole); - player.RpcSetRoleDesync(newRoleType, seer.GetClientId()); } } } @@ -303,7 +326,7 @@ public static void RpcRevive(this PlayerControl player) Main.PlayerStates[player.PlayerId].IsDead = false; player.SetDeathReason(PlayerState.DeathReason.etc); - player.RpcChangeRoleBasis(Utils.GetRoleMap(player.PlayerId).Item2); + player.RpcChangeRoleBasis(Utils.GetRoleMap(player.PlayerId).customRole, true); player.SetKillCooldown(); player.RpcResetAbilityCooldown(); player.SyncGeneralOptions(); diff --git a/Modules/NameNotifyManager.cs b/Modules/NameNotifyManager.cs index abfa49ed7e..dad4f8ddc9 100644 --- a/Modules/NameNotifyManager.cs +++ b/Modules/NameNotifyManager.cs @@ -1,25 +1,26 @@ using Hazel; +using UnityEngine; namespace TOHE; public static class NameNotifyManager { - public static readonly Dictionary Notice = []; + public static readonly Dictionary Notice = []; public static void Reset() => Notice.Clear(); public static bool Notifying(this PlayerControl pc) => Notice.ContainsKey(pc.PlayerId); public static void Notify(this PlayerControl pc, string text, float time = 4f, bool sendInLog = true) { if (!AmongUsClient.Instance.AmHost || pc == null) return; if (!GameStates.IsInTask) return; - - if (!text.Contains("")) text = Utils.ColorString(Color.white, text); + if (!text.Contains("{text}"; Notice.Remove(pc.PlayerId); - Notice.Add(pc.PlayerId, new(text, Utils.GetTimeStamp() + (long)time)); + Notice.Add(pc.PlayerId, new(text, Utils.TimeStamp + (long)time)); SendRPC(pc.PlayerId); - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: false); - + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (sendInLog) Logger.Info($"New name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify"); } public static void OnFixedUpdate(PlayerControl player) @@ -29,7 +30,7 @@ public static void OnFixedUpdate(PlayerControl player) if (Notice.Any()) Notice.Clear(); return; } - if (Notice.ContainsKey(player.PlayerId) && Notice[player.PlayerId].Item2 < Utils.GetTimeStamp()) + if (Notice.ContainsKey(player.PlayerId) && Notice[player.PlayerId].TimeStamp < Utils.GetTimeStamp()) { Notice.Remove(player.PlayerId); Utils.NotifyRoles(SpecifySeer: player, ForceLoop: false); @@ -38,20 +39,20 @@ public static void OnFixedUpdate(PlayerControl player) public static bool GetNameNotify(PlayerControl player, out string name) { name = string.Empty; - if (!Notice.ContainsKey(player.PlayerId)) return false; - name = Notice[player.PlayerId].Item1; + if (!Notice.TryGetValue(player.PlayerId, out (string Text, long TimeStamp) value)) return false; + name = value.Text; return true; } private static void SendRPC(byte playerId) { if (!AmongUsClient.Instance.AmHost) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable); writer.Write(playerId); if (Notice.ContainsKey(playerId)) { writer.Write(true); - writer.Write(Notice[playerId].Item1); - writer.Write(Notice[playerId].Item2 - Utils.GetTimeStamp()); + writer.Write(Notice[playerId].Text); + writer.Write(Notice[playerId].TimeStamp - Utils.GetTimeStamp()); } else writer.Write(false); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -63,6 +64,6 @@ public static void ReceiveRPC(MessageReader reader) long now = Utils.GetTimeStamp(); if (reader.ReadBoolean()) Notice.Add(PlayerId, new(reader.ReadString(), now + (long)reader.ReadSingle())); - Logger.Info($"New name notify for {Main.AllPlayerNames[PlayerId]}: {Notice[PlayerId].Item1} ({Notice[PlayerId].Item2 - now}s)", "Name Notify"); + Logger.Info($"New name notify for {Main.AllPlayerNames[PlayerId]}: {Notice[PlayerId].Text} ({Notice[PlayerId].TimeStamp - now}s)", "Name Notify"); } } \ No newline at end of file diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 58291282a0..ee63218fd4 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -568,7 +568,7 @@ public static string GetVitalText(byte playerId, bool RealKillerColor = false) return deathReason; } - public static (RoleTypes, CustomRoles) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) + public static (RoleTypes roleType, CustomRoles customRole) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) { if (targetId == byte.MaxValue) targetId = seerId; @@ -1531,8 +1531,11 @@ public static string GradientColorText(string startColorHex, string endColorHex, public static Color HexToColor(string hex) { - _ = ColorUtility.TryParseHtmlString("#" + hex, out var color); - return color; + if (ColorUtility.TryParseHtmlString("#" + hex, out var color)) + { + return color; + } + return Color.white; } private static string ColorToHex(Color color) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 570ad632ce..3ef53b78d8 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -45,30 +45,30 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { - if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId && deadBody != null && deadBody.Object != null) + if (deadBody != null && deadBody.Object != null) { - var deadPlayer = deadBody.Object; - var deadPlayerId = deadPlayer.PlayerId; - var deadBodyObject = deadBody.GetDeadBody(); - reviverPlayerId = deadPlayerId; + if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) + { + var deadPlayer = deadBody.Object; + var deadPlayerId = deadPlayer.PlayerId; + var deadBodyObject = deadBody.GetDeadBody(); + reviverPlayerId = deadPlayerId; - deadPlayer.RpcTeleport(deadBodyObject.transform.position); - deadPlayer.RpcRevive(); + deadPlayer.RpcTeleport(deadBodyObject.transform.position); + deadPlayer.RpcRevive(); - if (deadPlayer.GetCustomRole().IsGhostRole() || deadPlayer.IsAnySubRole(sub => sub.IsGhostRole())) - { - deadPlayer.GetRoleClass().Remove(deadPlayerId); - deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).Item2); - deadPlayer.GetRoleClass().Add(deadPlayerId); - } + if (deadPlayer.GetCustomRole().IsGhostRole() || deadPlayer.IsAnySubRole(sub => sub.IsGhostRole())) + { + deadPlayer.GetRoleClass().Remove(deadPlayerId); + deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).customRole); + deadPlayer.GetRoleClass().Add(deadPlayerId); + } - _Player.SetDeathReason(PlayerState.DeathReason.Sacrificed); - _Player.Data.IsDead = true; - _Player.RpcExileV2(); - Main.PlayerStates[_Player.PlayerId].SetDead(); + _Player.SetDeathReason(PlayerState.DeathReason.Sacrificed); + _Player.Data.IsDead = true; + _Player.RpcExileV2(); + Main.PlayerStates[_Player.PlayerId].SetDead(); - _ = new LateTask(() => - { foreach (var pc in Main.AllPlayerControls) { if (pc.Is(Custom_Team.Impostor)) @@ -79,31 +79,34 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay } } Utils.NotifyRoles(); - }, 1f, "Notify Impostor about revive"); - return false; + return false; + } + else if (reporter.PlayerId == deadBody.PlayerId) + return false; } - + return true; } public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (reviverPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; - return Utils.ColorString(Utils.HexToColor("#9b0202"), TargetArrow.GetArrows(seer)); + Logger.Info($"{TargetArrow.GetArrows(seer)}", "Altruist"); + return Utils.ColorString(Utils.HexToColor("9b0202"), TargetArrow.GetArrows(seer, reviverPlayerId)); } - //public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) - //{ - // if (reviverPlayerId != byte.MaxValue) - // { - // foreach (var pc in Main.AllPlayerControls) - // { - // if (pc.Is(Custom_Team.Impostor)) - // { - // TargetArrow.Remove(pc.PlayerId, reviverPlayerId); - // continue; - // } - // } - // } - //} + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + if (reviverPlayerId != byte.MaxValue) + { + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.Is(Custom_Team.Impostor)) + { + TargetArrow.Remove(pc.PlayerId, reviverPlayerId); + continue; + } + } + } + } } From 704d752b6fd6912375fd320b38662a565576e843 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 01:37:39 +0800 Subject: [PATCH 457/778] Some fix for Altruist --- Roles/Crewmate/Altruist.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 3ef53b78d8..8cc9c8f658 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -17,7 +17,8 @@ internal class Altruist : RoleBase private static OptionItem BatteryCooldown; private static OptionItem BatteryDuration; - private byte reviverPlayerId = byte.MaxValue; + private byte ReviverPlayerId = byte.MaxValue; + private readonly static HashSet AllReviverPlayerId = []; public override void SetupCustomOption() { @@ -34,7 +35,8 @@ public override void SetupCustomOption() public override void Init() { - reviverPlayerId = byte.MaxValue; + ReviverPlayerId = byte.MaxValue; + AllReviverPlayerId.Clear(); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) @@ -52,7 +54,9 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay var deadPlayer = deadBody.Object; var deadPlayerId = deadPlayer.PlayerId; var deadBodyObject = deadBody.GetDeadBody(); - reviverPlayerId = deadPlayerId; + + ReviverPlayerId = deadPlayerId; + AllReviverPlayerId.Add(deadPlayerId); deadPlayer.RpcTeleport(deadBodyObject.transform.position); deadPlayer.RpcRevive(); @@ -81,7 +85,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay Utils.NotifyRoles(); return false; } - else if (reporter.PlayerId == deadBody.PlayerId) + else if (reporter.PlayerId == deadBody.PlayerId && reporter.PlayerId == ReviverPlayerId) return false; } @@ -90,20 +94,20 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (reviverPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; + if (ReviverPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; Logger.Info($"{TargetArrow.GetArrows(seer)}", "Altruist"); - return Utils.ColorString(Utils.HexToColor("9b0202"), TargetArrow.GetArrows(seer, reviverPlayerId)); + return Utils.ColorString(Utils.HexToColor("9b0202"), TargetArrow.GetArrows(seer, ReviverPlayerId)); } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - if (reviverPlayerId != byte.MaxValue) + if (ReviverPlayerId != byte.MaxValue) { foreach (var pc in Main.AllAlivePlayerControls) { if (pc.Is(Custom_Team.Impostor)) { - TargetArrow.Remove(pc.PlayerId, reviverPlayerId); + TargetArrow.Remove(pc.PlayerId, ReviverPlayerId); continue; } } From c06ac06d967e6ddccebc6a7c767effbe4be4b70f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 02:29:30 +0800 Subject: [PATCH 458/778] Fix bugs --- Patches/IntroPatch.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index f9b0827393..66ca0529f9 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -161,7 +161,8 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() sb.Append("------------Player Names------------\n"); foreach (var pc in allPlayerControlsArray) { - sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", "")})\n"); + if (pc == null) continue; + sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{Main.AllPlayerNames[pc.PlayerId].PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", "")})\n"); pc.cosmetics.nameText.text = pc.name; } @@ -172,7 +173,8 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() { foreach (var pc in allPlayerControlsArray) { - sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc?.Data?.PlayerName?.PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); + if (pc == null) continue; + sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{Main.AllPlayerNames[pc.PlayerId].PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); } } else @@ -190,7 +192,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() byte[] logBytes = Encoding.UTF8.GetBytes(logStringBuilder.ToString()); byte[] encryptedBytes = EncryptDES(logBytes, $"TOHE{PlayerControl.LocalPlayer.PlayerId}00000000"[..8]); string encryptedString = Convert.ToBase64String(encryptedBytes); - sb.Append(encryptedString); + sb.Append(encryptedString + "\n"); } catch (Exception ex) { From f08438ac25dc388162e73d62e500ca49a6b4c917 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 14:50:26 +0800 Subject: [PATCH 459/778] Fix null errors --- Modules/Utils.cs | 2 +- Patches/MeetingHudPatch.cs | 5 +++-- Patches/PlayerControlPatch.cs | 14 ++++++++------ Roles/AddOns/Common/Evader.cs | 36 ++++++++++++++++++++++++++++++++++- Roles/Neutral/Glitch.cs | 14 +++++--------- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 35d61e93de..d18885f87b 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1993,7 +1993,7 @@ static int GetInfoSize(string RoleInfo) foreach (var realTarget in targetList) { // if the target is the seer itself, do nothing - if (realTarget.PlayerId == seer.PlayerId) continue; + if (realTarget == null || (realTarget.PlayerId == seer.PlayerId)) continue; var target = realTarget; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 99b71f8c2f..ab32a4c4d5 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -739,6 +739,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta Dictionary dic = []; Collector.Clear(); Tiebreaker.Clear(); + Evader.RememberRandom(); // |Voted By| Number of Times Voted For foreach (var ps in __instance.playerStates) @@ -770,7 +771,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta //Add votes for roles var pc = GetPlayerById(ps.TargetPlayerId); - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, pc.GetCustomRole()) + if (pc != null && CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, pc.GetCustomRole()) && ps.TargetPlayerId != ps.VotedFor && ps != null) VoteNum += ps.TargetPlayerId.GetRoleClassById().AddRealVotesNum(ps); // returns + 0 or given role value (+/-) @@ -807,7 +808,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta } //Set influenced vote num to zero while counting votes, and count influenced vote upon finishing influenced check - if (target.Is(CustomRoles.Evader)) + if (target != null && target.Is(CustomRoles.Evader)) { Evader.CheckExile(ps.VotedFor, ref VoteNum); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 663737540c..a7c7f54503 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -955,7 +955,7 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta if (Aware.IsEnable) Aware.OnReportDeadBody(); Sleuth.OnReportDeadBody(player, target); - + Evader.ReportDeadBody(); } catch (Exception error) { @@ -1008,7 +1008,7 @@ class FixedUpdateInNormalGamePatch { private static readonly StringBuilder Mark = new(20); private static readonly StringBuilder Suffix = new(120); - private static readonly Dictionary BufferTime = []; + private static readonly Dictionary BufferTime = []; private static int LevelKickBufferTime = 20; public static async void Postfix(PlayerControl __instance) @@ -1020,8 +1020,10 @@ public static async void Postfix(PlayerControl __instance) byte id = __instance.PlayerId; if (AmongUsClient.Instance.AmHost && GameStates.IsInTask && ReportDeadBodyPatch.CanReport[id] && ReportDeadBodyPatch.WaitReport[id].Any()) { - if(Glitch.HasEnabled && !Glitch.OnCheckFixedUpdateReport(__instance, id)) - { } + if (Glitch.HasEnabled && Glitch.OnCheckFixedUpdateReport(id)) + { + Glitch.CancelReportInFixedUpdate(__instance, id); + } else { var info = ReportDeadBodyPatch.WaitReport[id][0]; @@ -1056,7 +1058,7 @@ public static Task DoPostfix(PlayerControl __instance) { if (!BufferTime.TryGetValue(player.PlayerId, out var timerLowLoad)) { - BufferTime.TryAdd(player.PlayerId, 30); + BufferTime[player.PlayerId] = 30; timerLowLoad = 30; } @@ -1178,7 +1180,7 @@ public static Task DoPostfix(PlayerControl __instance) min.OnFixedUpdates(player); } - if (!GameStates.IsLobby && player.Is(CustomRoles.Spurt) && !Mathf.Approximately(Main.AllPlayerSpeed[player.PlayerId], Spurt.StartingSpeed[player.PlayerId]) && !GameStates.IsInTask && !GameStates.IsMeeting) // fix ludicrous bug + if (!GameStates.IsLobby && !GameStates.IsInTask && !GameStates.IsMeeting && player.Is(CustomRoles.Spurt) && !Mathf.Approximately(Main.AllPlayerSpeed[player.PlayerId], Spurt.StartingSpeed[player.PlayerId])) // fix ludicrous bug { Main.AllPlayerSpeed[player.PlayerId] = Spurt.StartingSpeed[player.PlayerId]; player.MarkDirtySettings(); diff --git a/Roles/AddOns/Common/Evader.cs b/Roles/AddOns/Common/Evader.cs index bb87f27128..3a9549a8f5 100644 --- a/Roles/AddOns/Common/Evader.cs +++ b/Roles/AddOns/Common/Evader.cs @@ -9,7 +9,9 @@ public class Evader : IAddon private static OptionItem SkillLimitTimes; private static OptionItem ChanceNotExiled; + private static readonly Dictionary AlredyCheck = []; private static readonly Dictionary SkillLimit = []; + private static int RememberRandomForExile; public void SetupCustomOption() { @@ -23,20 +25,52 @@ public void SetupCustomOption() } public static void Init() { + AlredyCheck.Clear(); SkillLimit.Clear(); } public static void Add(byte playerId) { + AlredyCheck[playerId] = false; SkillLimit[playerId] = SkillLimitTimes.GetInt(); } + public static void ReportDeadBody() + { + if (AlredyCheck.Any()) + { + foreach (var evaderId in AlredyCheck.Keys) + { + AlredyCheck[evaderId] = false; + } + } + } + public static void RememberRandom() + { + RememberRandomForExile = IRandom.Instance.Next(1, 100); + } public static void CheckExile(byte evaderId, ref int VoteNum) { + CheckAddEvader(evaderId); + + if (AlredyCheck[evaderId] && RememberRandomForExile < ChanceNotExiled.GetInt()) + { + VoteNum = 0; + return; + } if (SkillLimit[evaderId] <= 0) return; - if (IRandom.Instance.Next(1, 100) < ChanceNotExiled.GetInt()) + if (RememberRandomForExile < ChanceNotExiled.GetInt()) { SkillLimit[evaderId]--; + AlredyCheck[evaderId] = true; VoteNum = 0; } } + private static void CheckAddEvader(byte evaderId) + { + if (!SkillLimit.ContainsKey(evaderId)) + SkillLimit[evaderId] = SkillLimitTimes.GetInt(); + + if (!AlredyCheck.ContainsKey(evaderId)) + AlredyCheck[evaderId] = false; + } } diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index 259ec63e55..bf46df93a2 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -260,16 +260,12 @@ public override bool OnCoEnterVentOthers(PlayerPhysics physics, int ventId) } return false; } - public static bool OnCheckFixedUpdateReport(PlayerControl __instance, byte id) + public static bool OnCheckFixedUpdateReport(byte id) => hackedIdList.ContainsKey(id); + public static void CancelReportInFixedUpdate(PlayerControl __instance, byte id) { - if (hackedIdList.ContainsKey(id)) - { - __instance.Notify(string.Format(GetString("HackedByGlitch"), "Report")); - Logger.Info("Dead Body Report Blocked (player is hacked by Glitch)", "FixedUpdate.ReportDeadBody"); - ReportDeadBodyPatch.WaitReport[id].Clear(); - return false; - } - return true; + __instance.Notify(string.Format(GetString("HackedByGlitch"), "Report")); + Logger.Info("Dead Body Report Blocked (player is hacked by Glitch)", "FixedUpdate.ReportDeadBody"); + ReportDeadBodyPatch.WaitReport[id].Clear(); } public static bool OnCheckMurderOthers(PlayerControl killer, PlayerControl target) { From 2d61bf8a456b276f123e5a910c533dede2e6cc15 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 15:08:55 +0800 Subject: [PATCH 460/778] Some fix for Altruist --- Modules/NameNotifyManager.cs | 2 +- Resources/Lang/en_US.json | 5 ++++- Roles/Crewmate/Altruist.cs | 40 +++++++++++++++++++++--------------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Modules/NameNotifyManager.cs b/Modules/NameNotifyManager.cs index dad4f8ddc9..858ad93dd5 100644 --- a/Modules/NameNotifyManager.cs +++ b/Modules/NameNotifyManager.cs @@ -8,7 +8,7 @@ public static class NameNotifyManager public static readonly Dictionary Notice = []; public static void Reset() => Notice.Clear(); public static bool Notifying(this PlayerControl pc) => Notice.ContainsKey(pc.PlayerId); - public static void Notify(this PlayerControl pc, string text, float time = 4f, bool sendInLog = true) + public static void Notify(this PlayerControl pc, string text, float time = 5f, bool sendInLog = true) { if (!AmongUsClient.Instance.AmHost || pc == null) return; if (!GameStates.IsInTask) return; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 04d3d2ca93..e49b20013c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -843,7 +843,7 @@ "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", - "AltruistInfoLong": "(Crewmates):\nAs the Altruist, you can sacrifice yourself to revive a dead body using the «Report» button.\nNote: If a dead player has left the game, you report that body normally", + "AltruistInfoLong": "(Crewmates):\nAs the Altruist, you can sacrifice yourself to revive a dead body using the «Report» button.\nNote: If a dead player has left the game, you report that body normally.\nAlso revived player cannot report self dead body", "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", @@ -1701,7 +1701,10 @@ "MadmateCountMode.Imp": "Impostors", "MadmateCountMode.Original": "Original Team", + "Altruist_RevivedDeadBodyCannotBeReported_Option": "Revived Dead Body Cannot Be Reported", + "Altruist_YouTriedReportRevivedDeadBody": "You Tried Report Revived Dead Body", "Altruist_DeadPlayerHasBeenRevived": "A Dead Player Has Been Revived!", + "SnatchesWin": "Snatches victory", "DemonKillCooldown": "Attack Cooldown", "DemonHealthMax": "Player max health", diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 8cc9c8f658..3f7de301cc 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -13,30 +13,33 @@ internal class Altruist : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ + private static OptionItem RevivedDeadBodyCannotBeReported; private static OptionItem CanHaveAccessToVitals; private static OptionItem BatteryCooldown; private static OptionItem BatteryDuration; - private byte ReviverPlayerId = byte.MaxValue; - private readonly static HashSet AllReviverPlayerId = []; + private byte RevivedPlayerId = byte.MaxValue; + private readonly static HashSet AllRevivedPlayerId = []; public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Altruist); - CanHaveAccessToVitals = BooleanOptionItem.Create(Id + 10, GeneralOption.CanHaveAccessToVitals, true, TabGroup.CrewmateRoles, false) + RevivedDeadBodyCannotBeReported = BooleanOptionItem.Create(Id + 10, "Altruist_RevivedDeadBodyCannotBeReported_Option", true, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); - BatteryCooldown = IntegerOptionItem.Create(Id + 11, GeneralOption.ScientistBase_BatteryCooldown, new(1, 250, 1), 15, TabGroup.CrewmateRoles, false) + CanHaveAccessToVitals = BooleanOptionItem.Create(Id + 11, GeneralOption.CanHaveAccessToVitals, true, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); + BatteryCooldown = IntegerOptionItem.Create(Id + 12, GeneralOption.ScientistBase_BatteryCooldown, new(1, 250, 1), 15, TabGroup.CrewmateRoles, false) .SetParent(CanHaveAccessToVitals) .SetValueFormat(OptionFormat.Seconds); - BatteryDuration = IntegerOptionItem.Create(Id + 12, GeneralOption.ScientistBase_BatteryDuration, new(1, 250, 1), 5, TabGroup.CrewmateRoles, false) + BatteryDuration = IntegerOptionItem.Create(Id + 13, GeneralOption.ScientistBase_BatteryDuration, new(1, 250, 1), 5, TabGroup.CrewmateRoles, false) .SetParent(CanHaveAccessToVitals) .SetValueFormat(OptionFormat.Seconds); } public override void Init() { - ReviverPlayerId = byte.MaxValue; - AllReviverPlayerId.Clear(); + RevivedPlayerId = byte.MaxValue; + AllRevivedPlayerId.Clear(); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) @@ -54,9 +57,9 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay var deadPlayer = deadBody.Object; var deadPlayerId = deadPlayer.PlayerId; var deadBodyObject = deadBody.GetDeadBody(); - - ReviverPlayerId = deadPlayerId; - AllReviverPlayerId.Add(deadPlayerId); + + RevivedPlayerId = deadPlayerId; + AllRevivedPlayerId.Add(deadPlayerId); deadPlayer.RpcTeleport(deadBodyObject.transform.position); deadPlayer.RpcRevive(); @@ -75,18 +78,21 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay foreach (var pc in Main.AllPlayerControls) { - if (pc.Is(Custom_Team.Impostor)) + if (pc.Is(Custom_Team.Impostor) && pc.PlayerId != RevivedPlayerId) { TargetArrow.Add(pc.PlayerId, deadPlayerId); pc.KillFlash(playKillSound: false); - pc.Notify(Translator.GetString("Altruist_DeadPlayerHasBeenRevived"), time: 2f); + pc.Notify(Translator.GetString("Altruist_DeadPlayerHasBeenRevived")); } } Utils.NotifyRoles(); return false; } - else if (reporter.PlayerId == deadBody.PlayerId && reporter.PlayerId == ReviverPlayerId) + else if ((RevivedDeadBodyCannotBeReported.GetBool() || reporter.PlayerId == RevivedPlayerId) && deadBody.PlayerId == RevivedPlayerId) + { + reporter.Notify(Translator.GetString("Altruist_YouTriedReportRevivedDeadBody")); return false; + } } return true; @@ -94,20 +100,20 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (ReviverPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; + if (RevivedPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; Logger.Info($"{TargetArrow.GetArrows(seer)}", "Altruist"); - return Utils.ColorString(Utils.HexToColor("9b0202"), TargetArrow.GetArrows(seer, ReviverPlayerId)); + return Utils.ColorString(Utils.HexToColor("9b0202"), TargetArrow.GetArrows(seer, RevivedPlayerId)); } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - if (ReviverPlayerId != byte.MaxValue) + if (RevivedPlayerId != byte.MaxValue) { foreach (var pc in Main.AllAlivePlayerControls) { if (pc.Is(Custom_Team.Impostor)) { - TargetArrow.Remove(pc.PlayerId, ReviverPlayerId); + TargetArrow.Remove(pc.PlayerId, RevivedPlayerId); continue; } } From b974f47379e18a95a3b1cc198a29845aea138e6c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 16:43:53 +0800 Subject: [PATCH 461/778] Fix --- Patches/PlayerControlPatch.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index ac5707837f..a1e7f2f186 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -158,7 +158,7 @@ public static bool CheckForInvalidMurdering(PlayerControl killer, PlayerControl Logger.Info("The target is in an unkillable state and the kill is canceled", "CheckMurder"); return false; } - // Target Is Dead? + // Target Is Dead if (!target.IsAlive()) { Logger.Info("The target is in a dead state and the kill is canceled", "CheckMurder"); @@ -170,6 +170,12 @@ public static bool CheckForInvalidMurdering(PlayerControl killer, PlayerControl Logger.Info("In the meeting, the kill was canceled", "CheckMurder"); return false; } + // AntiBlackOut protect is active + if (AntiBlackout.SkipTasks) + { + Logger.Info("Checking while AntiBlackOut protect, the kill was canceled", "CheckMurder"); + return false; + } var divice = Options.CurrentGameMode == CustomGameMode.FFA ? 3000f : 1500f; float minTime = Mathf.Max(0.02f, AmongUsClient.Instance.Ping / divice * 6f); //Ping value is milliseconds (ms), so ÷ 2000 From 35604f4e0c6a175885960d0b55eafcbfc329eba0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 17:31:43 +0800 Subject: [PATCH 462/778] Change cause of death display (from EHR) --- Modules/Utils.cs | 7 ++++--- Patches/MeetingHudPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ee63218fd4..fe927bdf4b 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1886,6 +1886,7 @@ static int GetInfoSize(string RoleInfo) // Size of player roles string fontSize = isForMeeting ? "1.6" : "1.8"; + string fontSizeDeathReason = "1.6"; if (isForMeeting && (seer.GetClient().PlatformData.Platform is Platforms.Playstation or Platforms.Xbox or Platforms.Switch)) fontSize = "70%"; //logger.Info("NotifyRoles-Loop1-" + seer.GetNameWithRole() + ":START"); @@ -1946,7 +1947,7 @@ static int GetInfoSize(string RoleInfo) string SelfTaskText = GetProgressText(seer); string SelfRoleName = $"{seer.GetDisplayRoleAndSubName(seer, false)}{SelfTaskText}"; - string SelfDeathReason = seer.KnowDeathReason(seer) ? $" ({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(seer.PlayerId))})" : string.Empty; + string SelfDeathReason = seer.KnowDeathReason(seer) ? $"\n『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(seer.PlayerId))}』" : string.Empty; string SelfName = $"{ColorString(seer.GetRoleColor(), SeerRealName)}{SelfDeathReason}{SelfMark}"; // Add protected player icon from ShieldPersonDiedFirst @@ -2161,8 +2162,8 @@ static int GetInfoSize(string RoleInfo) } // ====== Target Death Reason for target (Death Reason visible ​​only to the seer) ====== - string TargetDeathReason = seer.KnowDeathReason(target) - ? $" ({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))})" : string.Empty; + string TargetDeathReason = seer.KnowDeathReason(target) + ? $"\n『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』" : string.Empty; // Devourer if (CustomRoles.Devourer.HasEnabled()) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 406b6febee..d331a1fb93 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1135,7 +1135,7 @@ public static void Postfix(MeetingHud __instance) } if (seer.KnowDeathReason(target)) - sb.Append($" ({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))})"); + sb.Append($"『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』"); sb.Append(seerRoleClass?.GetMark(seer, target, true)); sb.Append(CustomRoleManager.GetMarkOthers(seer, target, true)); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index a1e7f2f186..067aed3e2c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1185,7 +1185,7 @@ public static Task DoPostfix(PlayerControl __instance) { if (Main.playerVersion.TryGetValue(__instance.GetClientId(), out var ver)) { - if (Main.ForkId != ver.forkId) // フォークIDが違う場合 + if (Main.ForkId != ver.forkId) __instance.cosmetics.nameText.text = $"{ver.forkId}\n{__instance?.name}"; else if (Main.version.CompareTo(ver.version) == 0) __instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"{__instance.name}" : $"{ver.tag}\n{__instance?.name}"; @@ -1323,7 +1323,7 @@ public static Task DoPostfix(PlayerControl __instance) RealName = $"{RealName} "; string DeathReason = seer.Data.IsDead && seer.KnowDeathReason(target) - ? $" ({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})" : string.Empty; + ? $"\n『{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))}』" : string.Empty; // code from EHR (Endless Host Roles by: Gurge44) var currentText = target.cosmetics.nameText.text; @@ -1337,6 +1337,16 @@ public static Task DoPostfix(PlayerControl __instance) float offset = 0.2f; float colorBlind = -0.2f; + if (NameNotifyManager.Notice.TryGetValue(seer.PlayerId, out var notify) && notify.Text.Contains('\n')) + { + int count = notify.Text.Count(x => x == '\n'); + for (int i = 0; i < count; i++) + { + offset += 0.1f; + colorBlind -= 0.1f; + } + } + if (Suffix.ToString() != string.Empty) { // If the name is on two lines, the job title text needs to be moved up. @@ -1344,6 +1354,8 @@ public static Task DoPostfix(PlayerControl __instance) colorBlind -= 0.2f; } + if (!seer.IsAlive() && !target.IsAlive()) { offset += 0.1f; colorBlind -= 0.1f; } + RoleText.transform.SetLocalY(offset); target.cosmetics.colorBlindText.transform.SetLocalY(colorBlind); } From 19b77f0cc378c545358ddb1ca42d22eb0f7b4bca Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 18:08:33 +0800 Subject: [PATCH 463/778] Remove --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 762fc06151..d4f680c140 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -722,7 +722,7 @@ "AnonymousInfoLong": "(Impostors):\nAs the Anonymous, you can Shapeshift to force your target to report whoever you killed this round.\nIf you killed nobody that round, the target will report their own dead body as if they had died.\nNote: This does not work on Lazy nor Lazy Guy, and this ability will work regardless of whether the body can normally be reported.", "MinerInfoLong": "(Impostors):\nAs the Miner, you can shapeshift to teleport back to the last vent you were in.", "KillingMachineInfoLong": "(Impostors):\nAs the Killing Machine, you have a very short kill cooldown with tiny vision. However, you cannot vent, sabotage, report, nor call emergency meetings.\n\nNote: You will bypass any shields, killing bait and beartrap won't take any effect", - "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot (the Shapeshifting animation will display after you teleport; be careful).", + "EscapistInfoLong": "(Impostors):\nAs the Escapist, you can Mark a location by Shapeshifting. Shapeshift again to teleport back to the Marked spot", "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", From 3ed61bf4dba1cf0018b89e9b95c4ec17376f5ecd Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 18:45:19 +0800 Subject: [PATCH 464/778] Change --- Patches/IntroPatch.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index a25ecc5601..e0137de1d8 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -135,7 +135,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() foreach (var pc in allPlayerControlsArray) { if (pc == null) continue; - sb.Append($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{Main.AllPlayerNames[pc.PlayerId].PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); + sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{Main.AllPlayerNames[pc.PlayerId].PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); } } else @@ -145,7 +145,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() foreach (var pc in allPlayerControlsArray) { - logStringBuilder.AppendLine($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc?.Data?.PlayerName?.PadRight(20)}:{pc.GetAllRoleName().RemoveHtmlTags()}"); + logStringBuilder.AppendLine($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc?.Data?.PlayerName?.PadRight(20)}:{pc.GetAllRoleName().RemoveHtmlTags()}"); } try @@ -172,7 +172,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() { var text = new StringBuilder(); sb.Append(pc.AmOwner ? "[*]" : " "); - sb.Append($"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString()?.Replace("Standalone", ""),-11}"); + sb.Append($"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString()?.Replace("Standalone", string.Empty),-11}"); if (Main.playerVersion.TryGetValue(pc.GetClientId(), out PlayerVersion pv)) { From 2e254ffea04d4af4fbec8d1193e412c03d3181af Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 21:08:13 +0800 Subject: [PATCH 465/778] Spawn Player Patch --- Patches/ShipStatusPatch.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 9ad7d1d942..045a4086d5 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -5,6 +5,7 @@ using TOHE.Roles.Neutral; using TOHE.Roles.Core; using static TOHE.Translator; +using MS.Internal.Xml.XPath; namespace TOHE; @@ -254,6 +255,20 @@ public static void Postfix() } } } +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.SpawnPlayer))] +class ShipStatusSpawnPlayerPatch +{ + // Since SnapTo is unstable on the server side and after a meeting all players sometimes do not appear on the table + // So better to use RpcTeleport + public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) + { + Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / (float)numPlayers)); + Vector2 position = (initialSpawn ? __instance.InitialSpawnCenter : __instance.MeetingSpawnCenter) + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); + + player.RpcTeleport(position); + return false; + } +} [HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] class CheckTaskCompletionPatch { From 4ba167ac3009c54b14ee007534dc033227ded2a1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 21:38:24 +0800 Subject: [PATCH 466/778] Clear usings --- Modules/AntiBlackout.cs | 2 -- Patches/PlayerJoinAndLeftPatch.cs | 1 - Patches/ShipStatusPatch.cs | 3 +-- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index bb7a29a862..21987a5f41 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -4,8 +4,6 @@ using System.Runtime.CompilerServices; using TOHE.Modules; using TOHE.Roles.Core; -using static TOHE.SelectRolesPatch; -using static UnityEngine.GraphicsBuffer; namespace TOHE; diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 78111f7239..7785ded25d 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -10,7 +10,6 @@ using TOHE.Roles.Crewmate; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; -using static TOHE.SelectRolesPatch; namespace TOHE; diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 045a4086d5..7e6425a0d1 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -5,7 +5,6 @@ using TOHE.Roles.Neutral; using TOHE.Roles.Core; using static TOHE.Translator; -using MS.Internal.Xml.XPath; namespace TOHE; @@ -262,7 +261,7 @@ class ShipStatusSpawnPlayerPatch // So better to use RpcTeleport public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { - Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / (float)numPlayers)); + Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); Vector2 position = (initialSpawn ? __instance.InitialSpawnCenter : __instance.MeetingSpawnCenter) + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); player.RpcTeleport(position); From 424a943bcb73e365d9ec272e50fe5e3f77c3adcf Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 31 Aug 2024 21:40:27 +0800 Subject: [PATCH 467/778] Alpha 8 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index a9444d06e2..bd82788f7d 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0825.210.00070"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 7"; + public const string PluginVersion = "2024.0831.210.00080"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 8"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 7 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 8 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 30af8035dfe3b0f05fd5bdd0c05fcf5a64ca95bb Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 1 Sep 2024 22:57:55 +0800 Subject: [PATCH 468/778] Fix dissconects while role assign --- Modules/AntiBlackout.cs | 2 +- Modules/CustomRpcSender.cs | 11 ++ Modules/ExtendedPlayerControl.cs | 2 +- Patches/IntroPatch.cs | 45 ++--- Patches/PlayerJoinAndLeftPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 302 +++++++++++++++++------------- 6 files changed, 210 insertions(+), 154 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 21987a5f41..49ff01de41 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -136,8 +136,8 @@ public static void SendGameData([CallerMemberName] string callerMethodName = "") { MessageWriter writer = MessageWriter.Get(SendOption.Reliable); writer.StartMessage(5); //0x05 GameData + writer.Write(AmongUsClient.Instance.GameId); { - writer.Write(AmongUsClient.Instance.GameId); writer.StartMessage(1); //0x01 Data { writer.WritePacked(playerinfo.NetId); diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 95a8173859..83bfb70960 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -1,3 +1,4 @@ +using AmongUs.GameOptions; using Hazel; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; @@ -227,3 +228,13 @@ public enum State Finished, //送信後 何もできない } } +public static class CustomRpcSenderExtensions +{ + public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) + { + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) + .Write((ushort)role) + .Write(true) + .EndRpc(); + } +} diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index eb2769b3d0..e1c42261f2 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -914,7 +914,7 @@ public static bool HasKillButton(this PlayerControl pc) var role = pc.GetCustomRole(); if (!role.IsImpostor()) { - return role.GetDYRole() == RoleTypes.Impostor; + return role.GetDYRole() is RoleTypes.Impostor or RoleTypes.Shapeshifter; } return role.GetVNRole() switch { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index e0137de1d8..cca12e45a4 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -25,6 +25,9 @@ public static void Prefix() { try { + // Sync players Data after intro + RpcSetRoleReplacer.RpcSetDisconnected(disconnected: false, doSync: true); + // Update name players Utils.DoNotifyRoles(NoCache: true); } @@ -524,29 +527,26 @@ public static void Postfix() if (AmongUsClient.Instance.AmHost) { - if (GameStates.IsNormalGame) + if (GameStates.IsNormalGame && !GameStates.AirshipIsActive) { - if (!GameStates.AirshipIsActive) + foreach (var pc in Main.AllPlayerControls) { - foreach (var pc in Main.AllPlayerControls) - { - pc.RpcResetAbilityCooldown(); - } - if (Options.FixFirstKillCooldown.GetBool() && Options.CurrentGameMode != CustomGameMode.FFA) + pc.RpcResetAbilityCooldown(); + } + if (Options.FixFirstKillCooldown.GetBool() && Options.CurrentGameMode != CustomGameMode.FFA) + { + _ = new LateTask(() => { - _ = new LateTask(() => + foreach (var pc in Main.AllPlayerControls) { - foreach (var pc in Main.AllPlayerControls) - { - pc.ResetKillCooldown(); + pc.ResetKillCooldown(); - if ((Main.AllPlayerKillCooldown[pc.PlayerId] - 2f) > 0f) - { - pc.SetKillCooldown(Options.FixKillCooldownValue.GetFloat() - 2f); - } + if (Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out var killTimer) && (killTimer - 2f) > 0f) + { + pc.SetKillCooldown(Options.FixKillCooldownValue.GetFloat() - 2f); } - }, 2f, "Fix Kill Cooldown Task"); - } + } + }, 2f, "Fix Kill Cooldown Task"); } } @@ -614,13 +614,14 @@ public static void Postfix() }; if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); } + } - var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); - if (amDesyncImpostor) - { - PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; - } + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); + if (amDesyncImpostor) + { + PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; } + Logger.Info("OnDestroy", "IntroCutscene"); } } diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 7785ded25d..1de9e5ceb1 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -463,7 +463,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client case DisconnectReasons.Hacking: Logger.SendInGame(string.Format(GetString("PlayerLeftByAU-Anticheat"), data?.PlayerName)); break; - case DisconnectReasons.Error: + case DisconnectReasons.Error when !GameStates.IsLobby: Logger.SendInGame(string.Format(GetString("PlayerLeftByError"), data?.PlayerName)); _ = new LateTask(() => { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3313366d6e..36459603ec 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -295,15 +295,6 @@ public static void Prefix() // Block "RpcSetRole" for set desync roles for some players RpcSetRoleReplacer.Initialize(); - // Set GM for Host - if (Main.EnableGM.Value && Options.CurrentGameMode == CustomGameMode.Standard) - { - PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); - PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate, true); - PlayerControl.LocalPlayer.Data.IsDead = true; - Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); - } - // Select custom roles / add-ons EAC.OriginalRoles = []; RoleAssign.StartSelect(); @@ -331,8 +322,7 @@ public static void Postfix() { if (!AmongUsClient.Instance.AmHost) return; - // There is a delay of 1 seconds because after the player exits during the assign of desync roles, - // Either a black screen will occur or the Scientist role will be set + //There is a delay of 1 seconds because after the player exits during the assign of desync roles, either a black screen will occur or the Scientist role will be set _ = new LateTask(() => { try @@ -345,7 +335,7 @@ public static void Postfix() Utils.ErrorEnd("Set Roles After Select In LateTask"); Utils.ThrowException(ex); } - }, 1f, "Set Roles After Select"); + }, 1f, "Set Role Types After Select"); // There is a delay of 3 seconds because after assign roles player data "Disconnected" does not allow assigning tasks due AU code side _ = new LateTask(() => @@ -353,6 +343,7 @@ public static void Postfix() try { ShipStatusBeginPatch.RolesIsAssigned = true; + // Assign tasks ShipStatus.Instance.Begin(); } @@ -387,24 +378,13 @@ private static void SetRolesAfterSelect() //Initialization of CustomRpcSender and RpcSetRoleReplacer RpcSetRoleReplacer.StartReplace(); - //Not in use rn, but is gonna make it able to have neutral players of the same team spawn together - //Important to remember that all team players need to have all teamplayers in their lists - //gonna make a seperate thing making so that it's a rolebasething in another PR. - //Dictionary> DesyncImpTeammates = []; - - //foreach (var blotnik in Main.AllPlayerControls) - //{ - // RoleAssign.RoleResult[blotnik].GetStaticRoleClass().SetDesyncImpostorBuddies(ref DesyncImpTeammates, blotnik); - //} - - CreateRoleMap(); + RpcSetRoleReplacer.AssignDesyncRoles(); + RpcSetRoleReplacer.AssignNormalRoles(); - // Set RoleType by "RpcSetRole" RpcSetRoleReplacer.Release(); foreach (var pc in Main.AllPlayerControls) { - pc.Data.IsDead = false; if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; // Skip if a custom role has already been assigned var role = CustomRoles.NotAssigned; switch (pc.Data.Role.Role) @@ -456,6 +436,8 @@ private static void SetRolesAfterSelect() foreach (var kv in RoleAssign.RoleResult) { + if (kv.Value.IsDesyncRole()) continue; + AssignCustomRole(kv.Value, Utils.GetPlayerById(kv.Key)); } @@ -487,7 +469,7 @@ private static void SetRolesAfterSelect() if (Utils.IsMethodOverridden(pc.GetRoleClass(), "UnShapeShiftButton")) { Main.UnShapeShifter.Add(pc.PlayerId); - Logger.Info($"Added {pc.GetRealName()} because of {pc.GetCustomRole()}", "UnShapeShift..OnGameStartedPatch"); + Logger.Info($"Added {pc.GetRealName()} because of {pc.GetCustomRole()}", "UnShapeShift.OnGameStartedPatch"); } var roleClass = pc.GetRoleClass(); @@ -498,7 +480,7 @@ private static void SetRolesAfterSelect() if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) { // Is Desync Shapeshifter - if (pc.HasDesyncRole()) + if (pc.AmOwner && pc.HasDesyncRole()) { foreach (var target in Main.AllPlayerControls) { @@ -571,14 +553,15 @@ private static void SetRolesAfterSelect() case CustomRoles.Evader: Evader.Add(pc.PlayerId); break; + case CustomRoles.Spurt: + Spurt.Add(); + break; default: break; } } } - Spurt.Add(); - EndOfSelectRolePatch: try @@ -587,7 +570,6 @@ private static void SetRolesAfterSelect() DestroyableSingleton.Instance.SetHudActive(true); } catch { } - //HudManager.Instance.Chat.SetVisible(true); foreach (var pc in Main.AllPlayerControls) pc.ResetKillCooldown(); @@ -629,74 +611,96 @@ private static void SetRolesAfterSelect() { Utils.ErrorEnd("Set Roles After Select"); Utils.ThrowException(ex); - Logger.Error(ex.ToString(), "SetRolesAfterSelect"); } } - private static void AssignCustomRole(CustomRoles role, PlayerControl player) + public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) { if (player == null) return; + var hostId = PlayerControl.LocalPlayer.PlayerId; + var isHost = player.PlayerId == hostId; + Main.PlayerStates[player.PlayerId].SetMainRole(role); - Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role}", "AssignRoles"); + + var selfRole = isHost ? BaseRole == RoleTypes.Shapeshifter ? RoleTypes.Shapeshifter : hostBaseRole : BaseRole; + var othersRole = isHost ? RoleTypes.Crewmate : RoleTypes.Scientist; + + // Set Desync role for self and for others + foreach (var target in Main.AllPlayerControls) + { + var roleType = othersRole; + + if (RoleAssign.RoleResult[target.PlayerId].GetVNRole() is CustomRoles.Noisemaker) + roleType = RoleTypes.Noisemaker; + + rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? (roleType, RoleAssign.RoleResult[target.PlayerId]) : (selfRole, role); + } + + // Set Desync role for others + foreach (var seer in Main.AllPlayerControls.Where(x => player.PlayerId != x.PlayerId).ToArray()) + rolesMap[(seer.PlayerId, player.PlayerId)] = (othersRole, role); + + + if (!isHost) + { + RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); + //Set role for host + player.SetRole(othersRole); + } + + Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); } - private static void CreateRoleMap() + public static void MakeDesyncSender(Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap) { - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) + foreach (var seer in Main.AllPlayerControls) { - var isModded = seer.OwnedByHost() || seer.IsModClient(); - var seerRole = RoleAssign.RoleResult[seer.PlayerId]; - foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - RoleTypes targetRoleType; - var isSelf = seer.PlayerId == target.PlayerId; - var targetRole = RoleAssign.RoleResult[target.PlayerId]; - if (targetRole.IsDesyncRole()) - { - if (isSelf) - { - if (isModded) - targetRoleType = RoleTypes.Crewmate; - else - targetRoleType = RoleTypes.Impostor; - - // For Desync Shapeshifter - if (targetRole.GetDYRole() is RoleTypes.Shapeshifter) - targetRoleType = RoleTypes.Shapeshifter; - } - else - { - targetRoleType = RoleTypes.Scientist; - } - } - else + if (seer.OwnedByHost()) continue; + + foreach (var target in Main.AllPlayerControls) + { + if (target.OwnedByHost()) continue; + + if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleMap)) { - if (!isModded && seerRole.IsDesyncRole()) + try { - targetRoleType = targetRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; - } - else - { - targetRoleType = targetRole.GetRoleTypes(); + var roleType = roleMap.Item1; + var sender = senders[seer.PlayerId]; + sender.RpcSetRole(seer, roleType, target.GetClientId()); } + catch + { } } - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, target.PlayerId)] = (targetRoleType, targetRole); - Logger.Info($"seer {seer?.Data?.PlayerName}-{target.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {targetRoleType}, {targetRole}", "Role Map"); } } } -} -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole)), HarmonyPriority(Priority.High)] + private static void AssignCustomRole(CustomRoles role, PlayerControl player) + { + if (player == null) return; + + Main.PlayerStates[player.PlayerId].SetMainRole(role); + //Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role}", "AssignCustomRoles"); + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] public static class RpcSetRoleReplacer { public static bool BlockSetRole = false; public static Dictionary Senders = []; + public static Dictionary StoragedData = []; + public static Dictionary DataDisconnected = []; public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; + // List of Senders that do not require additional writing because SetRoleRpc has already been written by another process such as Position Desync + public static List OverriddenSenderList = []; public static void Initialize() { + BlockSetRole = true; Senders = []; RoleMap = []; - BlockSetRole = true; + StoragedData = []; + DataDisconnected = []; + OverriddenSenderList = []; } public static bool Prefix() { @@ -706,92 +710,132 @@ public static void StartReplace() { foreach (var pc in Main.AllPlayerControls) { + if (pc.OwnedByHost()) continue; + Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) .StartMessage(pc.GetClientId()); } } - public static void Release() + public static void AssignDesyncRoles() { - foreach (var ((seerId, targetId), (roleType, _)) in RoleMap) + // Assign desync roles + foreach (var (playerId, role) in RoleAssign.RoleResult.Where(x => x.Value.IsDesyncRole())) + SelectRolesPatch.AssignDesyncRole(role, Utils.GetPlayerById(playerId), Senders, RoleMap, BaseRole: role.GetDYRole()); + + // Set Desync RoleType by "RpcSetRole" + SelectRolesPatch.MakeDesyncSender(Senders, RoleMap); + } + public static void AssignNormalRoles() + { + foreach (var (playerId, role) in RoleAssign.RoleResult) { - if (seerId == targetId) continue; + var player = Utils.GetPlayerById(playerId); + if (player == null || role.IsDesyncRole()) continue; + + var roleType = role.GetRoleTypes(); - var seer = Utils.GetPlayerById(seerId); - var target = Utils.GetPlayerById(targetId); - if (seer == null || target == null) continue; + if (!player.OwnedByHost()) + StoragedData.Add(player, roleType); - if (seer.OwnedByHost()) + foreach (var target in Main.AllPlayerControls) { - target.SetRole(roleType); - continue; + if (target.HasDesyncRole()) continue; + + RoleMap[(target.PlayerId, playerId)] = (roleType, role); } - try + Logger.Info($"Set original role type => {player.GetRealName()}: {role} => {role.GetRoleTypes()}", "AssignNormalRoles"); + } + } + public static void Release() + { + foreach (var sender in Senders) + { + if (OverriddenSenderList.Contains(sender.Value)) continue; + if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) + throw new InvalidOperationException("A CustomRpcSender had Invalid State."); + + foreach (var (seer, roleType) in StoragedData) { - var sender = Senders[targetId]; - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetRole, seer.GetClientId()) - .Write((ushort)roleType) - .Write(true) - .EndRpc(); + try + { + seer.SetRole(roleType); + sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) + .Write((ushort)roleType) + .Write(true) + .EndRpc(); + } + catch + { } } - catch { } + sender.Value.EndMessage(); } - SetSelfRoles(); - + BlockSetRole = false; Senders.Do(kvp => kvp.Value.SendMessage()); EndReplace(); - } - //Self roles set seperately so that we can trick the game into intro-cutsene via disconnecting everyone temporarily for client. - private static void SetSelfRoles() + SetRoleForHost(); + } + private static void EndReplace() { - foreach (var pc in Main.AllPlayerControls) + Senders = null; + OverriddenSenderList = null; + StoragedData = null; + } + private static void SetRoleForHost() + { + try { - try + RpcSetDisconnected(disconnected: true, doSync: true); + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - var roleType = RoleMap[(pc.PlayerId, pc.PlayerId)].roleType; - - var stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(6); - stream.Write(AmongUsClient.Instance.GameId); - stream.WritePacked(pc.GetClientId()); + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - RpcSetDisconnect(stream, true); - - stream.StartMessage(2); - stream.WritePacked(pc.NetId); - stream.Write((byte)RpcCalls.SetRole); - { - stream.Write((ushort)roleType); - stream.Write(true); //canOverrideRole - } - stream.EndMessage(); - //Logger.Info($"SetSelfRole to:{pc?.name}({pc.GetClientId()}) player:{pc?.name}({roleType})", "★RpcSetRole"); + if (!target.OwnedByHost()) continue; - RpcSetDisconnect(stream, false); + RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map); + target.RpcSetRoleDesync(map.roleType, seer.GetClientId()); } - stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); } - catch { } + RpcSetDisconnected(disconnected: false, doSync: false); } + catch { } } - private static void RpcSetDisconnect(MessageWriter stream, bool disconnected) + public static void RpcSetDisconnected(bool disconnected, bool doSync) { - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + foreach (var playerinfo in GameData.Instance.AllPlayers) { - pc.Data.Disconnected = disconnected; + if (disconnected) + { + // if player left the game remember current data + DataDisconnected[playerinfo] = playerinfo.Disconnected; + playerinfo.Disconnected = disconnected; + playerinfo.IsDead = false; + } + else + { + playerinfo.Disconnected = DataDisconnected[playerinfo]; + playerinfo.IsDead = DataDisconnected[playerinfo]; + } + + if (!doSync) continue; + + MessageWriter writer = MessageWriter.Get(SendOption.None); + writer.StartMessage(5); //0x05 GameData + writer.Write(AmongUsClient.Instance.GameId); + { + writer.StartMessage(1); //0x01 Data + { + writer.WritePacked(playerinfo.NetId); + playerinfo.Serialize(writer, true); + } + writer.EndMessage(); + } + writer.EndMessage(); - stream.StartMessage(1); - stream.WritePacked(pc.Data.NetId); - pc.Data.Serialize(stream, true); - stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); } } - private static void EndReplace() - { - Senders = null; - } -} +} \ No newline at end of file From ee96f8edb223c789a4e833b5435a829cf295a5fe Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 1 Sep 2024 23:11:51 +0800 Subject: [PATCH 469/778] PolusShipStatus SpawnPlayer --- Patches/CheckGameEndPatch.cs | 13 +++++++++++++ Patches/ShipStatusPatch.cs | 35 ++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index e7cc8d9a44..0fab96ece7 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -21,6 +21,19 @@ public static bool Prefix(ref bool __result) return false; } } +[HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] +class CheckTaskCompletionPatch +{ + public static bool Prefix(ref bool __result) + { + if (Options.DisableTaskWin.GetBool() || Options.NoGameEnd.GetBool() || TaskState.InitialTotalTasks == 0 || Options.CurrentGameMode == CustomGameMode.FFA) + { + __result = false; + return false; + } + return true; + } +} [HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] class GameEndCheckerForNormal { diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 7e6425a0d1..66847b8aec 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -254,30 +254,43 @@ public static void Postfix() } } } + +/* + Since SnapTo is unstable on the server side and after a meeting all players sometimes do not appear on the table + So better to use RpcTeleport +*/ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.SpawnPlayer))] class ShipStatusSpawnPlayerPatch { - // Since SnapTo is unstable on the server side and after a meeting all players sometimes do not appear on the table - // So better to use RpcTeleport public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); Vector2 position = (initialSpawn ? __instance.InitialSpawnCenter : __instance.MeetingSpawnCenter) + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); - player.RpcTeleport(position); + player.RpcTeleport(position, sendInfoInLogs: false); return false; } } -[HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] -class CheckTaskCompletionPatch +[HarmonyPatch(typeof(PolusShipStatus), nameof(PolusShipStatus.SpawnPlayer))] +class PolusShipStatusSpawnPlayerPatch { - public static bool Prefix(ref bool __result) + public static bool Prefix(PolusShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { - if (Options.DisableTaskWin.GetBool() || Options.NoGameEnd.GetBool() || TaskState.InitialTotalTasks == 0 || Options.CurrentGameMode == CustomGameMode.FFA) + if (initialSpawn) { - __result = false; - return false; + ShipStatusSpawnPlayerPatch.Prefix(__instance, player, numPlayers, initialSpawn); } - return true; + else + { + int num1 = Mathf.FloorToInt(numPlayers / 2f); + int num2 = player.PlayerId % 15; + + Vector2 position = num2 >= num1 + ? __instance.MeetingSpawnCenter2 + Vector2.right * (num2 - num1) * 0.6f + : __instance.MeetingSpawnCenter + Vector2.right * num2 * 0.6f; + + player.RpcTeleport(position, sendInfoInLogs: false); + } + return false; } -} +} \ No newline at end of file From 83c44da4c3a2669bad34cc8705ae8649c5dfa93c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 1 Sep 2024 23:20:54 +0800 Subject: [PATCH 470/778] Return --- Patches/onGameStartedPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 36459603ec..fccf749865 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -611,6 +611,7 @@ private static void SetRolesAfterSelect() { Utils.ErrorEnd("Set Roles After Select"); Utils.ThrowException(ex); + Logger.Error(ex.ToString(), "SetRolesAfterSelect"); } } public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) From f93303766f7e972669890c8405d8c6ca335c9b8c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 1 Sep 2024 23:22:40 +0800 Subject: [PATCH 471/778] Use AllPlayerNames in exiled --- Patches/MeetingHudPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index f6ae46d891..24b9eb7e3a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -417,7 +417,7 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti var exileId = exiledPlayer.PlayerId; if (exileId is < 0 or > 254) return; - var realName = exiledPlayer.Object.GetRealName(isMeeting: true); + var realName = Main.AllPlayerNames[exiledPlayer.PlayerId]; Main.LastVotedPlayer = realName; var player = GetPlayerById(exiledPlayer.PlayerId); From 9a04ff3cb436b518dd19ee4b8c06305907cbc73b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 00:31:21 +0800 Subject: [PATCH 472/778] Change --- Roles/Crewmate/Altruist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 3f7de301cc..21076478ae 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -8,7 +8,7 @@ internal class Altruist : RoleBase //===========================SETUP================================\\ private const int Id = 29800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Altruist); - + public override bool IsExperimental => true; public override CustomRoles ThisRoleBase => CanHaveAccessToVitals.GetBool() ? CustomRoles.Scientist : CustomRoles.Crewmate; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ From dac6d0e918b7eca0aca7473cee8dfadeace914e9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 00:58:54 +0800 Subject: [PATCH 473/778] Goodbye summer 2024 --- Patches/onGameStartedPatch.cs | 2 +- Roles/Crewmate/Sheriff.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index fccf749865..a097517cc4 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -809,7 +809,7 @@ public static void RpcSetDisconnected(bool disconnected, bool doSync) { if (disconnected) { - // if player left the game remember current data + // if player left the game, remember current data DataDisconnected[playerinfo] = playerinfo.Disconnected; playerinfo.Disconnected = disconnected; playerinfo.IsDead = false; diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index 81a7b4fa83..81b31c0938 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -80,7 +80,7 @@ public override void Add(byte playerId) } private static void SetUpNeutralOptions(int Id) { - foreach (var neutral in CustomRolesHelper.AllRoles.Where(x => x.IsNeutral() && !x.IsTNA() && x is not CustomRoles.Glitch).ToArray()) + foreach (var neutral in CustomRolesHelper.AllRoles.Where(x => x.IsNeutral() && !x.IsTNA() && x is not CustomRoles.Glitch and not CustomRoles.Killer).ToArray()) { SetUpKillTargetOption(neutral, Id, true, CanKillNeutralsMode); Id++; From f50858e688a332e5f11a77f4cc3d51e6d2cea639 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 01:06:38 +0800 Subject: [PATCH 474/778] Change --- Patches/onGameStartedPatch.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index a097517cc4..821319bfff 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -131,16 +131,16 @@ public static void Postfix(AmongUsClient __instance) Logger.Error(msg, "CoStartGame"); } - foreach (var target in Main.AllPlayerControls) + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - foreach (var seer in Main.AllPlayerControls) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { var pair = (target.PlayerId, seer.PlayerId); Main.LastNotifyNames[pair] = target.name; } } - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { var outfit = pc.Data.DefaultOutfit; var colorId = pc.Data.DefaultOutfit.ColorId; @@ -361,7 +361,7 @@ private static void SetRolesAfterSelect() if (GameStates.IsHideNSeek) { GameOptionsSender.AllSenders.Clear(); - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { GameOptionsSender.AllSenders.Add( new PlayerGameOptionsSender(pc) @@ -383,7 +383,7 @@ private static void SetRolesAfterSelect() RpcSetRoleReplacer.Release(); - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; // Skip if a custom role has already been assigned var role = CustomRoles.NotAssigned; @@ -464,7 +464,7 @@ private static void SetRolesAfterSelect() GhostRoleAssign.Add(); - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (Utils.IsMethodOverridden(pc.GetRoleClass(), "UnShapeShiftButton")) { @@ -482,7 +482,7 @@ private static void SetRolesAfterSelect() // Is Desync Shapeshifter if (pc.AmOwner && pc.HasDesyncRole()) { - foreach (var target in Main.AllPlayerControls) + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { // Set all players as killable players target.Data.Role.CanBeKilled = true; @@ -571,7 +571,7 @@ private static void SetRolesAfterSelect() } catch { } - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) pc.ResetKillCooldown(); // Role types @@ -593,7 +593,7 @@ private static void SetRolesAfterSelect() } GameOptionsSender.AllSenders.Clear(); - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { GameOptionsSender.AllSenders.Add( new PlayerGameOptionsSender(pc) @@ -627,7 +627,7 @@ public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dict var othersRole = isHost ? RoleTypes.Crewmate : RoleTypes.Scientist; // Set Desync role for self and for others - foreach (var target in Main.AllPlayerControls) + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { var roleType = othersRole; @@ -653,11 +653,11 @@ public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dict } public static void MakeDesyncSender(Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap) { - foreach (var seer in Main.AllPlayerControls) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (seer.OwnedByHost()) continue; - foreach (var target in Main.AllPlayerControls) + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (target.OwnedByHost()) continue; @@ -709,7 +709,7 @@ public static bool Prefix() } public static void StartReplace() { - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (pc.OwnedByHost()) continue; @@ -738,7 +738,7 @@ public static void AssignNormalRoles() if (!player.OwnedByHost()) StoragedData.Add(player, roleType); - foreach (var target in Main.AllPlayerControls) + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (target.HasDesyncRole()) continue; @@ -805,7 +805,7 @@ private static void SetRoleForHost() } public static void RpcSetDisconnected(bool disconnected, bool doSync) { - foreach (var playerinfo in GameData.Instance.AllPlayers) + foreach (var playerinfo in GameData.Instance.AllPlayers.GetFastEnumerator()) { if (disconnected) { From a3db04bbee2fcf3c1292a83a6313724fc6ec831f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 01:53:17 +0800 Subject: [PATCH 475/778] Try fix assign roles --- Patches/IntroPatch.cs | 28 ++++++++++++++++------------ Patches/onGameStartedPatch.cs | 25 ++++--------------------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index cca12e45a4..b6b46fb059 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -23,19 +23,21 @@ public static void Prefix() _ = new LateTask(() => { - try - { - // Sync players Data after intro - RpcSetRoleReplacer.RpcSetDisconnected(disconnected: false, doSync: true); + // Sync players Data after intro + RpcSetRoleReplacer.RpcSetDisconnected(disconnected: false, doSync: true); - // Update name players - Utils.DoNotifyRoles(NoCache: true); - } - catch (Exception ex) - { - Utils.ThrowException(ex); - } - }, 0.35f, "Do Notify Roles In Show Intro"); + // Update name players + Utils.DoNotifyRoles(NoCache: true); + + }, 0.6f, "Set Disconnected"); + + _ = new LateTask(() => + { + ShipStatusBeginPatch.RolesIsAssigned = true; + + // Assign tasks + ShipStatus.Instance.Begin(); + }, 4f, "Assing Task"); } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] @@ -601,6 +603,8 @@ public static void Postfix() }, 3f, "Set UnShapeShift Button"); } + Utils.DoNotifyRoles(NoCache: true); + if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 821319bfff..50f8dff0f2 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -336,23 +336,6 @@ public static void Postfix() Utils.ThrowException(ex); } }, 1f, "Set Role Types After Select"); - - // There is a delay of 3 seconds because after assign roles player data "Disconnected" does not allow assigning tasks due AU code side - _ = new LateTask(() => - { - try - { - ShipStatusBeginPatch.RolesIsAssigned = true; - - // Assign tasks - ShipStatus.Instance.Begin(); - } - catch (Exception ex) - { - Utils.ErrorEnd("Set Tasks In LateTask"); - Utils.ThrowException(ex); - } - }, 3f, "Set Tasks For All Players"); } private static void SetRolesAfterSelect() { @@ -690,7 +673,7 @@ public static class RpcSetRoleReplacer public static bool BlockSetRole = false; public static Dictionary Senders = []; public static Dictionary StoragedData = []; - public static Dictionary DataDisconnected = []; + public static Dictionary DataDisconnected = []; public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; // List of Senders that do not require additional writing because SetRoleRpc has already been written by another process such as Position Desync public static List OverriddenSenderList = []; @@ -810,14 +793,14 @@ public static void RpcSetDisconnected(bool disconnected, bool doSync) if (disconnected) { // if player left the game, remember current data - DataDisconnected[playerinfo] = playerinfo.Disconnected; + DataDisconnected[playerinfo.PlayerId] = playerinfo.Disconnected; playerinfo.Disconnected = disconnected; playerinfo.IsDead = false; } else { - playerinfo.Disconnected = DataDisconnected[playerinfo]; - playerinfo.IsDead = DataDisconnected[playerinfo]; + playerinfo.Disconnected = DataDisconnected[playerinfo.PlayerId]; + playerinfo.IsDead = DataDisconnected[playerinfo.PlayerId]; } if (!doSync) continue; From 81416c7b923c3d5b4a78a831f0bb2e512fc89099 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 01:54:05 +0800 Subject: [PATCH 476/778] Hotfix 1 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index bd82788f7d..a21ad9cb85 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0831.210.00080"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 8"; + public const string PluginVersion = "2024.0831.210.00081"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 8 Hotfix 1"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 8 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 8 Hotfix 1 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From c0c63cd70f910668477ad9c6004748591838e311 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 20:56:10 +0800 Subject: [PATCH 477/778] Fix role assigns --- Modules/CustomRpcSender.cs | 2 +- Modules/ExtendedPlayerControl.cs | 6 +- Patches/IntroPatch.cs | 16 ++-- Patches/PlayerControlPatch.cs | 3 + Patches/onGameStartedPatch.cs | 144 +++++++++++++++++-------------- 5 files changed, 96 insertions(+), 75 deletions(-) diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 83bfb70960..5329e1c09b 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -234,7 +234,7 @@ public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, { sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) .Write((ushort)role) - .Write(true) + .Write(true) // canOverride .EndRpc(); } } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e1c42261f2..a2b8684451 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -47,16 +47,16 @@ public static void RpcSetCustomRole(byte PlayerId, CustomRoles role) AmongUsClient.Instance.FinishRpcImmediately(writer); } } - public static void SetRole(this PlayerControl player, RoleTypes role/*, bool canOverride = false*/) + public static void SetRole(this PlayerControl player, RoleTypes role, bool canOverride) { - player.StartCoroutine(player.CoSetRole(role, true)); + player.StartCoroutine(player.CoSetRole(role, canOverride)); } public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* bool canOverride,*/ int clientId) { if (player == null) return; if (AmongUsClient.Instance.ClientId == clientId) { - player.SetRole(role); + player.SetRole(role, true); return; } MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index b6b46fb059..71bd099dc3 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -23,19 +23,15 @@ public static void Prefix() _ = new LateTask(() => { - // Sync players Data after intro - RpcSetRoleReplacer.RpcSetDisconnected(disconnected: false, doSync: true); - - // Update name players + // Update name players for custom vanilla intro Utils.DoNotifyRoles(NoCache: true); - - }, 0.6f, "Set Disconnected"); + }, 0.35f, "Update names"); _ = new LateTask(() => { ShipStatusBeginPatch.RolesIsAssigned = true; - // Assign tasks + // Assign tasks after assign all roles, as it should be ShipStatus.Instance.Begin(); }, 4f, "Assing Task"); } @@ -516,6 +512,12 @@ public static void Postfix() Main.introDestroyed = true; + // Set roleAssigned as false for override role for modded players + // for vanilla clients we use "Data.Disconnected" + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + pc.roleAssigned = false; + } if (!GameStates.AirshipIsActive) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 63c9d6fa73..fc4f987853 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1795,6 +1795,9 @@ class PlayerControlSetRolePatch private static readonly Dictionary GhostRoles = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) { + // Skip after first assign + if (RpcSetRoleReplacer.BlockSetRole) return true; + canOverrideRole = true; if (GameStates.IsHideNSeek || __instance == null) return true; if (!ShipStatus.Instance.enabled || !AmongUsClient.Instance.AmHost) return true; diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 50f8dff0f2..537d0ffaff 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -624,13 +624,11 @@ public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dict foreach (var seer in Main.AllPlayerControls.Where(x => player.PlayerId != x.PlayerId).ToArray()) rolesMap[(seer.PlayerId, player.PlayerId)] = (othersRole, role); - - if (!isHost) - { - RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); - //Set role for host - player.SetRole(othersRole); - } + + RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); + // Set role for host + // canOverride should be false for the host during assign + player.SetRole(othersRole, false); Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); } @@ -638,11 +636,9 @@ public static void MakeDesyncSender(Dictionary senders, D { foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (seer.OwnedByHost()) continue; - foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (target.OwnedByHost()) continue; + if (seer.PlayerId == target.PlayerId && seer.PlayerId != PlayerControl.LocalPlayer.PlayerId) continue; if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleMap)) { @@ -667,12 +663,12 @@ private static void AssignCustomRole(CustomRoles role, PlayerControl player) //Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role}", "AssignCustomRoles"); } } -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole)), HarmonyPriority(Priority.High)] public static class RpcSetRoleReplacer { public static bool BlockSetRole = false; public static Dictionary Senders = []; - public static Dictionary StoragedData = []; + public static Dictionary StoragedData = []; public static Dictionary DataDisconnected = []; public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; // List of Senders that do not require additional writing because SetRoleRpc has already been written by another process such as Position Desync @@ -694,8 +690,6 @@ public static void StartReplace() { foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (pc.OwnedByHost()) continue; - Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) .StartMessage(pc.GetClientId()); } @@ -718,8 +712,7 @@ public static void AssignNormalRoles() var roleType = role.GetRoleTypes(); - if (!player.OwnedByHost()) - StoragedData.Add(player, roleType); + StoragedData.Add(playerId, roleType); foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { @@ -733,18 +726,26 @@ public static void AssignNormalRoles() } public static void Release() { - foreach (var sender in Senders) + foreach (var (targetId, sender) in Senders) { - if (OverriddenSenderList.Contains(sender.Value)) continue; - if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) + var target = Utils.GetPlayerById(targetId); + if (OverriddenSenderList.Contains(sender)) continue; + if (sender.CurrentState != CustomRpcSender.State.InRootMessage) throw new InvalidOperationException("A CustomRpcSender had Invalid State."); - foreach (var (seer, roleType) in StoragedData) + foreach (var (seerId, roleType) in StoragedData) { + var seer = Utils.GetPlayerById(seerId); + if (seer == null || target == null) continue; + if (targetId == seerId && targetId != PlayerControl.LocalPlayer.PlayerId) continue; + try { - seer.SetRole(roleType); - sender.Value.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) + // canOverride should be false for the host during assign + seer.SetRole(roleType, false); + + // send rpc set role for others clients + sender.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, target.GetClientId()) .Write((ushort)roleType) .Write(true) .EndRpc(); @@ -752,41 +753,67 @@ public static void Release() catch { } } - sender.Value.EndMessage(); + sender.EndMessage(); } BlockSetRole = false; Senders.Do(kvp => kvp.Value.SendMessage()); - EndReplace(); - SetRoleForHost(); + DummySetRole(); + + EndReplace(); } - private static void EndReplace() + public static void DummySetRole() { - Senders = null; - OverriddenSenderList = null; - StoragedData = null; + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + if (pc.PlayerId == PlayerControl.LocalPlayer.PlayerId) continue; + DummySetRole(pc); + } } - private static void SetRoleForHost() + public static void DummySetRole(PlayerControl target) { - try + if (target == null) return; + + RoleTypes roleType; + int targetClientId = target.GetClientId(); + + if (RoleMap.TryGetValue((target.PlayerId, target.PlayerId), out var roleMap)) { - RpcSetDisconnected(disconnected: true, doSync: true); - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - if (!target.OwnedByHost()) continue; + roleType = roleMap.roleType; + } + else + { + roleType = StoragedData[target.PlayerId]; + } - RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map); - target.RpcSetRoleDesync(map.roleType, seer.GetClientId()); - } - } - RpcSetDisconnected(disconnected: false, doSync: false); + var stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(6); + stream.Write(AmongUsClient.Instance.GameId); + stream.WritePacked(targetClientId); + { + RpcSetDisconnected(stream, true); + + stream.StartMessage(2); + stream.WritePacked(target.NetId); + stream.Write((byte)RpcCalls.SetRole); + stream.Write((ushort)roleType); + stream.Write(true); //canOverrideRole + stream.EndMessage(); + + RpcSetDisconnected(stream, false); } - catch { } + stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); + } + private static void EndReplace() + { + Senders = null; + OverriddenSenderList = null; + StoragedData = null; } - public static void RpcSetDisconnected(bool disconnected, bool doSync) + public static void RpcSetDisconnected(MessageWriter stream, bool disconnected) { foreach (var playerinfo in GameData.Instance.AllPlayers.GetFastEnumerator()) { @@ -794,32 +821,21 @@ public static void RpcSetDisconnected(bool disconnected, bool doSync) { // if player left the game, remember current data DataDisconnected[playerinfo.PlayerId] = playerinfo.Disconnected; - playerinfo.Disconnected = disconnected; + + playerinfo.Disconnected = true; playerinfo.IsDead = false; } else { - playerinfo.Disconnected = DataDisconnected[playerinfo.PlayerId]; - playerinfo.IsDead = DataDisconnected[playerinfo.PlayerId]; - } - - if (!doSync) continue; - - MessageWriter writer = MessageWriter.Get(SendOption.None); - writer.StartMessage(5); //0x05 GameData - writer.Write(AmongUsClient.Instance.GameId); - { - writer.StartMessage(1); //0x01 Data - { - writer.WritePacked(playerinfo.NetId); - playerinfo.Serialize(writer, true); - } - writer.EndMessage(); + var data = DataDisconnected.GetValueOrDefault(playerinfo.PlayerId, true); + playerinfo.Disconnected = data; + playerinfo.IsDead = data; } - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + stream.StartMessage(1); + stream.WritePacked(playerinfo.NetId); + playerinfo.Serialize(stream, false); + stream.EndMessage(); } } } \ No newline at end of file From 41566d58e4217c38542df5b91f507045a6394908 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 20:56:45 +0800 Subject: [PATCH 478/778] Alpha 8 (Yeah, again) --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index a21ad9cb85..e78abcc94f 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0831.210.00081"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 8 Hotfix 1"; + public const string PluginVersion = "2024.0902.210.00080"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 8"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 8 Hotfix 1 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 8 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 3deab8a1aea35fb5681afa10d1831adc577cba79 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 21:15:58 +0800 Subject: [PATCH 479/778] Change --- Patches/IntroPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 71bd099dc3..d6d2e15a10 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -513,7 +513,7 @@ public static void Postfix() Main.introDestroyed = true; // Set roleAssigned as false for override role for modded players - // for vanilla clients we use "Data.Disconnected" + // For override role for vanilla clients we use "Data.Disconnected" while assign foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { pc.roleAssigned = false; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index fc4f987853..f0c8b930d2 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1795,7 +1795,7 @@ class PlayerControlSetRolePatch private static readonly Dictionary GhostRoles = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) { - // Skip after first assign + // Skip first assign if (RpcSetRoleReplacer.BlockSetRole) return true; canOverrideRole = true; From 1adfa8e0243408665ace7bea1a2326cbd84c3418 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 2 Sep 2024 22:30:41 +0800 Subject: [PATCH 480/778] Fix bug --- Modules/CustomRolesHelper.cs | 6 ++++-- Patches/MeetingHudPatch.cs | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 3189d943ec..79aebcd946 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -963,7 +963,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Fool: if (pc.Is(CustomRoles.Mechanic) || pc.Is(CustomRoles.GuardianAngelTOHE) - || pc.Is(CustomRoles.Alchemist)) + || pc.Is(CustomRoles.Alchemist) + || pc.Is(CustomRoles.Troller)) return false; break; @@ -1018,7 +1019,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Lighter) || pc.Is(CustomRoles.Flash) - || pc.Is(CustomRoles.Mare)) + || pc.Is(CustomRoles.Mare) + || pc.Is(CustomRoles.Troller)) return false; break; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 24b9eb7e3a..a9a6226483 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -871,10 +871,16 @@ public static void NotifyRoleSkillOnMeetingStart() if (msgToSend.Count >= 1) { - var msgTemp = msgToSend.ToList(); + var msgToSendNewList = msgToSend.ToList(); _ = new LateTask(() => { - msgTemp.Do(x => SendMessage(x.Item1, x.Item2, x.Item3)); + foreach (var (text, sendTo, title) in msgToSendNewList) + { + // check player left + if (sendTo != byte.MaxValue && GetPlayerById(sendTo) == null) continue; + + SendMessage(text, sendTo, title); + } }, 3f, "Skill Description First Meeting"); } @@ -966,7 +972,13 @@ public static void NotifyRoleSkillOnMeetingStart() // Send message _ = new LateTask(() => { - msgToSend.Do(x => SendMessage(x.Item1, x.Item2, x.Item3)); + foreach (var (text, sendTo, title) in msgToSend) + { + // check player left + if (sendTo != byte.MaxValue && GetPlayerById(sendTo) == null) continue; + + SendMessage(text, sendTo, title); + } }, 3f, "Skill Notice On Meeting Start"); Main.PlayerStates.Do(x From 6fa7ba9a8f22fd200fbcc40d48d00662ca2100e3 Mon Sep 17 00:00:00 2001 From: Pyro Date: Mon, 2 Sep 2024 14:32:51 -0400 Subject: [PATCH 481/778] Add Sloth omg maybe who knows conflicts gonna suck tho ongod --- Resources/Lang/en_US.json | 4 ++++ Resources/roleColor.json | 3 ++- Roles/AddOns/Common/Sloth.cs | 30 ++++++++++++++++++++++++++++++ main.cs | 1 + 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 Roles/AddOns/Common/Sloth.cs diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 48e7fe3e02..883b80be35 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -394,6 +394,7 @@ "Statue": "Statue", "DollMaster": "Dollmaster", "DoubleAgent": "Double Agent", + "Sloth": "Sloth", "BracketAddons": "Add Brackets To Add-ons", "EngineerTOHEInfo": "Use the vents to catch the Impostors", "ScientistTOHEInfo": "Access portable vitals from anywhere", @@ -700,6 +701,7 @@ "RainbowInfo": "Colorful melodies! You don't even know your own color.", "DollMasterInfo": "Take control of players actions!", "DoubleAgentInfo": "Plant bombs on players in meetings", + "SlothInfo": "You're slower", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", @@ -1006,6 +1008,7 @@ "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-impostors.", "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", "DoubleAgentInfoLong": "(Impostor):\nAs the Double Agent, you cannot access the kill button. However, you can vote for someone in a meeting to pass a bomb onto them, which can only be done one player at a time. Once the meeting has finished, the bomb will activate and explode in a set amount of time.\nNote: when you pass the bomb onto someone in a meeting, you can vote afterward.\n\nAdditionally depending on settings the Double Agent can diffuse Bastion and Agitator bombs when venting.\n\nThe Double Agent can change roles when they are the Last Imposter, depending on the settings the role can be a Admired Impostor, Trickster, Traitor, or stay as the Double Agent.", + "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", @@ -1575,6 +1578,7 @@ "JesterVision": "Jester Vision", "LawyerVision": "Lawyer Vision", "FlashSpeed": "Flash Speed", + "SlothSpeed": "Sloth Speed", "LoverSuicide": "Lovers die together", "NumberOfLovers": "Number of Lover Pairs (x2 members)", "LoverKnowRoles": "Lovers know the roles of each other", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index a2c317e35e..fd0758d4c0 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -232,5 +232,6 @@ "Hawk": "#606c80", "Ghastly": "#9ad6d4", "Glow": "#E2F147", - "Radar": "#1eff1e" + "Radar": "#1eff1e", + "Sloth": "#376db8" } diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs new file mode 100644 index 0000000000..420fa0f4ef --- /dev/null +++ b/Roles/AddOns/Common/Sloth.cs @@ -0,0 +1,30 @@ +using AmongUs.GameOptions; +using static TOHE.Options; +using UnityEngine; + +namespace TOHE.Roles.AddOns.Common; + +public static class Sloth +{ + private const int Id = 29000; + + private static OptionItem OptionSpeed; + + public static void SetupCustomOption() + { + SetupAdtRoleOptions(Id, CustomRoles.Sloth, canSetNum: true, tab: TabGroup.Addons); + OptionSpeed = FloatOptionItem.Create(Id + 10, "SlothSpeed", new(25f, 75f, 5f), 50f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sloth]) + .SetValueFormat(OptionFormat.Multiplier); + } + public static void SetSpeed(byte playerId, bool clearAddOn) + { + if (!clearAddOn) + { + float reductionFactor = Mathf.Clamp(OptionSpeed.GetFloat(), 0f, 75f) / 75f; + Main.AllPlayerSpeed[playerId] *= Mathf.Clamp(1f - reductionFactor, 0.25f, 1f); + } + else + Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + } +} \ No newline at end of file diff --git a/main.cs b/main.cs index 6b1cbef2ac..9077a3b86d 100644 --- a/main.cs +++ b/main.cs @@ -845,6 +845,7 @@ public enum CustomRoles Seer, Silent, Sleuth, + Sloth, Soulless, Statue, Stubborn, From 5c2569d949d2077ec130e240319fad0e1df596a4 Mon Sep 17 00:00:00 2001 From: Pyro <141536178+NotPyro404@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:05:59 -0400 Subject: [PATCH 482/778] sloth stuff --- Modules/CustomRolesHelper.cs | 18 ++++++++++++++++-- Modules/OptionHolder.cs | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 70685794b9..584697124f 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -315,7 +315,8 @@ public static bool IsSpeedRole(this CustomRoles role) return role is CustomRoles.Flash or CustomRoles.Alchemist or - CustomRoles.Tired; + CustomRoles.Tired or + CustomRoles.Sloth; } public static bool IsRevealingRole(this CustomRoles role, PlayerControl target) { @@ -988,7 +989,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Statue) || pc.Is(CustomRoles.Seeker) || pc.Is(CustomRoles.Doppelganger) - || pc.Is(CustomRoles.DollMaster)) + || pc.Is(CustomRoles.DollMaster) + || pc.Is(CustomRoles.Sloth)) return false; break; @@ -1079,6 +1081,18 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if ((pc.GetCustomRole().IsCrewmate() && !Statue.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Statue.CanBeOnNeutral.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Statue.CanBeOnImp.GetBool())) return false; break; + + case CustomRoles.Sloth: + if (pc.Is(CustomRoles.Swooper) + || pc.Is(CustomRoles.Solsticer) + || pc.Is(CustomRoles.Tired) + || pc.Is(CustomRoles.Statue) + || pc.Is(CustomRoles.Seeker) + || pc.Is(CustomRoles.Doppelganger) + || pc.Is(CustomRoles.DollMaster) + || pc.Is(CustomRoles.Flash)) + return false; + break; } return true; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 329606c78c..7fb6be9712 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -946,6 +946,8 @@ private static System.Collections.IEnumerator CoLoadOptions() Rascal.SetupCustomOptions(); + Sloth.SetupCustomOptions(); + Unlucky.SetupCustomOptions(); Tired.SetupCustomOptions(); From 1e2fc8be7980903bc2cfa5297e92f86c52f6c65b Mon Sep 17 00:00:00 2001 From: Pyro Date: Tue, 3 Sep 2024 02:49:49 -0400 Subject: [PATCH 483/778] Change Id = 29700 ( idont think this will make issues) --- Roles/AddOns/Common/Sloth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs index 420fa0f4ef..d79ea49f7e 100644 --- a/Roles/AddOns/Common/Sloth.cs +++ b/Roles/AddOns/Common/Sloth.cs @@ -6,7 +6,7 @@ namespace TOHE.Roles.AddOns.Common; public static class Sloth { - private const int Id = 29000; + private const int Id = 29700; private static OptionItem OptionSpeed; From a40bb19bc5432cda776924eee75b3e023bc4bdf4 Mon Sep 17 00:00:00 2001 From: Pyro Date: Tue, 3 Sep 2024 03:04:30 -0400 Subject: [PATCH 484/778] save --- Modules/GuessManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 6469c7a1b5..36d1c04255 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -230,7 +230,7 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("GuessRainbow")); return true; } - if (role is CustomRoles.LastImpostor or CustomRoles.Mare or CustomRoles.Cyber or CustomRoles.Flash or CustomRoles.Glow) + if (role is CustomRoles.LastImpostor or CustomRoles.Mare or CustomRoles.Cyber or CustomRoles.Flash or CustomRoles.Glow or CustomRoles.Sloth) { pc.ShowInfoMessage(isUI, GetString("GuessObviousAddon")); return true; From 8e34a53742fdc822f4d7caa97f2b7558058ec307 Mon Sep 17 00:00:00 2001 From: Pyro Date: Tue, 3 Sep 2024 19:10:12 -0400 Subject: [PATCH 485/778] 29800 last id for roles/add-ons (Next use 29900) --- Modules/OptionHolder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 640a7e6631..545d1feab2 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -621,7 +621,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29700 last id for roles/add-ons (Next use 29800) + // 29800 last id for roles/add-ons (Next use 29900) // Limit id for roles/add-ons --- "59999" //####################################### From a2ea1a3ddf81deeb52589eae72446244cdc4a6f2 Mon Sep 17 00:00:00 2001 From: Pyro Date: Wed, 4 Sep 2024 00:50:22 -0400 Subject: [PATCH 486/778] revert id --- Modules/OptionHolder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 545d1feab2..640a7e6631 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -621,7 +621,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29800 last id for roles/add-ons (Next use 29900) + // 29700 last id for roles/add-ons (Next use 29800) // Limit id for roles/add-ons --- "59999" //####################################### From 490c615f139b322e9f28e46691cd1b25f36a4bd2 Mon Sep 17 00:00:00 2001 From: Pyro Date: Wed, 4 Sep 2024 09:12:44 -0400 Subject: [PATCH 487/778] modified but IAddon has a problem, idk why im a c# loser --- Roles/AddOns/Common/Sloth.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs index d79ea49f7e..71eb64d930 100644 --- a/Roles/AddOns/Common/Sloth.cs +++ b/Roles/AddOns/Common/Sloth.cs @@ -1,29 +1,26 @@ using AmongUs.GameOptions; using static TOHE.Options; -using UnityEngine; namespace TOHE.Roles.AddOns.Common; -public static class Sloth +public class Sloth : IAddon { private const int Id = 29700; + public AddonTypes Type => AddonTypes.Harmful; private static OptionItem OptionSpeed; public static void SetupCustomOption() { - SetupAdtRoleOptions(Id, CustomRoles.Sloth, canSetNum: true, tab: TabGroup.Addons); + SetupAdtRoleOptions(Id, CustomRoles.Sloth, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); OptionSpeed = FloatOptionItem.Create(Id + 10, "SlothSpeed", new(25f, 75f, 5f), 50f, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sloth]) + .SetParent(CustomRoleSpawnChances[CustomRoles.Flash]) .SetValueFormat(OptionFormat.Multiplier); } public static void SetSpeed(byte playerId, bool clearAddOn) { if (!clearAddOn) - { - float reductionFactor = Mathf.Clamp(OptionSpeed.GetFloat(), 0f, 75f) / 75f; - Main.AllPlayerSpeed[playerId] *= Mathf.Clamp(1f - reductionFactor, 0.25f, 1f); - } + Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); else Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); } From ee35abe9183c52f4c8490e2e2fe3d865df8c26c8 Mon Sep 17 00:00:00 2001 From: Pyro Date: Wed, 4 Sep 2024 09:15:22 -0400 Subject: [PATCH 488/778] no errors i believe --- Roles/AddOns/Common/Sloth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs index 71eb64d930..acf25d4257 100644 --- a/Roles/AddOns/Common/Sloth.cs +++ b/Roles/AddOns/Common/Sloth.cs @@ -10,7 +10,7 @@ public class Sloth : IAddon private static OptionItem OptionSpeed; - public static void SetupCustomOption() + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Sloth, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); OptionSpeed = FloatOptionItem.Create(Id + 10, "SlothSpeed", new(25f, 75f, 5f), 50f, TabGroup.Addons, false) From bbacc70023ca653d548a0344cdb9f3d6e3be9605 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:15:45 -0400 Subject: [PATCH 489/778] Jester and Zombie can't get Rebirth --- Modules/CustomRolesHelper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 3189d943ec..81e3235570 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -738,7 +738,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Rebirth: - if (pc.Is(CustomRoles.Doppelganger)) return false; + if (pc.Is(CustomRoles.Doppelganger) + || pc.Is(CustomRoles.Jester) + || pc.Is(CustomRoles.Zombie)) return false; break; case CustomRoles.Youtuber: From 9c27118bea17459deec7b1834f90b2c422ee6941 Mon Sep 17 00:00:00 2001 From: Pyro Date: Thu, 5 Sep 2024 11:57:07 -0400 Subject: [PATCH 490/778] Update Sloth incompatible list (added Sloth to other lists) and add Sloth.SetSpeed --- Modules/CustomRolesHelper.cs | 16 ++++++++++++---- Roles/Core/CustomRoleManager.cs | 3 +++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index b7817eb8e6..4a857bbdaa 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -958,7 +958,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Seeker) || pc.Is(CustomRoles.Doppelganger) || pc.Is(CustomRoles.DollMaster) - || pc.Is(CustomRoles.Sloth)) + || pc.Is(CustomRoles.Sloth) + || pc.Is(CustomRoles.Zombie) + || pc.Is(CustomRoles.Wraith) + || pc.Is(CustomRoles.Spurt)) return false; break; @@ -1020,14 +1023,16 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Lighter) || pc.Is(CustomRoles.Flash) - || pc.Is(CustomRoles.Mare)) + || pc.Is(CustomRoles.Mare) + || pc.Is(CustomRoles.Sloth)) return false; break; case CustomRoles.Statue: if (pc.Is(CustomRoles.Alchemist) || pc.Is(CustomRoles.Flash) - || pc.Is(CustomRoles.Tired)) + || pc.Is(CustomRoles.Tired) + || pc.Is(CustomRoles.Sloth)) return false; break; @@ -1039,7 +1044,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Seeker) || pc.Is(CustomRoles.Doppelganger) || pc.Is(CustomRoles.DollMaster) - || pc.Is(CustomRoles.Flash)) + || pc.Is(CustomRoles.Flash) + || pc.Is(CustomRoles.Zombie) + || pc.Is(CustomRoles.Wraith) + || pc.Is(CustomRoles.Spurt)) return false; break; } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 1ec0670f3c..023db0df45 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -118,6 +118,9 @@ public static void BuildCustomGameOptions(this PlayerControl player, ref IGameOp case CustomRoles.Flash: Flash.SetSpeed(player.PlayerId, false); break; + case CustomRoles.Sloth: + Sloth.SetSpeed(player.PlayerId, false); + break; case CustomRoles.Torch: Torch.ApplyGameOptions(opt); break; From 53a997759f3b1e6925e43a20be15ee36c9e89250 Mon Sep 17 00:00:00 2001 From: Pyro Date: Fri, 6 Sep 2024 02:25:07 -0400 Subject: [PATCH 491/778] Add Sloth to Cleansed List in GameState.cs --- Modules/GameState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index f59534b33a..5d84d56c79 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -130,6 +130,11 @@ public void SetSubRole(CustomRoles role, bool AllReplace = false, PlayerControl Flash.SetSpeed(pc.PlayerId, true); sync = true; } + if (pc.Is(CustomRoles.Sloth)) + { + Sloth.SetSpeed(pc.PlayerId, true); + sync = true; + } SubRoles.Remove(subRole); if (sync) MarkEveryoneDirtySettings(); From 45b41019cdfd6f9283bbcf76f9ae72001d5147be Mon Sep 17 00:00:00 2001 From: Pyro Date: Fri, 6 Sep 2024 09:17:56 -0400 Subject: [PATCH 492/778] fix Sloth lines in Sloth.cs + GuessManager.cs --- Modules/GuessManager.cs | 1 + Roles/AddOns/Common/Sloth.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index f9609ab770..962cfd6bb1 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -950,6 +950,7 @@ or CustomRoles.Rebound or CustomRoles.LastImpostor or CustomRoles.Mare or CustomRoles.Cyber + or CustomRoles.Sloth || (role.IsTNA() && !Options.TransformedNeutralApocalypseCanBeGuessed.GetBool())) continue; CreateRole(role); diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs index acf25d4257..b689fe538b 100644 --- a/Roles/AddOns/Common/Sloth.cs +++ b/Roles/AddOns/Common/Sloth.cs @@ -14,7 +14,7 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Sloth, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); OptionSpeed = FloatOptionItem.Create(Id + 10, "SlothSpeed", new(25f, 75f, 5f), 50f, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Flash]) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sloth]) .SetValueFormat(OptionFormat.Multiplier); } public static void SetSpeed(byte playerId, bool clearAddOn) From 1181b2607d4107471fc6b9d964e1f11d3ce612f8 Mon Sep 17 00:00:00 2001 From: Pyro Date: Fri, 6 Sep 2024 09:41:39 -0400 Subject: [PATCH 493/778] Fix Sloth Speed woops --- Roles/AddOns/Common/Sloth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs index b689fe538b..a377700b50 100644 --- a/Roles/AddOns/Common/Sloth.cs +++ b/Roles/AddOns/Common/Sloth.cs @@ -13,7 +13,7 @@ public class Sloth : IAddon public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Sloth, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - OptionSpeed = FloatOptionItem.Create(Id + 10, "SlothSpeed", new(25f, 75f, 5f), 50f, TabGroup.Addons, false) + OptionSpeed = FloatOptionItem.Create(Id + 10, "SlothSpeed", new(0.25f, 0.75f, 0.25f), 0.75f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Sloth]) .SetValueFormat(OptionFormat.Multiplier); } From b4e1f00e2f9a2610cb8f15a03eff45ea9e7bfa31 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 8 Sep 2024 13:57:03 +0800 Subject: [PATCH 494/778] Lot of fixes & changes & Improve death reason in meeting for modded, Added warning message about enabled setting "No Game End", others --- FodyWeavers.xml | 8 +- GameModes/FFAManager.cs | 2 +- Modules/AntiBlackout.cs | 37 +- Modules/BanManager.cs | 63 +-- Modules/CustomRolesHelper.cs | 20 +- Modules/CustomSounds.cs | 4 +- Modules/DelayNetworkedData.cs | 2 +- Modules/DisableDevice.cs | 2 +- Modules/ExtendedPlayerControl.cs | 85 ++-- Modules/GuessManager.cs | 1 + Modules/NameColorManager.cs | 2 +- Modules/OptionHolder.cs | 2 +- Modules/RPC.cs | 6 +- Modules/Utils.cs | 191 ++++---- Patches/ChatCommandPatch.cs | 31 +- Patches/ControlPatch.cs | 1 + Patches/DleksPatch.cs | 10 + Patches/GameStartManagerPatch.cs | 80 +-- Patches/IntroPatch.cs | 203 +++++--- Patches/MeetingHudPatch.cs | 49 +- Patches/PhantomRolePatch.cs | 50 +- Patches/PlayerControlPatch.cs | 23 +- Patches/PlayerJoinAndLeftPatch.cs | 8 +- Patches/ShipStatusPatch.cs | 27 +- Patches/onGameStartedPatch.cs | 515 ++++++++++++-------- Resources/Lang/en_US.json | 4 +- Roles/AddOns/Common/Rebirth.cs | 2 +- Roles/AddOns/Common/Spurt.cs | 2 +- Roles/Core/AssignManager/GhostRoleAssign.cs | 19 +- Roles/Core/AssignManager/RoleAssign.cs | 6 +- Roles/Crewmate/Alchemist.cs | 4 +- Roles/Crewmate/Altruist.cs | 2 +- Roles/Crewmate/Chameleon.cs | 4 +- Roles/Crewmate/Grenadier.cs | 4 +- Roles/Crewmate/Mortician.cs | 4 +- Roles/Crewmate/TimeMaster.cs | 2 +- Roles/Double/Mini.cs | 2 +- Roles/Impostor/Bomber.cs | 2 +- Roles/Impostor/Butcher.cs | 2 +- Roles/Impostor/Chronomancer.cs | 2 +- Roles/Impostor/DoubleAgent.cs | 14 +- Roles/Impostor/Eraser.cs | 5 + Roles/Impostor/Penguin.cs | 5 +- Roles/Impostor/Swooper.cs | 6 +- Roles/Neutral/Berserker.cs | 2 +- Roles/Neutral/Collector.cs | 4 +- Roles/Neutral/Glitch.cs | 6 +- Roles/Neutral/Wraith.cs | 6 +- TOHE.csproj | 3 +- main.cs | 10 +- nuget.config | 9 +- 51 files changed, 878 insertions(+), 675 deletions(-) diff --git a/FodyWeavers.xml b/FodyWeavers.xml index 7d3b013344..5029e70602 100644 --- a/FodyWeavers.xml +++ b/FodyWeavers.xml @@ -1,7 +1,3 @@  - - - Csv - - - + + \ No newline at end of file diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index 56d1b3e02e..99282ee4e3 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -124,7 +124,7 @@ public static void ReceiveRPCSyncFFAPlayer(MessageReader reader) } public static void SendRPCSyncNameNotify(PlayerControl pc) { - if (pc.AmOwner || !pc.IsModClient()) return; + if (!pc.IsNonHostModdedClient()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncFFANameNotify, SendOption.Reliable, pc.GetClientId()); if (NameNotify.ContainsKey(pc.PlayerId)) writer.Write(NameNotify[pc.PlayerId].TEXT); diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 49ff01de41..6e06c20eda 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -103,7 +103,7 @@ private static void RevivePlayersAndSetDummyImp() foreach (var seer in Main.AllPlayerControls) { - if (seer.OwnedByHost() || seer.IsModClient()) continue; + if (seer.IsModded()) continue; foreach (var target in Main.AllPlayerControls) { RoleTypes targetRoleType = target.PlayerId == dummyImp.PlayerId ? RoleTypes.Impostor : RoleTypes.Crewmate; @@ -170,8 +170,8 @@ public static void AntiBlackRpcVotingComplete(this MeetingHud __instance, Meetin var sender = CustomRpcSender.Create("AntiBlack RpcVotingComplete", SendOption.None); foreach (var pc in Main.AllPlayerControls) { - if (pc.AmOwner) continue; - if (pc.IsModClient()) //For mod client show real result + if (pc.IsHost()) continue; + if (pc.IsNonHostModdedClient()) //For mod client show real result { sender.AutoStartRpc(__instance.NetId, (byte)RpcCalls.VotingComplete, pc.GetClientId()); { @@ -213,7 +213,7 @@ public static void AfterMeetingTasks() if (CheckForEndVotingPatch.TempExileMsg != null && BlackOutIsActive) { timeNotify = 4f; - foreach (var pc in Main.AllPlayerControls.Where(p => p != null && !(p.AmOwner || p.IsModClient())).ToArray()) + foreach (var pc in Main.AllPlayerControls.Where(p => !p.IsModded()).ToArray()) { pc.Notify(CheckForEndVotingPatch.TempExileMsg, time: timeNotify); } @@ -247,7 +247,7 @@ public static void SetRealPlayerRoles() var target = Utils.GetPlayerById(targetId); if (seer == null || target == null) continue; - if (seer.IsModClient()) continue; + if (seer.IsModded()) continue; var isSelf = seerId == targetId; var changedRoleType = roletype; @@ -257,30 +257,15 @@ public static void SetRealPlayerRoles() { target.RpcExile(); - if (target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole())) - { - changedRoleType = RoleTypes.GuardianAngel; - } - else if (target.Is(Custom_Team.Impostor) || target.HasDesyncRole()) - { - changedRoleType = RoleTypes.ImpostorGhost; - } - else - { - changedRoleType = RoleTypes.CrewmateGhost; - } + if (target.HasGhostRole()) changedRoleType = RoleTypes.GuardianAngel; + else if (target.Is(Custom_Team.Impostor) || target.HasDesyncRole()) changedRoleType = RoleTypes.ImpostorGhost; + else changedRoleType = RoleTypes.CrewmateGhost; } else { var seerIsKiller = seer.Is(Custom_Team.Impostor) || seer.HasDesyncRole(); - if (!seerIsKiller && target.Is(Custom_Team.Impostor)) - { - changedRoleType = RoleTypes.ImpostorGhost; - } - else - { - changedRoleType = RoleTypes.CrewmateGhost; - } + if (!seerIsKiller && target.Is(Custom_Team.Impostor)) changedRoleType = RoleTypes.ImpostorGhost; + else changedRoleType = RoleTypes.CrewmateGhost; } } @@ -297,7 +282,7 @@ private static void ResetAllCooldown() seer.SetKillCooldown(); seer.RpcResetAbilityCooldown(); } - else if (seer.GetCustomRole().IsGhostRole() || seer.IsAnySubRole(x => x.IsGhostRole())) + else if (seer.HasGhostRole()) { seer.RpcResetAbilityCooldown(); } diff --git a/Modules/BanManager.cs b/Modules/BanManager.cs index b5920effc3..2a708ba4be 100644 --- a/Modules/BanManager.cs +++ b/Modules/BanManager.cs @@ -11,11 +11,11 @@ namespace TOHE; public static class BanManager { - private static readonly string DENY_NAME_LIST_PATH = @"./TOHE-DATA/DenyName.txt"; - private static readonly string BAN_LIST_PATH = @"./TOHE-DATA/BanList.txt"; - private static readonly string MODERATOR_LIST_PATH = @"./TOHE-DATA/Moderators.txt"; - private static readonly string VIP_LIST_PATH = @"./TOHE-DATA/VIP-List.txt"; - private static readonly string WHITE_LIST_LIST_PATH = @"./TOHE-DATA/WhiteList.txt"; + private const string DenyNameListPath = "./TOHE-DATA/DenyName.txt"; + private const string BanListPath = "./TOHE-DATA/BanList.txt"; + private const string ModeratorListPath = "./TOHE-DATA/Moderators.txt"; + private const string VIPListPath = "./TOHE-DATA/VIP-List.txt"; + private const string WhiteListListPath = "./TOHE-DATA/WhiteList.txt"; //private static List EACList = []; // Don't make it read-only public static List TempBanWhiteList = []; //To prevent writing to ban list public static List> EACDict = []; @@ -25,31 +25,31 @@ public static void Init() { Directory.CreateDirectory("TOHE-DATA"); - if (!File.Exists(BAN_LIST_PATH)) + if (!File.Exists(BanListPath)) { Logger.Warn("Create a new BanList.txt file", "BanManager"); - File.Create(BAN_LIST_PATH).Close(); + File.Create(BanListPath).Close(); } - if (!File.Exists(DENY_NAME_LIST_PATH)) + if (!File.Exists(DenyNameListPath)) { Logger.Warn("Create a new DenyName.txt file", "BanManager"); - File.Create(DENY_NAME_LIST_PATH).Close(); - File.WriteAllText(DENY_NAME_LIST_PATH, GetResourcesTxt("TOHE.Resources.Config.DenyName.txt")); + File.Create(DenyNameListPath).Close(); + File.WriteAllText(DenyNameListPath, GetResourcesTxt("TOHE.Resources.Config.DenyName.txt")); } - if (!File.Exists(MODERATOR_LIST_PATH)) + if (!File.Exists(ModeratorListPath)) { Logger.Warn("Creating a new Moderators.txt file", "BanManager"); - File.Create(MODERATOR_LIST_PATH).Close(); + File.Create(ModeratorListPath).Close(); } - if (!File.Exists(VIP_LIST_PATH)) + if (!File.Exists(VIPListPath)) { Logger.Warn("Creating a new VIP-List.txt file", "BanManager"); - File.Create(VIP_LIST_PATH).Close(); + File.Create(VIPListPath).Close(); } - if (!File.Exists(WHITE_LIST_LIST_PATH)) + if (!File.Exists(WhiteListListPath)) { Logger.Warn("Creating a new WhiteList.txt file", "BanManager"); - File.Create(WHITE_LIST_LIST_PATH).Close(); + File.Create(WhiteListListPath).Close(); } // Read EAC List @@ -89,7 +89,7 @@ public static string GetHashedPuid(this ClientData player) // pick front 5 and last 4 return string.Concat(sha256Hash.AsSpan(0, 5), sha256Hash.AsSpan(sha256Hash.Length - 4)); } - public static void AddBanPlayer(InnerNet.ClientData player) + public static void AddBanPlayer(ClientData player) { if (!AmongUsClient.Instance.AmHost || player == null) return; if (!CheckBanList(player?.FriendCode, player?.GetHashedPuid()) && !TempBanWhiteList.Contains(player?.GetHashedPuid())) @@ -98,7 +98,7 @@ public static void AddBanPlayer(InnerNet.ClientData player) { var additionalInfo = ""; if (CheckEACList(player?.FriendCode, player?.GetHashedPuid())) additionalInfo = " //added by EAC"; - File.AppendAllText(BAN_LIST_PATH, $"{player?.FriendCode},{player?.GetHashedPuid()},{player.PlayerName.RemoveHtmlTags()}{additionalInfo}\n"); + File.AppendAllText(BanListPath, $"{player?.FriendCode},{player?.GetHashedPuid()},{player.PlayerName.RemoveHtmlTags()}{additionalInfo}\n"); Logger.SendInGame(string.Format(GetString("Message.AddedPlayerToBanList"), player.PlayerName)); } else Logger.Info($"Failed to add player {player?.PlayerName.RemoveHtmlTags()}/{player?.FriendCode}/{player?.GetHashedPuid()} to ban list!", "AddBanPlayer"); @@ -112,8 +112,8 @@ public static bool CheckDenyNamePlayer(PlayerControl player, string name) try { Directory.CreateDirectory("TOHE-DATA"); - if (!File.Exists(DENY_NAME_LIST_PATH)) File.Create(DENY_NAME_LIST_PATH).Close(); - using StreamReader sr = new(DENY_NAME_LIST_PATH); + if (!File.Exists(DenyNameListPath)) File.Create(DenyNameListPath).Close(); + using StreamReader sr = new(DenyNameListPath); string line; while ((line = sr.ReadLine()) != null) { @@ -149,19 +149,21 @@ public static bool CheckDenyNamePlayer(PlayerControl player, string name) return true; } } - public static void CheckBanPlayer(InnerNet.ClientData player) + public static void CheckBanPlayer(ClientData player) { - if (!AmongUsClient.Instance.AmHost || !Options.ApplyBanList.GetBool()) return; + if (!AmongUsClient.Instance.AmHost) return; string friendcode = player?.FriendCode; - if (CheckBanList(friendcode, player?.GetHashedPuid())) + // Check file BanList.txt + if (Options.ApplyBanList.GetBool() && CheckBanList(friendcode, player?.GetHashedPuid())) { AmongUsClient.Instance.KickPlayer(player.Id, true); Logger.SendInGame(string.Format(GetString("Message.BannedByBanList"), player.PlayerName)); Logger.Info($"{player.PlayerName}は過去にBAN済みのためBANされました。", "BAN"); return; } + // Check EAC list from API if (CheckEACList(friendcode, player?.GetHashedPuid())) { AmongUsClient.Instance.KickPlayer(player.Id, true); @@ -193,8 +195,8 @@ public static bool CheckBanList(string code, string hashedpuid = "") try { Directory.CreateDirectory("TOHE-DATA"); - if (!File.Exists(BAN_LIST_PATH)) File.Create(BAN_LIST_PATH).Close(); - using StreamReader sr = new(BAN_LIST_PATH); + if (!File.Exists(BanListPath)) File.Create(BanListPath).Close(); + using StreamReader sr = new(BanListPath); string line; while ((line = sr.ReadLine()) != null) { @@ -239,9 +241,8 @@ public static bool CheckEACList(string code, string hashedPuid) public static bool CheckAllowList(string friendcode) { if (friendcode == "") return false; - var allowListFilePath = @"./TOHE-DATA/WhiteList.txt"; - if (!File.Exists(allowListFilePath)) File.Create(allowListFilePath).Close(); - var friendcodes = File.ReadAllLines(allowListFilePath); + if (!File.Exists(WhiteListListPath)) File.Create(WhiteListListPath).Close(); + var friendcodes = File.ReadAllLines(WhiteListListPath); return friendcodes.Any(x => x == friendcode || x.Contains(friendcode)); } } @@ -250,8 +251,10 @@ class BanMenuSelectPatch { public static void Postfix(BanMenu __instance, int clientId) { - InnerNet.ClientData recentClient = AmongUsClient.Instance.GetRecentClient(clientId); + ClientData recentClient = AmongUsClient.Instance.GetRecentClient(clientId); if (recentClient == null) return; - if (!BanManager.CheckBanList(recentClient?.FriendCode, recentClient?.GetHashedPuid())) __instance.BanButton.GetComponent().SetEnabledColors(); + + if (!BanManager.CheckBanList(recentClient?.FriendCode, recentClient?.GetHashedPuid())) + __instance.BanButton.GetComponent().SetEnabledColors(); } } \ No newline at end of file diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 79aebcd946..3333615dd3 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1,13 +1,13 @@ using AmongUs.GameOptions; +using System; using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using static TOHE.Roles.Core.CustomRoleManager; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; -using System; +using static TOHE.Roles.Core.CustomRoleManager; namespace TOHE; @@ -18,7 +18,7 @@ public static class CustomRolesHelper public static readonly Custom_Team[] AllRoleTypes = EnumHelper.GetAllValues(); public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor, Shapeshifter, Crewmate, Engineer, Scientist { - // Vanilla roles + // Vanilla rolesf if (role.IsVanilla()) return role; // Role base @@ -49,7 +49,7 @@ public static bool HasImpKillButton(this PlayerControl player, bool considerVani var customRole = player.GetCustomRole(); bool ModSideHasKillButton = customRole.GetDYRole() == RoleTypes.Impostor || customRole.GetVNRole() is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom; - if (player.IsModClient() || (!considerVanillaShift && !player.IsModClient())) + if (player.IsModded() || (!considerVanillaShift && !player.IsModded())) return ModSideHasKillButton; bool vanillaSideHasKillButton = EAC.OriginalRoles.TryGetValue(player.PlayerId, out var OriginalRole) ? @@ -67,10 +67,12 @@ Custom_RoleType.CrewmateVanillaGhosts or return true; return role is - CustomRoles.EvilSpirit; + CustomRoles.EvilSpirit; } - + public static bool HasGhostRole(this PlayerControl player) => player.GetCustomRole().IsGhostRole() || player.IsAnySubRole(x => x.IsGhostRole()); + + /* public static bool IsExperimental(this CustomRoles role) { @@ -726,7 +728,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Mortician) || pc.Is(CustomRoles.Medium) || pc.Is(CustomRoles.KillingMachine) - || pc.Is(CustomRoles.GuardianAngelTOHE)) + || pc.Is(CustomRoles.GuardianAngelTOHE) + || pc.Is(CustomRoles.Altruist)) return false; break; @@ -797,7 +800,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Puppeteer) || pc.Is(CustomRoles.Scavenger) || pc.Is(CustomRoles.Lightning) - || pc.Is(CustomRoles.Swift)) + || pc.Is(CustomRoles.Swift) + || pc.Is(CustomRoles.Swooper)) return false; if (!pc.GetCustomRole().IsImpostor()) return false; diff --git a/Modules/CustomSounds.cs b/Modules/CustomSounds.cs index c1ba2ec637..334487c783 100644 --- a/Modules/CustomSounds.cs +++ b/Modules/CustomSounds.cs @@ -10,7 +10,7 @@ public static class CustomSoundsManager { public static void RPCPlayCustomSound(this PlayerControl pc, string sound, bool force = false) { - if (!force) if (!AmongUsClient.Instance.AmHost || !pc.IsModClient()) return; + if (!force) if (!AmongUsClient.Instance.AmHost || !pc.IsModded()) return; if (pc == null || PlayerControl.LocalPlayer.PlayerId == pc.PlayerId) { Play(sound); @@ -56,7 +56,7 @@ public static void Play(string sound) Logger.Msg($"play sound:{sound}", "CustomSounds"); } - [DllImport("winmm.dll")] + [DllImport("winmm.dll", CharSet = CharSet.Unicode)] private static extern bool PlaySound(string Filename, int Mod, int Flags); private static void StartPlay(string path) => PlaySound(@$"{path}", 0, 1); //第3个形参,把1换为9,连续播放 diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index d77d087a90..ca9055abcd 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -22,7 +22,7 @@ public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId Il2CppSystem.Collections.Generic.List obj = __instance.allObjects; lock (obj) { - HashSet hashSet = new HashSet(); + HashSet hashSet = []; for (int i = 0; i < __instance.allObjects.Count; i++) { InnerNetObject innerNetObject = __instance.allObjects[i]; diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index 20f9f0a84f..d2ad437e3a 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -53,7 +53,7 @@ public static void FixedUpdate() { try { - if (pc.IsModClient()) continue; + if (pc.IsModded()) continue; bool doComms = false; Vector2 PlayerPos = pc.transform.position; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index a2b8684451..695bee69a1 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -59,7 +59,7 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* player.SetRole(role, true); return; } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, clientId); writer.Write((ushort)role); writer.Write(true); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -82,8 +82,11 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new { foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + var isSelf = player.PlayerId == seer.PlayerId; - if (!isSelf && seer.HasDesyncRole() && !(seer.AmOwner || seer.IsModClient())) + if (!isSelf && seer.HasDesyncRole() && !seer.IsHost()) { remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } @@ -91,19 +94,22 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new remeberRoleType = newRoleType; RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seer.GetClientId()); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); } } // When player change normal role to desync role else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) { - var isModded = player.OwnedByHost() || player.IsModClient(); + var isHost = player.IsHost(); foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + var isSelf = player.PlayerId == seer.PlayerId; if (isSelf) { - if (isModded) + if (isHost) remeberRoleType = RoleTypes.Crewmate; else remeberRoleType = RoleTypes.Impostor; @@ -116,7 +122,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new } else { - if (!isModded && seer.HasDesyncRole()) + if (!isHost && seer.HasDesyncRole()) { remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } @@ -124,7 +130,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new remeberRoleType = newRoleType; } RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seer.GetClientId()); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); } } // When player change desync role to desync role @@ -134,26 +140,29 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var playerIsDesync = player.HasDesyncRole(); foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if ((playerIsDesync && seer.PlayerId != playerId) || seer.HasDesyncRole()) + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + + if ((playerIsDesync || seer.HasDesyncRole()) && seer.PlayerId != playerId) { - remeberRoleType = RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)].roleType; + remeberRoleType = Utils.GetRoleMap(seer.PlayerId, playerId).roleType; } else remeberRoleType = newRoleType; RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seer.GetClientId()); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); } } if (loggerRoleMap) { - foreach (var seer in Main.AllPlayerControls) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - foreach (var target in Main.AllPlayerControls) + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - RpcSetRoleReplacer.RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map); - Logger.Info($"seer {seer?.Data?.PlayerName}-{seer.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); + var (roleType, customRole) = Utils.GetRoleMap(seer.PlayerId, target.PlayerId); + Logger.Info($"seer {seer?.Data?.PlayerName}-{seer.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {roleType}, {customRole}", "Role Map"); } } } @@ -366,12 +375,12 @@ public static void RpcGuardAndKill(this PlayerControl killer, PlayerControl targ } // Host - if (killer.AmOwner) + if (killer.IsHost()) { killer.MurderPlayer(target, MurderResultFlags.FailedProtected); } // Other Clients - if (!killer.OwnedByHost()) + else { var writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable, killer.GetClientId()); writer.WriteNetObject(target); @@ -397,7 +406,7 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, gc.LastKill = Utils.GetTimeStamp() + ((int)(time / 2) - Glitch.KillCooldown.GetInt()); gc.KCDTimer = (int)(time / 2); } - else if (forceAnime || !player.IsModClient() || !Options.DisableShieldAnimations.GetBool()) + else if (forceAnime || !player.IsModded() || !Options.DisableShieldAnimations.GetBool()) { player.SyncSettings(); player.RpcGuardAndKill(target, fromSetKCD: true); @@ -405,7 +414,7 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, else { time = Main.AllPlayerKillCooldown[player.PlayerId] / 2; - if (player.AmOwner) PlayerControl.LocalPlayer.SetKillTimer(time); + if (player.IsHost()) PlayerControl.LocalPlayer.SetKillTimer(time); else { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillTimer, SendOption.Reliable, player.GetClientId()); @@ -439,7 +448,7 @@ public static void SetKillCooldownV3(this PlayerControl player, float time = -1f if (target == null) target = player; if (time >= 0f) Main.AllPlayerKillCooldown[player.PlayerId] = time * 2; else Main.AllPlayerKillCooldown[player.PlayerId] *= 2; - if (forceAnime || !player.IsModClient() || !Options.DisableShieldAnimations.GetBool()) + if (forceAnime || !player.IsModded() || !Options.DisableShieldAnimations.GetBool()) { player.SyncSettings(); player.RpcGuardAndKill(target, fromSetKCD: true); @@ -447,7 +456,7 @@ public static void SetKillCooldownV3(this PlayerControl player, float time = -1f else { time = Main.AllPlayerKillCooldown[player.PlayerId] / 2; - if (player.AmOwner) PlayerControl.LocalPlayer.SetKillTimer(time); + if (player.IsHost()) PlayerControl.LocalPlayer.SetKillTimer(time); else { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillTimer, SendOption.Reliable, player.GetClientId()); @@ -465,7 +474,7 @@ public static void SetKillCooldownV3(this PlayerControl player, float time = -1f public static void RpcSpecificShapeshift(this PlayerControl player, PlayerControl target, bool shouldAnimate) { if (!AmongUsClient.Instance.AmHost) return; - if (player.OwnedByHost()) + if (player.IsHost()) { player.Shapeshift(target, shouldAnimate); return; @@ -554,17 +563,15 @@ public static void RpcCheckAppear(this PlayerControl player, bool shouldAnimate) } public static void RpcSpecificMurderPlayer(this PlayerControl killer, PlayerControl target, PlayerControl seer) { - if (seer.AmOwner) + if (seer.IsHost()) { killer.MurderPlayer(target, MurderResultFlags.Succeeded); + return; } - else - { - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.None, seer.GetClientId()); - messageWriter.WriteNetObject(target); - messageWriter.Write((int)MurderResultFlags.Succeeded); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.None, seer.GetClientId()); + messageWriter.WriteNetObject(target); + messageWriter.Write((int)MurderResultFlags.Succeeded); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } //Must provide seer, target [Obsolete] @@ -581,8 +588,9 @@ public static void RpcSpecificProtectPlayer(this PlayerControl killer, PlayerCon } public static void RpcResetAbilityCooldown(this PlayerControl target) { - if (!AmongUsClient.Instance.AmHost) return; // Nothing happens when run by anyone other than the host. + if (!AmongUsClient.Instance.AmHost || target == null) return; // Nothing happens when run by anyone other than the host. Logger.Info($"Ability cooldown reset: {target.name}({target.PlayerId})", "RpcResetAbilityCooldown"); + if (target.GetRoleClass() is Glitch gc) { gc.LastHack = Utils.GetTimeStamp(); @@ -590,16 +598,13 @@ public static void RpcResetAbilityCooldown(this PlayerControl target) gc.MimicCDTimer = 10; gc.HackCDTimer = 10; } - else if (PlayerControl.LocalPlayer == target && !target.GetCustomRole().IsGhostRole() && !target.IsAnySubRole(x => x.IsGhostRole())) + else if (PlayerControl.LocalPlayer.PlayerId == target.PlayerId) { //if target is the host, except for guardian angel, that breaks it. PlayerControl.LocalPlayer.Data.Role.SetCooldown(); } else { - // target is other than the host (not ghosts) - try { if (PlayerControl.LocalPlayer == target) target.MarkDirtySettings(); } - catch (Exception e) { Logger.Warn($"{e}", "RpcResetAbilityCooldown.HostAbility"); } MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.ProtectPlayer, SendOption.None, target.GetClientId()); writer.WriteNetObject(target); writer.Write(0); @@ -705,9 +710,6 @@ public static void RpcRandomVentTeleport(this PlayerControl player) player.RpcTeleport(new Vector2(vent.transform.position.x, vent.transform.position.y + 0.3636f)); } - public static bool OwnedByHost(this InnerNetObject innerObject) - => innerObject.OwnerId == AmongUsClient.Instance.HostId; - public static ClientData GetClient(this PlayerControl player) { try @@ -836,7 +838,7 @@ public static Color GetRoleColor(this PlayerControl player) } public static void ResetPlayerCam(this PlayerControl pc, float delay = 0f) { - if (pc == null || !AmongUsClient.Instance.AmHost || pc.AmOwner || pc.IsModClient()) return; + if (pc == null || !AmongUsClient.Instance.AmHost || pc.IsModded()) return; var systemtypes = Utils.GetCriticalSabotageSystemType(); @@ -1091,7 +1093,10 @@ public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPla DestroyableSingleton.Instance.OpenMeetingRoom(reporter); reporter.RpcStartMeeting(target); } - public static bool IsModClient(this PlayerControl player) => Main.playerVersion.ContainsKey(player.GetClientId()); + public static bool IsHost(this InnerNetObject innerObject) => innerObject.OwnerId == AmongUsClient.Instance.HostId; + public static bool IsHost(this byte id) => Utils.GetPlayerById(id)?.OwnerId == AmongUsClient.Instance.HostId; + public static bool IsModded(this PlayerControl player) => player != null && (player.AmOwner || player.IsHost() || Main.playerVersion.ContainsKey(player.GetClientId())); + public static bool IsNonHostModdedClient(this PlayerControl pc) => !pc.IsHost() && Main.playerVersion.ContainsKey(pc.GetClientId()); /// ///プレイヤーのRoleBehaviourのGetPlayersInAbilityRangeSortedを実行し、戻り値を返します。 /// @@ -1276,7 +1281,7 @@ public static bool ShowSubRoleTarget(this PlayerControl seer, PlayerControl targ if (target == null) target = seer; if (seer.PlayerId == target.PlayerId) return true; - else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || seer.Is(CustomRoles.God) || (seer.AmOwner && Main.GodMode.Value)) return true; + else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || seer.Is(CustomRoles.God) || (seer.IsHost() && Main.GodMode.Value)) return true; else if (Main.VisibleTasksCount && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) return true; else if (Options.ImpsCanSeeEachOthersAddOns.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor) && !subRole.IsBetrayalAddon()) return true; diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 1e4e4b27c3..e698c3ac08 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -677,6 +677,7 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); textTemplate.enabled = true; if (textTemplate.transform.FindChild("RoleTextMeeting") != null) UnityEngine.Object.Destroy(textTemplate.transform.FindChild("RoleTextMeeting").gameObject); + if (textTemplate.transform.FindChild("DeathReasonTextMeeting") != null) UnityEngine.Object.Destroy(textTemplate.transform.FindChild("DeathReasonTextMeeting").gameObject); Transform exitButtonParent = new GameObject().transform; exitButtonParent.SetParent(container); diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index b2d491c83f..ce1b13cc82 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -84,7 +84,7 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target if (color != "" && color != string.Empty) return true; else return seer == target - || (Main.GodMode.Value && seer.AmOwner) + || (Main.GodMode.Value && seer.IsHost()) || (Options.CurrentGameMode == CustomGameMode.FFA) || (Main.VisibleTasksCount && Main.PlayerStates[seer.Data.PlayerId].IsDead && seer.Data.IsDead && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) || seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 345bd33083..c38c41c7df 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1819,7 +1819,7 @@ private static System.Collections.IEnumerator CoLoadOptions() ConvertedCanBecomeGhost = BooleanOptionItem.Create(60840, "ConvertedCanBeGhostRole", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - ConvertedCanBecomeGhost = BooleanOptionItem.Create(60841, "NeutralCanBeGhostRole", false, TabGroup.ModSettings, false) + NeutralCanBecomeGhost = BooleanOptionItem.Create(60841, "NeutralCanBeGhostRole", false, TabGroup.ModSettings, false) .SetParent(ConvertedCanBecomeGhost) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(217, 218, 255, byte.MaxValue)); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index cba41d8ab4..0a7a1ee2cd 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -159,7 +159,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte ca var rpcType = (RpcCalls)callId; MessageReader subReader = MessageReader.Get(reader); if (EAC.PlayerControlReceiveRpc(__instance, callId, reader)) return false; - Logger.Info($"{__instance?.Data?.PlayerId}({(__instance.OwnedByHost() ? "Host" : __instance?.Data?.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "ReceiveRPC"); + Logger.Info($"{__instance?.Data?.PlayerId}({(__instance.IsHost() ? "Host" : __instance?.Data?.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "ReceiveRPC"); switch (rpcType) { case RpcCalls.SetName: //SetNameRPC @@ -189,7 +189,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte ca Logger.Info($"{__instance.GetNameWithRole()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); break; } - if (!__instance.OwnedByHost() && + if (!__instance.IsHost() && ((Enum.IsDefined(typeof(CustomRPC), callId) && !TrustedRpc(callId)) // Is Custom RPC || (!Enum.IsDefined(typeof(CustomRPC), callId) && !Enum.IsDefined(typeof(RpcCalls), callId)))) //Is not Custom RPC and not Vanilla RPC { @@ -760,7 +760,7 @@ public static bool Prefix(PlayerPhysics __instance, byte callId, MessageReader r Logger.Warn("Received Physics RPC without a player", "PlayerPhysics_ReceiveRPC"); return false; } - Logger.Info($"{player.PlayerId}({(__instance.OwnedByHost() ? "Host" : player.Data.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "PlayerPhysics_ReceiveRPC"); + Logger.Info($"{player.PlayerId}({(__instance.IsHost() ? "Host" : player.Data.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "PlayerPhysics_ReceiveRPC"); return true; } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 1a2012dca3..4e91eb8800 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -295,12 +295,12 @@ public static void KillFlash(this PlayerControl player, bool playKillSound = tru //Start Main.PlayerStates[player.PlayerId].IsBlackOut = true; //Set black out for player - if (player.AmOwner) + if (player.IsHost()) { FlashColor(new(1f, 0f, 0f, 0.3f)); if (Constants.ShouldPlaySfx()) RPC.PlaySound(player.PlayerId, playKillSound ? Sounds.KillSound : Sounds.SabotageSound); } - else if (player.IsModClient()) + else if (player.IsNonHostModdedClient()) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.KillFlash, SendOption.Reliable, player.GetClientId()); writer.Write(playKillSound); @@ -308,6 +308,7 @@ public static void KillFlash(this PlayerControl player, bool playKillSound = tru } else if (!ReactorCheck) player.ReactorFlash(0f); //Reactor flash for vanilla player.MarkDirtySettings(); + _ = new LateTask(() => { Main.PlayerStates[player.PlayerId].IsBlackOut = false; //Remove black out for player @@ -572,7 +573,7 @@ public static (RoleTypes roleType, CustomRoles customRole) GetRoleMap(byte seerI { if (targetId == byte.MaxValue) targetId = seerId; - return RpcSetRoleReplacer.RoleMap[(seerId, targetId)]; + return RpcSetRoleReplacer.RoleMap.GetValueOrDefault((seerId, targetId), (RoleTypes.CrewmateGhost, CustomRoles.NotAssigned)); } public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = true) { @@ -937,21 +938,13 @@ public static void ShowLastRoles(byte PlayerId = byte.MaxValue) } break; } - string lr = sb.ToString(); - try{ - if (lr.Length > 2024 && (!GetPlayerById(PlayerId).IsModClient())) - { - lr = lr.Replace(" SendMessage("\n", PlayerId, $"" + x + "")); //Since it will always capture a newline, there's more than enough space to put this in - } - else - { - SendMessage("\n", PlayerId, "" + lr + ""); - } + try + { + SendMessage("\n", PlayerId, $"{sb}"); } catch (Exception err) { - Logger.Warn($"Error after try split the msg {lr} at: {err}", "Utils.ShowLastRoles..LastRoles"); + Logger.Warn($"Error after try split the msg {sb} at: {err}", "Utils.ShowLastRoles..LastRoles"); } } public static void ShowKillLog(byte PlayerId = byte.MaxValue) @@ -1388,24 +1381,30 @@ public static string[] SplitMessage(this string LongMsg) { List result = []; var lines = LongMsg.Split('\n'); - var shortenedtext = string.Empty; + var shortenedText = string.Empty; foreach (var line in lines) { - - if (shortenedtext.Length + line.Length < 1200) + if (shortenedText.Length + line.Length < 1200) { - shortenedtext += line + "\n"; + shortenedText += line + "\n"; continue; } - if (shortenedtext.Length >= 1200) result.AddRange(shortenedtext.Chunk(1200).Select(x => new string(x))); - else result.Add(shortenedtext); - shortenedtext = line + "\n"; + if (shortenedText.Length >= 1200) result.AddRange(shortenedText.Chunk(1200).Select(x => new string(x))); + else result.Add(shortenedText); + var sentText = shortenedText; + shortenedText = line + "\n"; + + if (Regex.Matches(sentText, " Regex.Matches(sentText, "").Count) + { + var sizeTag = Regex.Matches(sentText, @"")[^1].Value; + shortenedText = sizeTag + shortenedText; + } } - if (shortenedtext.Length > 0) result.Add(shortenedtext); + if (shortenedText.Length > 0) result.Add(shortenedText); return [.. result]; } @@ -1416,7 +1415,7 @@ public static void SendSpesificMessage(string text, byte sendTo = byte.MaxValue, { // Always splits it, this is incase you want to very heavily modify msg and use the splitmsg functionality. bool isfirst = true; - if (text.Length > 1200 && !(Utils.GetPlayerById(sendTo).IsModClient())) + if (text.Length > 1200 && !GetPlayerById(sendTo).IsModded()) { foreach(var txt in text.SplitMessage()) { @@ -1440,9 +1439,19 @@ public static void SendSpesificMessage(string text, byte sendTo = byte.MaxValue, public static void SendMessage(string text, byte sendTo = byte.MaxValue, string title = "", bool logforChatManager = false, bool noReplay = false, bool ShouldSplit = false) { if (!AmongUsClient.Instance.AmHost) return; + if (title == "") title = "" + GetString("DefaultSystemMessageTitle") + ""; + if (title.Count(x => x == '\u2605') == 2 && !title.Contains('\n')) + { + if (title.Contains('<') && title.Contains('>') && title.Contains('#')) + title = $"{title[..(title.IndexOf('>') + 1)]}\u27a1{title.Replace("\u2605", "")[..(title.LastIndexOf('<') - 2)]}\u2b05"; + else title = "\u27a1" + title.Replace("\u2605", "") + "\u2b05"; + } + + text = text.Replace("color=", string.Empty); + try { - if (ShouldSplit && text.Length > 1200 && (!GetPlayerById(sendTo).IsModClient())) + if (ShouldSplit && text.Length > 1200) { text.SplitMessage().Do(x => SendMessage(x, sendTo, title)); return; @@ -1460,8 +1469,6 @@ public static void SendMessage(string text, byte sendTo = byte.MaxValue, string // set noReplay to false when you want to send previous sys msg or do not want to add a sys msg in the history if (!noReplay && GameStates.IsInGame) ChatManager.AddSystemChatHistory(sendTo, text); - if (title == "") title = "" + GetString("DefaultSystemMessageTitle") + ""; - if (!logforChatManager) ChatManager.AddToHostMessage(text.RemoveHtmlTagsTemplate()); @@ -1577,7 +1584,7 @@ void SetRealName() if (Main.HostRealName != "" && player.AmOwner) name = Main.HostRealName; if (name == "" || !GameStates.IsLobby) return; - if (player.AmOwner && player.IsModClient()) + if (player.IsHost()) { if (GameStates.IsOnlineGame || GameStates.IsLocalGame) { @@ -1693,7 +1700,7 @@ void SetRealName() }; } - if (!name.Contains($"\r\r") && player.FriendCode.GetDevUser().HasTag() && (player.AmOwner || player.IsModClient())) + if (!name.Contains($"\r\r") && player.FriendCode.GetDevUser().HasTag() && player.IsModded()) { name = player.FriendCode.GetDevUser().GetTag() + "" + modtag + "" + name; } @@ -1766,6 +1773,63 @@ public static bool IsMethodOverridden(this RoleBase roleInstance, string methodN return baseMethod.DeclaringType != derivedMethod.DeclaringType; } + // During intro scene to set team name and role info for non-modded clients and skip the rest. + // Note: When Neutral is based on the Crewmate role then it is impossible to display the info for it + // If not a Desync Role remove team display + public static void SetCustomIntro(this PlayerControl player) + { + if (!SetUpRoleTextPatch.IsInIntro || player == null || player.IsModded()) return; + + //Get role info font size based on the length of the role info + static int GetInfoSize(string RoleInfo) + { + RoleInfo = Regex.Replace(RoleInfo, "<[^>]*>", ""); + RoleInfo = Regex.Replace(RoleInfo, "{[^}]*}", ""); + + var BaseFontSize = 200; + int BaseFontSizeMin = 100; + + BaseFontSize -= 3 * RoleInfo.Length; + if (BaseFontSize < BaseFontSizeMin) + BaseFontSize = BaseFontSizeMin; + return BaseFontSize; + } + + string IconText = "|"; + string Font = ""; + string SelfTeamName = $"{IconText} {Font}{ColorString(GetTeamColor(player), $"{player.GetCustomRole().GetCustomRoleTeam()}")} {IconText}\n \n\r\n"; + string SelfRoleName = $"{Font}{ColorString(player.GetRoleColor(), GetRoleName(player.GetCustomRole()))}"; + string SelfSubRolesName = string.Empty; + string RoleInfo = $"\n{Font}{ColorString(player.GetRoleColor(), player.GetRoleInfo())}"; + string RoleNameUp = "\n\n"; + + if (!player.HasDesyncRole()) + { + SelfTeamName = string.Empty; + RoleNameUp = "\n"; + RoleInfo = $"\n{Font}{ColorString(player.GetRoleColor(), player.GetRoleInfo())}"; + } + + // Format addons + bool isFirstSub = true; + foreach (var subRole in player.GetCustomSubRoles().ToArray()) + { + if (isFirstSub) + { + SelfSubRolesName += $"\n{Font}{ColorString(GetRoleColor(subRole), GetString($"{subRole}"))}"; + RoleNameUp += "\n"; + } + else + SelfSubRolesName += $" {Font}{ColorString(Color.white, "+")} {ColorString(GetRoleColor(subRole), GetString($"{subRole}"))}"; + isFirstSub = false; + } + + var SelfName = $"{SelfTeamName}{SelfRoleName}{SelfSubRolesName}\r\n{RoleInfo}{RoleNameUp}"; + + // Privately sent name. + player.RpcSetNamePrivate(SelfName, player); + } + public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == PlayerId); private static readonly StringBuilder SelfSuffix = new(); @@ -1824,65 +1888,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl if (seer == null) continue; // Only non-modded players - if (seer.IsModClient()) continue; - - // During intro scene to set team name and role info for non-modded clients and skip the rest. - // Note: When Neutral is based on the Crewmate role then it is impossible to display the info for it - // If not a Desync Role remove team display - if (SetUpRoleTextPatch.IsInIntro) - { - //Get role info font size based on the length of the role info - static int GetInfoSize(string RoleInfo) - { - RoleInfo = Regex.Replace(RoleInfo, "<[^>]*>", ""); - RoleInfo = Regex.Replace(RoleInfo, "{[^}]*}", ""); - - var BaseFontSize = 200; - int BaseFontSizeMin = 100; - - BaseFontSize -= 3 * RoleInfo.Length; - if (BaseFontSize < BaseFontSizeMin) - BaseFontSize = BaseFontSizeMin; - return BaseFontSize; - } - - string IconText = "|"; - string Font = ""; - string SelfTeamName = $"{IconText} {Font}{ColorString(GetTeamColor(seer), $"{seer.GetCustomRole().GetCustomRoleTeam()}")} {IconText}\n \n\r\n"; - string SelfRoleName = $"{Font}{ColorString(seer.GetRoleColor(), GetRoleName(seer.GetCustomRole()))}"; - string SelfSubRolesName = string.Empty; - string SeerRealName = seer.GetRealName(); - string SelfName = ColorString(seer.GetRoleColor(), SeerRealName); - string RoleInfo = $"\n{Font}{ColorString(seer.GetRoleColor(), seer.GetRoleInfo())}"; - string RoleNameUp = "\n\n"; - - if (!seer.HasDesyncRole()) - { - SelfTeamName = string.Empty; - RoleNameUp = "\n"; - RoleInfo = $"\n{Font}{ColorString(seer.GetRoleColor(), seer.GetRoleInfo())}"; - } - - // Format addons - bool isFirstSub = true; - foreach (var subRole in seer.GetCustomSubRoles().ToArray()) - { - if (isFirstSub) - { - SelfSubRolesName += $"\n{Font}{ColorString(GetRoleColor(subRole), GetString($"{subRole}"))}"; - RoleNameUp += "\n"; - } - else - SelfSubRolesName += $" {Font}{ColorString(Color.white, "+")} {ColorString(GetRoleColor(subRole), GetString($"{subRole}"))}"; - isFirstSub = false; - } - - SelfName = $"{SelfTeamName}{SelfRoleName}{SelfSubRolesName}\r\n{RoleInfo}{RoleNameUp}"; - - // Privately sent name. - seer.RpcSetNamePrivate(SelfName, seer); - continue; - } + if (seer.IsModded()) continue; // Size of player roles string fontSize = isForMeeting ? "1.6" : "1.8"; @@ -2205,6 +2211,11 @@ public static void SyncAllSettings() PlayerGameOptionsSender.SetDirtyToAll(); GameOptionsSender.SendAllGameOptions(); } + public static void MarkAllDirtyGameData() + { + foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) + playerInfo.MarkDirty(); + } public static bool DeathReasonIsEnable(this PlayerState.DeathReason reason, bool checkbanned = false) { static bool BannedReason(PlayerState.DeathReason rso) @@ -2529,13 +2540,13 @@ public static void SetChatVisibleSpecific(this PlayerControl player) { if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost || GameStates.IsMeeting) return; - if (player.AmOwner) + if (player.IsHost()) { HudManager.Instance.Chat.SetVisible(true); return; } - if (player.IsModClient()) + if (player.IsModded()) { var modsend = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShowChat, SendOption.Reliable, player.OwnerId); modsend.WritePacked(player.OwnerId); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 2022d46aa9..e258f3c2cb 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -288,7 +288,10 @@ public static bool Prefix(ChatController __instance) case "/р": case "/роль": canceled = true; - subArgs = text.Remove(0, 2); + if (text.Contains("/role") || text.Contains("/роль")) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); break; @@ -312,6 +315,15 @@ public static bool Prefix(ChatController __instance) SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); break; + //case "/setbasic": + // canceled = true; + // if (GameStates.IsLobby) + // { + // break; + // } + // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); + // break; + case "/setplayers": case "/maxjogadores": canceled = true; @@ -827,7 +839,7 @@ public static bool Prefix(ChatController __instance) player.RpcExileV2(); MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); - if (player.AmOwner) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); } break; @@ -846,7 +858,7 @@ public static bool Prefix(ChatController __instance) if (target != null) { target.RpcMurderPlayer(target); - if (target.AmOwner) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); _ = new LateTask(() => @@ -1866,10 +1878,8 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can { canceled = false; if (!AmongUsClient.Instance.AmHost) return; - if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && !player.OwnedByHost()) // Blackmailer.ForBlackmailer.Contains(player.PlayerId)) && PlayerControl.LocalPlayer.IsAlive() && !player.OwnedByHost()) - { - ChatManager.SendMessage(player, text); - } + + if (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); if (text.StartsWith("\n")) text = text[1..]; //if (!text.StartsWith("/")) return; @@ -1894,7 +1904,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can Directory.CreateDirectory(vipTagsFiles); Directory.CreateDirectory(sponsorTagsFiles); - if (Blackmailer.CheckBlackmaile(player) && player.IsAlive() && !player.IsModClient()) + if (Blackmailer.CheckBlackmaile(player) && player.IsAlive()) { Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); ChatManager.SendPreviousMessagesToAll(); @@ -1910,7 +1920,10 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can case "/р": case "/роль": Logger.Info($"Command '/r' was activated", "OnReceiveChat"); - subArgs = text.Remove(0, 2); + if (text.Contains("/role") || text.Contains("/роль")) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); break; diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 776098e868..b70fbf0168 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -209,6 +209,7 @@ public static void Postfix(/*ControllerManager __instance*/) Utils.DoNotifyRoles(ForceLoop: true); CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); GameManager.Instance.LogicFlow.CheckEndCriteria(); + GameEndCheckerForNormal.ShowAllRolesWhenGameEnd = true; if (GameStates.IsHideNSeek) { GameEndCheckerForNormal.StartEndGame(GameOverReason.ImpostorDisconnect); diff --git a/Patches/DleksPatch.cs b/Patches/DleksPatch.cs index 51d086d613..02b7ebc6db 100644 --- a/Patches/DleksPatch.cs +++ b/Patches/DleksPatch.cs @@ -3,11 +3,21 @@ namespace TOHE.Patches; // Thanks Galster (https://github.com/Galster-dev) + +/* + * Info for those who port this code to their mod or view the code + * We patch CoStartGameHost so it's not work now in normal game + * But work for AU code + * So not used, execpt vanilla Hide&Seek +*/ + [HarmonyPatch(typeof(AmongUsClient._CoStartGameHost_d__32), nameof(AmongUsClient._CoStartGameHost_d__32.MoveNext))] public static class DleksPatch { private static bool Prefix(AmongUsClient._CoStartGameHost_d__32 __instance, ref bool __result) { + if (GameStates.IsNormalGame) return true; + if (__instance.__1__state != 0) { return true; diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs index ffa96daeb8..f09c472332 100644 --- a/Patches/GameStartManagerPatch.cs +++ b/Patches/GameStartManagerPatch.cs @@ -108,7 +108,7 @@ public static void Postfix(GameStartManager __instance) AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; if (AURoleOptions.GuardianAngelCooldown == 0f) - AURoleOptions.GuardianAngelCooldown = Options.DefaultAngelCooldown.GetFloat(); + AURoleOptions.GuardianAngelCooldown = Main.LastGuardianAngelCooldown.Value; } } } @@ -158,14 +158,14 @@ public static void Prefix(GameStartManager __instance) if ((GameData.Instance.PlayerCount >= Options.StartWhenPlayersReach.GetInt() && Options.StartWhenPlayersReach.GetInt() > 1) || (timer <= Options.StartWhenTimerLowerThan.GetInt() && Options.StartWhenTimerLowerThan.GetInt() > 0)) { - BeginAutoStart(Options.ImmediateStartTimer.GetInt()); + BeginGameAutoStart(Options.ImmediateStartTimer.GetInt()); return; } } if ((GameData.Instance.PlayerCount >= minPlayer && timer <= minWait) || timer <= maxWait) { - BeginAutoStart(Options.AutoStartTimer.GetInt()); + BeginGameAutoStart(Options.AutoStartTimer.GetInt()); return; } } @@ -250,7 +250,7 @@ public static void Postfix(GameStartManager __instance) if (timer <= 60) countDown = Utils.ColorString(Color.red, countDown); timerText.text = countDown; } - private static void BeginAutoStart(float countdown) + private static void BeginGameAutoStart(float countdown) { if (AlredyBegin) return; AlredyBegin = true; @@ -269,57 +269,7 @@ private static void BeginAutoStart(float countdown) Utils.SendMessage(msg); } - if (Options.RandomMapsMode.GetBool()) - { - var mapId = GameStartRandomMap.SelectRandomMap(); - - if (GameStates.IsNormalGame) - { - Main.NormalOptions.MapId = mapId; - } - else if (GameStates.IsHideNSeek) - { - Main.HideNSeekOptions.MapId = mapId; - } - - if (mapId == 3) // Dleks map - CreateOptionsPickerPatch.SetDleks = true; - else - CreateOptionsPickerPatch.SetDleks = false; - } - else if (CreateOptionsPickerPatch.SetDleks) - { - if (GameStates.IsNormalGame) - Main.NormalOptions.MapId = 3; - - else if (GameStates.IsHideNSeek) - Main.HideNSeekOptions.MapId = 3; - } - - //if (GameStates.IsNormalGame && Options.IsActiveDleks) - //{ - // Logger.SendInGame(GetString("Warning.BrokenVentsInDleksSendInGame")); - // Utils.SendMessage(GetString("Warning.BrokenVentsInDleksMessage"), title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceMini), GetString("WarningTitle"))); - //} - - IGameOptions opt = GameStates.IsNormalGame - ? Main.NormalOptions.Cast() - : Main.HideNSeekOptions.Cast(); - - if (GameStates.IsNormalGame) - { - Options.DefaultKillCooldown = Main.NormalOptions.KillCooldown; - Main.LastKillCooldown.Value = Main.NormalOptions.KillCooldown; - Main.NormalOptions.KillCooldown = 0f; - - AURoleOptions.SetOpt(opt); - Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; - AURoleOptions.ShapeshifterCooldown = 0f; - AURoleOptions.ImpostorsCanSeeProtect = false; - } - - PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt, AprilFoolsMode.IsAprilFoolsModeToggledOn)); - RPC.RpcVersionCheck(); + GameStartManagerBeginGamePatch.DoTasksForBeginGame(); GameStartManager.Instance.startState = GameStartManager.StartingStates.Countdown; GameStartManager.Instance.countDownTimer = (countdown == 0 ? 0.2f : countdown); @@ -344,7 +294,7 @@ private static void Postfix(TextBoxTMP __instance) } } [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.BeginGame))] -public class GameStartRandomMap +public class GameStartManagerBeginGamePatch { public static bool Prefix(GameStartManager __instance) { @@ -358,6 +308,16 @@ public static bool Prefix(GameStartManager __instance) return false; } + DoTasksForBeginGame(); + + __instance.ReallyBegin(false); + return false; + } + public static void DoTasksForBeginGame() + { + if (Options.NoGameEnd.GetBool()) + Logger.SendInGame(string.Format(GetString("Warning.NoGameEndIsEnabled"), GetString("NoGameEnd"))); + if (Options.RandomMapsMode.GetBool()) { var mapId = SelectRandomMap(); @@ -405,15 +365,15 @@ public static bool Prefix(GameStartManager __instance) Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; AURoleOptions.ShapeshifterCooldown = 0f; AURoleOptions.ImpostorsCanSeeProtect = false; + + Main.LastGuardianAngelCooldown.Value = Options.DefaultAngelCooldown.GetFloat(); + AURoleOptions.GuardianAngelCooldown = 0f; } PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt, AprilFoolsMode.IsAprilFoolsModeToggledOn)); RPC.RpcVersionCheck(); - - __instance.ReallyBegin(false); - return false; } - public static byte SelectRandomMap() + private static byte SelectRandomMap() { var rand = IRandom.Instance; List randomMaps = []; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d6d2e15a10..26b1dc6ff0 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -19,21 +19,46 @@ class CoShowIntroPatch { public static void Prefix() { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsModHost) return; + if (!AmongUsClient.Instance.AmHost || !GameStates.IsModHost || GameStates.IsHideNSeek) return; _ = new LateTask(() => { - // Update name players for custom vanilla intro - Utils.DoNotifyRoles(NoCache: true); - }, 0.35f, "Update names"); + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + pc.SetCustomIntro(); + } + }, 0.35f, "Set Custom Intro"); _ = new LateTask(() => { - ShipStatusBeginPatch.RolesIsAssigned = true; + if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) + { + StartGameHostPatch.RpcSetDisconnected(disconnected: false); - // Assign tasks after assign all roles, as it should be - ShipStatus.Instance.Begin(); - }, 4f, "Assing Task"); + if (!AmongUsClient.Instance.IsGameOver) + DestroyableSingleton.Instance.SetHudActive(true); + } + }, 0.6f, "Set Disconnected"); + + _ = new LateTask(() => + { + try + { + if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) + { + ShipStatusBeginPatch.RolesIsAssigned = true; + + // Assign tasks after assign all roles, as it should be + ShipStatus.Instance.Begin(); + + Utils.SyncAllSettings(); + } + } + catch + { + Logger.Warn($"Game ended? {AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd}", "ShipStatus.Begin"); + } + }, 4f, "Assing Task For All"); } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] @@ -52,6 +77,16 @@ public static void Postfix(IntroCutscene __instance) Utils.DoNotifyRoles(NoCache: true); } + // Show role map + /*foreach (var seer in Main.AllPlayerControls) + { + foreach (var target in Main.AllPlayerControls) + { + RpcSetRoleReplacer.RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map); + Logger.Info($"seer {seer?.Data?.PlayerName}-{seer.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); + } + }*/ + _ = new LateTask(() => { PlayerControl localPlayer = PlayerControl.LocalPlayer; @@ -124,7 +159,7 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() foreach (var pc in allPlayerControlsArray) { if (pc == null) continue; - sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", string.Empty)})\n"); + sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{Main.AllPlayerNames[pc.PlayerId]}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", string.Empty)})\n"); pc.cosmetics.nameText.text = pc.name; } @@ -419,21 +454,22 @@ public static void Postfix(IntroCutscene __instance) __instance.ImpostorText.text = "KILL EVERYONE TO WIN"; } + // I hope no one notices this in code if (Input.GetKey(KeyCode.RightShift)) { - __instance.TeamTitle.text = "明天就跑路啦"; + __instance.TeamTitle.text = "Damn!!"; __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = "嘿嘿嘿嘿嘿嘿"; - __instance.TeamTitle.color = Color.cyan; - StartFadeIntro(__instance, Color.cyan, Color.yellow); + __instance.ImpostorText.text = "You Found The Secret Intro"; + __instance.TeamTitle.color = new Color32(186, 3, 175, byte.MaxValue); + StartFadeIntro(__instance, Color.yellow, Color.cyan); } if (Input.GetKey(KeyCode.RightControl)) { - __instance.TeamTitle.text = "警告"; + __instance.TeamTitle.text = "Warning!"; __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = "请远离无知的玩家"; - __instance.TeamTitle.color = Color.magenta; - StartFadeIntro(__instance, Color.magenta, Color.magenta); + __instance.ImpostorText.text = "Please stay away from all impostor based players"; + __instance.TeamTitle.color = new Color32(241, 187, 2, byte.MaxValue); + StartFadeIntro(__instance, new Color32(241, 187, 2, byte.MaxValue), Color.red); } } public static AudioClip GetIntroSound(RoleTypes roleType) @@ -452,7 +488,7 @@ private static async void StartFadeIntro(IntroCutscene __instance, Color start, Color LerpingColor = Color.Lerp(start, end, time); if (__instance == null || milliseconds > 500) { - Logger.Info("ループを終了します", "StartFadeIntro"); + Logger.Info("Terminates the loop", "StartFadeIntro"); break; } __instance.BackgroundBar.material.color = LerpingColor; @@ -506,18 +542,69 @@ public static void Postfix(IntroCutscene __instance) [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] class IntroCutsceneDestroyPatch { + public static void Prefix() + { + if (AmongUsClient.Instance.AmHost && !AmongUsClient.Instance.IsGameOver) + { + // Host is Desync Shapeshifter + if (PlayerControl.LocalPlayer.HasDesyncRole()) + { + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + // Set all players as killable players + target.Data.Role.CanBeKilled = true; + + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + target.Data.Role.NameColor = Color.white; + } + } + + if (Main.UnShapeShifter.Any()) + { + _ = new LateTask(() => + { + Main.UnShapeShifter.Do(x => + { + var PC = Utils.GetPlayerById(x); + var firstPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); + PC.RpcShapeshift(firstPlayer, false); + PC.RpcRejectShapeshift(); + PC.ResetPlayerOutfit(force: true); + Main.CheckShapeshift[x] = false; + }); + Main.GameIsLoaded = true; + }, 3f, "Set UnShapeShift Button"); + } + + Utils.DoNotifyRoles(NoCache: true); + + if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) + { + var mapId = Utils.GetActiveMapId(); + Logger.Msg($"Check map {mapId}", "Map"); + RandomSpawn.SpawnMap map = mapId switch + { + 0 => new RandomSpawn.SkeldSpawnMap(), + 1 => new RandomSpawn.MiraHQSpawnMap(), + 2 => new RandomSpawn.PolusSpawnMap(), + 3 => new RandomSpawn.DleksSpawnMap(), + 5 => new RandomSpawn.FungleSpawnMap(), + _ => null, + }; + if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); + } + } + } public static void Postfix() { - if (!GameStates.IsInGame/* || RoleBasisChanger.SkipTasksAfterAssignRole*/) return; + if (!GameStates.IsInGame) return; Main.introDestroyed = true; // Set roleAssigned as false for override role for modded players // For override role for vanilla clients we use "Data.Disconnected" while assign - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - pc.roleAssigned = false; - } + Main.AllPlayerControls.Do(pc => pc.roleAssigned = false); if (!GameStates.AirshipIsActive) { @@ -529,28 +616,35 @@ public static void Postfix() CustomRoleManager.Add(); + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); + if (amDesyncImpostor) + { + PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; + } + if (AmongUsClient.Instance.AmHost) { if (GameStates.IsNormalGame && !GameStates.AirshipIsActive) { - foreach (var pc in Main.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { pc.RpcResetAbilityCooldown(); - } - if (Options.FixFirstKillCooldown.GetBool() && Options.CurrentGameMode != CustomGameMode.FFA) - { - _ = new LateTask(() => + + if (Options.FixFirstKillCooldown.GetBool() && Options.CurrentGameMode != CustomGameMode.FFA) { - foreach (var pc in Main.AllPlayerControls) + _ = new LateTask(() => { - pc.ResetKillCooldown(); - - if (Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out var killTimer) && (killTimer - 2f) > 0f) + if (pc != null) { - pc.SetKillCooldown(Options.FixKillCooldownValue.GetFloat() - 2f); + pc.ResetKillCooldown(); + + if (Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out var killTimer) && (killTimer - 2f) > 0f) + { + pc.SetKillCooldown(Options.FixKillCooldownValue.GetFloat() - 2f); + } } - } - }, 2f, "Fix Kill Cooldown Task"); + }, 2f, $"Fix Kill Cooldown Task for playerId {pc.PlayerId}"); + } } } @@ -587,45 +681,6 @@ public static void Postfix() { Logger.Error($"Error: {error}", "FFA chat visible"); } - - if (Main.UnShapeShifter.Any()) - { - _ = new LateTask(() => - { - Main.UnShapeShifter.Do(x => - { - var PC = Utils.GetPlayerById(x); - var firstPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); - PC.RpcShapeshift(firstPlayer, false); - PC.RpcRejectShapeshift(); - PC.ResetPlayerOutfit(force: true); - Main.CheckShapeshift[x] = false; - }); - Main.GameIsLoaded = true; - }, 3f, "Set UnShapeShift Button"); - } - - Utils.DoNotifyRoles(NoCache: true); - - if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) - { - RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null, - }; - if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); - } - } - - var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); - if (amDesyncImpostor) - { - PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; } Logger.Info("OnDestroy", "IntroCutscene"); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index a9a6226483..17bc4a29a8 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -757,12 +757,6 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta var target = GetPlayerById(ps.VotedFor); if (target != null) { - // Remove all votes for Zombie - Zombie.CheckRealVotes(target, ref VoteNum); - - //Solsticer can not get voted out - if (target.Is(CustomRoles.Solsticer)) VoteNum = 0; - // Check Tiebreaker voting Tiebreaker.CheckVote(target, ps); @@ -788,7 +782,7 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta } // Additional votes - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Stealer)) + if (pc != null && pc.Is(CustomRoles.Stealer)) { VoteNum += Stealer.AddRealVotesNum(ps); } @@ -800,6 +794,15 @@ public static Dictionary CustomCalculateVotes(this MeetingHud __insta if (Jailer.IsTarget(ps.VotedFor) || Jailer.IsTarget(ps.TargetPlayerId)) VoteNum = 0; //jailed can't vote and can't get voted + if (target != null) + { + // Remove all votes for Zombie + Zombie.CheckRealVotes(target, ref VoteNum); + + //Solsticer can not get voted out + if (target.Is(CustomRoles.Solsticer)) VoteNum = 0; + } + if (!CountInfluenced) { if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Influenced)) @@ -835,10 +838,9 @@ public static void NotifyRoleSkillOnMeetingStart() msgToSend = []; - // Description in first meeting if (Options.SendRoleDescriptionFirstMeeting.GetBool() && MeetingStates.FirstMeeting) - foreach (var pc in Main.AllAlivePlayerControls.Where(x => !x.IsModClient()).ToArray()) + foreach (var pc in Main.AllAlivePlayerControls.Where(x => !x.IsModded()).ToArray()) { var role = pc.GetCustomRole(); var Des = pc.GetRoleInfo(true); @@ -1000,7 +1002,6 @@ public static void Postfix(MeetingHud __instance) SoundManager.Instance.ChangeAmbienceVolume(0f); if (!GameStates.IsModHost) return; - //提前储存赌怪游戏组件的模板 GuessManager.textTemplate = UnityEngine.Object.Instantiate(__instance.playerStates[0].NameText); GuessManager.textTemplate.enabled = false; @@ -1008,9 +1009,14 @@ public static void Postfix(MeetingHud __instance) { var pc = GetPlayerById(pva.TargetPlayerId); if (pc == null) continue; + var textTemplate = pva.NameText; + + // Create role text in meeting var RoleTextData = GetRoleAndSubText(PlayerControl.LocalPlayer.PlayerId, pc.PlayerId); - var roleTextMeeting = UnityEngine.Object.Instantiate(pva.NameText); - roleTextMeeting.transform.SetParent(pva.NameText.transform); + var roleTextMeeting = UnityEngine.Object.Instantiate(textTemplate); + if (roleTextMeeting.transform.FindChild("DeathReasonTextMeeting") != null) + UnityEngine.Object.Destroy(roleTextMeeting.transform.FindChild("DeathReasonTextMeeting").gameObject); + roleTextMeeting.transform.SetParent(textTemplate.transform); roleTextMeeting.transform.localPosition = new Vector3(0f, -0.18f, 0f); roleTextMeeting.fontSize = 1.6f; roleTextMeeting.text = RoleTextData.Item1; @@ -1020,6 +1026,19 @@ public static void Postfix(MeetingHud __instance) roleTextMeeting.enableWordWrapping = false; roleTextMeeting.enabled = pc.AmOwner || ExtendedPlayerControl.KnowRoleTarget(PlayerControl.LocalPlayer, pc); + // Create death reason text in meeting + var deathReasonText = UnityEngine.Object.Instantiate(textTemplate); + if (deathReasonText.transform.FindChild("RoleTextMeeting") != null) + UnityEngine.Object.Destroy(deathReasonText.transform.FindChild("RoleTextMeeting").gameObject); + deathReasonText.transform.transform.SetParent(textTemplate.transform); + deathReasonText.transform.localPosition = new Vector3(0f, +0.18f, 0f); + deathReasonText.fontSize = 1.4f; + deathReasonText.text = $"『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(pc.PlayerId))}』"; + deathReasonText.color = Color.white; + deathReasonText.gameObject.name = "DeathReasonTextMeeting"; + deathReasonText.enableWordWrapping = false; + deathReasonText.enabled = PlayerControl.LocalPlayer.KnowDeathReason(pc); + var myRole = PlayerControl.LocalPlayer.GetRoleClass(); var enable = true; @@ -1040,7 +1059,7 @@ public static void Postfix(MeetingHud __instance) // If Doppelganger.CurrentVictimCanSeeRolesAsDead is disabled and player is the most recent victim from the doppelganger hide role information for player. var player = PlayerControl.LocalPlayer; var target = GetPlayerById(pva.TargetPlayerId); - + if (suffixBuilder.Length > 0) { roleTextMeeting.text = suffixBuilder.ToString(); @@ -1147,8 +1166,8 @@ public static void Postfix(MeetingHud __instance) } - if (seer.KnowDeathReason(target)) - sb.Append($"『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』"); + //if (seer.KnowDeathReason(target)) + // sb.Append($"『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』"); sb.Append(seerRoleClass?.GetMark(seer, target, true)); sb.Append(CustomRoleManager.GetMarkOthers(seer, target, true)); diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index 3db908a584..fcc9cdaf9f 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -1,5 +1,6 @@ -using AmongUs.GameOptions; -using Hazel; +using Hazel; +using AmongUs.GameOptions; +using Il2CppInterop.Runtime.InteropTypes.Arrays; using TOHE.Roles.Core; namespace TOHE.Patches; @@ -41,7 +42,6 @@ private static bool CmdCheckAppear_Prefix(PlayerControl __instance, bool shouldA AmongUsClient.Instance.FinishRpcImmediately(messageWriter); return false; } - // Called when Phantom press vanish button when visible [HarmonyPatch(nameof(PlayerControl.CheckVanish)), HarmonyPrefix] private static void CheckVanish_Prefix(PlayerControl __instance) @@ -62,7 +62,8 @@ private static void CheckVanish_Prefix(PlayerControl __instance) _ = new LateTask(() => { - phantom?.RpcExileDesync(target); + if (!Main.MeetingIsStarted) + phantom?.RpcExileDesync(target); }, 1.2f, "Set Phantom invisible", shoudLog: false); } InvisibilityList.Add(phantom); @@ -76,6 +77,11 @@ private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnim var phantom = __instance; Logger.Info($"Player: {phantom.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); + if (phantom.walkingToVent || phantom.inVent) + { + phantom.MyPhysics.RpcBootFromVent(Main.LastEnteredVent[phantom.PlayerId].Id); + } + foreach (var target in Main.AllPlayerControls) { if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; @@ -94,10 +100,13 @@ private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnim _ = new LateTask(() => { - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + if (!Main.MeetingIsStarted) + { + InvisibilityList.Remove(phantom); + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + } }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); } - InvisibilityList.Remove(phantom); } [HarmonyPatch(nameof(PlayerControl.SetRoleInvisibility)), HarmonyPrefix] private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) @@ -107,7 +116,7 @@ private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool is Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); } - public static void OnReportBody(PlayerControl seer) + public static void OnReportDeadBody(PlayerControl seer) { if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; @@ -141,3 +150,30 @@ public static void OnReportBody(PlayerControl seer) } } } +// Fixed vanilla bug for host (from TOH-Y) +[HarmonyPatch(typeof(PhantomRole), nameof(PhantomRole.UseAbility))] +public static class PhantomRoleUseAbilityPatch +{ + public static bool Prefix(PhantomRole __instance) + { + if (!AmongUsClient.Instance.AmHost) return true; + + if (__instance.Player.AmOwner && !__instance.Player.Data.IsDead && __instance.Player.moveable && !Minigame.Instance && !__instance.IsCoolingDown && !__instance.fading) + { + System.Func roleEffectAnimation = x => x.effectType == RoleEffectAnimation.EffectType.Vanish_Charge; + if (!__instance.Player.currentRoleAnimations.Find(roleEffectAnimation) && !__instance.Player.walkingToVent && !__instance.Player.inMovingPlat) + { + if (__instance.isInvisible) + { + __instance.MakePlayerVisible(true, true); + return false; + } + DestroyableSingleton.Instance.AbilityButton.SetSecondImage(__instance.Ability); + DestroyableSingleton.Instance.AbilityButton.OverrideText(DestroyableSingleton.Instance.GetString(StringNames.PhantomAbilityUndo, new Il2CppReferenceArray(0))); + __instance.Player.CmdCheckVanish(GameManager.Instance.LogicOptions.GetPhantomDuration()); + return false; + } + } + return false; + } +} \ No newline at end of file diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index f0c8b930d2..6445d5f934 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -354,6 +354,12 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC { Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}{(target.IsProtected() ? "(Protected)" : "")}, flags : {resultFlags}", "MurderPlayer Prefix"); + if (GameStates.IsLobby) + { + Logger.Info("Murder triggered in lobby, so murder canceled", "MurderPlayer Prefix"); + return false; + } + var isProtectedByClient = resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.IsProtected(); var isProtectedByHost = resultFlags.HasFlag(MurderResultFlags.FailedProtected); var isFailed = resultFlags.HasFlag(MurderResultFlags.FailedError); @@ -456,7 +462,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player } // Sync protected player from being killed first info for modded clients - if (PlayerControl.LocalPlayer.OwnedByHost()) + if (PlayerControl.LocalPlayer.IsHost()) { var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncShieldPersonDiedFirst, SendOption.None, -1); writer.Write(Main.FirstDied); @@ -511,6 +517,12 @@ public static bool Prefix(PlayerControl __instance, PlayerControl target, bool d if (!AmongUsClient.Instance.AmHost) Logger.Error("Client is calling RpcMurderPlayer, are you Hacking?", "RpcMurderPlayerPatch.Prefix"); + if (GameStates.IsLobby) + { + Logger.Info("Murder triggered in lobby, so murder canceled", "RpcMurderPlayer.Prefix"); + return false; + } + MurderResultFlags murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; if (AmongUsClient.Instance.AmClient) { @@ -892,7 +904,7 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta pc.FixMixedUpOutfit(); } - PhantomRolePatch.OnReportBody(pc); + PhantomRolePatch.OnReportDeadBody(pc); Logger.Info($"Player {pc?.Data?.PlayerName}: Id {pc.PlayerId} - is alive: {pc.IsAlive()}", "CheckIsAlive"); } @@ -1658,7 +1670,7 @@ public static void Postfix(PlayerControl __instance, ref string playerName) _ = new LateTask(() => { - if (__instance != null && !__instance.Data.Disconnected && !__instance.IsModClient()) + if (__instance != null && !__instance.Data.Disconnected && !__instance.IsModded()) { var sender = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, __instance.OwnerId); AmongUsClient.Instance.FinishRpcImmediately(sender); @@ -1808,7 +1820,6 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol // Ghost assign if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) { - try { Action SelfExile = __instance.GetRoleClass().OnSelfReducedToAtoms; @@ -1829,7 +1840,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol var self = seer.PlayerId == target.PlayerId; var seerIsKiller = seer.Is(Custom_Team.Impostor) || seer.HasDesyncRole(); - if (target.GetCustomRole().IsGhostRole() || target.IsAnySubRole(x => x.IsGhostRole())) + if (target.HasGhostRole()) { GhostRoles[seer] = RoleTypes.GuardianAngel; } @@ -1849,7 +1860,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol __instance.RpcSetRoleDesync(RoleTypes.GuardianAngel, __instance.GetClientId()); foreach (var seer in Main.AllPlayerControls) { - if (seer == __instance) continue; + if (seer.PlayerId == __instance.PlayerId) continue; __instance.RpcSetRoleDesync(RoleTypes.CrewmateGhost, seer.GetClientId()); } GhostRoleAssign.CreateGAMessage(__instance); diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 1de9e5ceb1..4bcbe76fab 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -69,9 +69,13 @@ public static void Postfix(AmongUsClient __instance) Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; AURoleOptions.SetOpt(Main.NormalOptions.Cast()); + if (AURoleOptions.ShapeshifterCooldown == 0f) AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; + if (AURoleOptions.GuardianAngelCooldown == 0f) + AURoleOptions.GuardianAngelCooldown = Main.LastGuardianAngelCooldown.Value; + // if custom game mode is HideNSeekTOHE in normal game, set standart if (Options.CurrentGameMode == CustomGameMode.HidenSeekTOHE) { @@ -578,7 +582,7 @@ public static void Postfix([HarmonyArgument(1)] int ownerId, [HarmonyArgument(2) if (GameStates.IsLobby && client.Character != null && LobbyBehaviour.Instance != null && GameStates.IsVanillaServer) { // Only for vanilla - if (!client.Character.OwnedByHost() && !client.Character.IsModClient()) + if (!client.Character.IsModded()) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(LobbyBehaviour.Instance.NetId, (byte)RpcCalls.LobbyTimeExpiring, SendOption.None, client.Id); writer.WritePacked((int)GameStartManagerPatch.timer); @@ -586,7 +590,7 @@ public static void Postfix([HarmonyArgument(1)] int ownerId, [HarmonyArgument(2) AmongUsClient.Instance.FinishRpcImmediately(writer); } // Non-host modded client - else if (!client.Character.OwnedByHost() && client.Character.IsModClient()) + else if (client.Character.IsNonHostModdedClient()) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncLobbyTimer, SendOption.Reliable, client.Id); writer.WritePacked((int)GameStartManagerPatch.timer); diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 66847b8aec..0a9c5d7ae9 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -240,7 +240,7 @@ public static void Postfix() { Logger.CurrentMethod(); - if (RolesIsAssigned && !Main.introDestroyed) + if (RolesIsAssigned && !Main.introDestroyed && GameStates.IsNormalGame) { foreach (var player in Main.AllPlayerControls) { @@ -264,6 +264,9 @@ class ShipStatusSpawnPlayerPatch { public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { + // Skip first spawn + if (initialSpawn) return true; + Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); Vector2 position = (initialSpawn ? __instance.InitialSpawnCenter : __instance.MeetingSpawnCenter) + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); @@ -276,21 +279,17 @@ class PolusShipStatusSpawnPlayerPatch { public static bool Prefix(PolusShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { - if (initialSpawn) - { - ShipStatusSpawnPlayerPatch.Prefix(__instance, player, numPlayers, initialSpawn); - } - else - { - int num1 = Mathf.FloorToInt(numPlayers / 2f); - int num2 = player.PlayerId % 15; + // Skip first spawn + if (initialSpawn) return true; - Vector2 position = num2 >= num1 - ? __instance.MeetingSpawnCenter2 + Vector2.right * (num2 - num1) * 0.6f - : __instance.MeetingSpawnCenter + Vector2.right * num2 * 0.6f; + int num1 = Mathf.FloorToInt(numPlayers / 2f); + int num2 = player.PlayerId % 15; - player.RpcTeleport(position, sendInfoInLogs: false); - } + Vector2 position = num2 >= num1 + ? __instance.MeetingSpawnCenter2 + Vector2.right * (num2 - num1) * 0.6f + : __instance.MeetingSpawnCenter + Vector2.right * num2 * 0.6f; + + player.RpcTeleport(position, sendInfoInLogs: false); return false; } } \ No newline at end of file diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 537d0ffaff..0fbaaa567b 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using Hazel; using System; +using InnerNet; using UnityEngine; using TOHE.Modules; using TOHE.Modules.ChatManager; @@ -9,6 +10,7 @@ using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; +using BepInEx.Unity.IL2CPP.Utils.Collections; using static TOHE.Translator; namespace TOHE; @@ -255,9 +257,11 @@ public static void Postfix(AmongUsClient __instance) } } } -[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] -internal class SelectRolesPatch +[HarmonyPatch] +internal class StartGameHostPatch { + private static AmongUsClient thiz; + private static RoleOptionsCollectionV08 RoleOpt => Main.NormalOptions.roleOptions; private static Dictionary RoleTypeNums = []; public static void UpdateRoleTypeNums() @@ -272,23 +276,100 @@ public static void UpdateRoleTypeNums() { RoleTypes.Tracker, RoleAssign.AddTrackerNum } }; } - public static void Prefix() - { - if (!AmongUsClient.Instance.AmHost) return; + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGameHost))] + [HarmonyPrefix] + public static bool CoStartGameHost_Prefix(AmongUsClient __instance, ref Il2CppSystem.Collections.IEnumerator __result) + { if (GameStates.IsHideNSeek) { - if (Main.EnableGM.Value) + ShipStatusBeginPatch.RolesIsAssigned = true; + return true; + } + + thiz = __instance; + __result = StartGameHost().WrapToIl2Cpp(); + return false; + } + + public static System.Collections.IEnumerator StartGameHost() + { + if (LobbyBehaviour.Instance) + { + LobbyBehaviour.Instance.Despawn(); + } + if (!ShipStatus.Instance) + { + int num = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); + // No need this becouse Dleks map sets in settings + /* try { - PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); - PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate, true); - PlayerControl.LocalPlayer.Data.IsDead = true; - Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); + if (num == 0 && AprilFoolsMode.ShouldFlipSkeld()) + { + num = 3; + } + else if (num == 3 && !AprilFoolsMode.ShouldFlipSkeld()) + { + num = 0; + } } - - EAC.OriginalRoles = []; - return; + catch (Exception ex) + { + Debug.LogError(ex.Message); + }*/ + thiz.ShipLoadingAsyncHandle = thiz.ShipPrefabs[num].InstantiateAsync(null, false); + yield return thiz.ShipLoadingAsyncHandle; + GameObject result = thiz.ShipLoadingAsyncHandle.Result; + ShipStatus.Instance = result.GetComponent(); + thiz.Spawn(ShipStatus.Instance, -2, SpawnFlags.None); } + float timer = 0f; + for (; ; ) + { + bool stopWaiting = true; + int maxTimer = 10; + if (GameOptionsManager.Instance.CurrentGameOptions.MapId == 5 || GameOptionsManager.Instance.CurrentGameOptions.MapId == 4) + { + maxTimer = 15; + } + var allClients = thiz.allClients.ToManaged(); + lock (allClients) + { + for (int i = 0; i < thiz.allClients.Count; i++) + { + ClientData clientData = thiz.allClients[i]; + if (clientData.Id != thiz.ClientId && !clientData.IsReady) + { + if (timer < maxTimer) + { + stopWaiting = false; + } + else + { + thiz.SendLateRejection(clientData.Id, DisconnectReasons.ClientTimeout); + clientData.IsReady = true; + thiz.OnPlayerLeft(clientData, DisconnectReasons.ClientTimeout); + } + } + } + } + yield return null; + if (stopWaiting) + { + break; + } + timer += Time.deltaTime; + } + thiz.SendClientReady(); + yield return new WaitForSeconds(2f); + yield return AssignRoles(); + //ShipStatus.Instance.Begin(); // Tasks sets in IntroPatch + yield break; + } + + public static System.Collections.IEnumerator AssignRoles() + { + if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) yield break; try { @@ -311,98 +392,40 @@ public static void Prefix() roleNum += roleType.Value; RoleOpt.SetRoleRate(roleType.Key, roleNum, roleType.Value > 0 ? 100 : RoleOpt.GetChancePerGame(roleType.Key)); } - } - catch (Exception ex) - { - Utils.ErrorEnd("Select Role Prefix"); - Utils.ThrowException(ex); - } - } - public static void Postfix() - { - if (!AmongUsClient.Instance.AmHost) return; - - //There is a delay of 1 seconds because after the player exits during the assign of desync roles, either a black screen will occur or the Scientist role will be set - _ = new LateTask(() => { - - try - { - // Set roles - SetRolesAfterSelect(); - } - catch (Exception ex) - { - Utils.ErrorEnd("Set Roles After Select In LateTask"); - Utils.ThrowException(ex); - } - }, 1f, "Set Role Types After Select"); - } - private static void SetRolesAfterSelect() - { - try - { - if (GameStates.IsHideNSeek) - { - GameOptionsSender.AllSenders.Clear(); - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - GameOptionsSender.AllSenders.Add( - new PlayerGameOptionsSender(pc) - ); - } - - EAC.LogAllRoles(); - Utils.SyncAllSettings(); - return; - } Logger.Msg("Is Started", "AssignRoles"); - //Initialization of CustomRpcSender and RpcSetRoleReplacer + //Start CustomRpcSender RpcSetRoleReplacer.StartReplace(); + // Assign roles and create role map for desync roles RpcSetRoleReplacer.AssignDesyncRoles(); + RpcSetRoleReplacer.SendRpcForDesync(); + + // Assign roles and create role map for normal roles RpcSetRoleReplacer.AssignNormalRoles(); + RpcSetRoleReplacer.SendRpcForNormal(); + // Send all Rpc RpcSetRoleReplacer.Release(); foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; // Skip if a custom role has already been assigned - var role = CustomRoles.NotAssigned; - switch (pc.Data.Role.Role) + if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; + var role = pc.Data.Role.Role switch { - case RoleTypes.Crewmate: - role = CustomRoles.Crewmate; - break; - case RoleTypes.Impostor: - role = CustomRoles.Impostor; - break; - case RoleTypes.Scientist: - role = CustomRoles.Scientist; - break; - case RoleTypes.Engineer: - role = CustomRoles.Engineer; - break; - case RoleTypes.GuardianAngel: - role = CustomRoles.GuardianAngel; - break; - case RoleTypes.Shapeshifter: - role = CustomRoles.Shapeshifter; - break; - case RoleTypes.Noisemaker: - role = CustomRoles.Noisemaker; - break; - case RoleTypes.Phantom: - role = CustomRoles.Phantom; - break; - case RoleTypes.Tracker: - role = CustomRoles.Tracker; - break; - default: - Logger.SendInGame(string.Format(GetString("Error.InvalidRoleAssignment"), pc?.Data?.PlayerName)); - break; - } + RoleTypes.Crewmate => CustomRoles.Crewmate, + RoleTypes.Impostor => CustomRoles.Impostor, + RoleTypes.Scientist => CustomRoles.Scientist, + RoleTypes.Engineer => CustomRoles.Engineer, + RoleTypes.GuardianAngel => CustomRoles.GuardianAngel, + RoleTypes.Shapeshifter => CustomRoles.Shapeshifter, + RoleTypes.Noisemaker => CustomRoles.Noisemaker, + RoleTypes.Phantom => CustomRoles.Phantom, + RoleTypes.Tracker => CustomRoles.Tracker, + _ => CustomRoles.NotAssigned + }; + if (role == CustomRoles.NotAssigned) Logger.SendInGame(string.Format(GetString("Error.InvalidRoleAssignment"), pc?.Data?.PlayerName)); Main.PlayerStates[pc.PlayerId].SetMainRole(role); } @@ -415,8 +438,6 @@ private static void SetRolesAfterSelect() goto EndOfSelectRolePatch; } - var rd = IRandom.Instance; - foreach (var kv in RoleAssign.RoleResult) { if (kv.Value.IsDesyncRole()) continue; @@ -460,23 +481,7 @@ private static void SetRolesAfterSelect() roleClass?.OnAdd(pc.PlayerId); // if based role is Shapeshifter - if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) - { - // Is Desync Shapeshifter - if (pc.AmOwner && pc.HasDesyncRole()) - { - foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - // Set all players as killable players - target.Data.Role.CanBeKilled = true; - - // When target is impostor, set name color as white - target.cosmetics.SetNameColor(Color.white); - target.Data.Role.NameColor = Color.white; - } - } - Main.CheckShapeshift.Add(pc.PlayerId, false); - } + if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); foreach (var subRole in pc.GetCustomSubRoles().ToArray()) { @@ -578,9 +583,7 @@ private static void SetRolesAfterSelect() GameOptionsSender.AllSenders.Clear(); foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - GameOptionsSender.AllSenders.Add( - new PlayerGameOptionsSender(pc) - ); + GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); } EAC.LogAllRoles(); @@ -592,11 +595,27 @@ private static void SetRolesAfterSelect() } catch (Exception ex) { - Utils.ErrorEnd("Set Roles After Select"); + Utils.ErrorEnd("Select Role Prefix"); Utils.ThrowException(ex); - Logger.Error(ex.ToString(), "SetRolesAfterSelect"); + yield break; } + + Logger.Info("Others assign finished", "AssignRoleTypes"); + yield return new WaitForSeconds(1f); + + Logger.Info("Send rpc disconnected for all", "AssignRoleTypes"); + DataDisconnected.Clear(); + RpcSetDisconnected(disconnected: true); + + yield return new WaitForSeconds(4f); + + Logger.Info("Assign self", "AssignRoleTypes"); + SetRoleSelf(); + + RpcSetRoleReplacer.EndReplace(); + yield break; } + public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) { if (player == null) return; @@ -612,12 +631,13 @@ public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dict // Set Desync role for self and for others foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - var roleType = othersRole; + var targetRoleType = othersRole; + var targetCustomRole = RoleAssign.RoleResult.GetValueOrDefault(target.PlayerId, CustomRoles.CrewmateTOHE); - if (RoleAssign.RoleResult[target.PlayerId].GetVNRole() is CustomRoles.Noisemaker) - roleType = RoleTypes.Noisemaker; + if (targetCustomRole.GetVNRole() is CustomRoles.Noisemaker) + targetRoleType = RoleTypes.Noisemaker; - rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? (roleType, RoleAssign.RoleResult[target.PlayerId]) : (selfRole, role); + rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? (targetRoleType, targetCustomRole) : (selfRole, role); } // Set Desync role for others @@ -626,9 +646,12 @@ public static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dict RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); - // Set role for host + // Set role for host, but not self // canOverride should be false for the host during assign - player.SetRole(othersRole, false); + if (!isHost) + { + player.SetRole(othersRole, false); + } Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); } @@ -638,15 +661,18 @@ public static void MakeDesyncSender(Dictionary senders, D { foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (seer.PlayerId == target.PlayerId && seer.PlayerId != PlayerControl.LocalPlayer.PlayerId) continue; + if (seer.PlayerId == target.PlayerId || target.PlayerId == PlayerControl.LocalPlayer.PlayerId) continue; if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var roleMap)) { try { + var targetClientId = target.GetClientId(); + if (targetClientId == -1) continue; + var roleType = roleMap.Item1; var sender = senders[seer.PlayerId]; - sender.RpcSetRole(seer, roleType, target.GetClientId()); + sender.RpcSetRole(seer, roleType, targetClientId); } catch { } @@ -654,13 +680,139 @@ public static void MakeDesyncSender(Dictionary senders, D } } } + private static void SetRoleSelf() + { + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + try + { + SetRoleSelf(pc); + } + catch { } + } + } + private static void SetRoleSelf(PlayerControl target) + { + if (target == null) return; + + RoleTypes roleType; + int targetClientId = target.GetClientId(); + if (targetClientId == -1) return; + + if (RpcSetRoleReplacer.RoleMap.TryGetValue((target.PlayerId, target.PlayerId), out var roleMap)) + { + roleType = roleMap.roleType; + } + else + { + roleType = RpcSetRoleReplacer.StoragedData[target.PlayerId]; + } + + // For host + //if (AmongUsClient.Instance.ClientId == targetClientId) + //{ + // // canOverride should be false for the host during assign + // target.SetRole(roleType, true); + // return; + //} + + target.RpcSetRoleDesync(roleType, targetClientId); + + // For vanilla clients + //var stream = MessageWriter.Get(SendOption.None); + //stream.StartMessage(6); + //stream.Write(AmongUsClient.Instance.GameId); + //stream.WritePacked(targetClientId); + //{ + // RpcSetDisconnected(stream, true, true); + + // stream.StartMessage(2); + // stream.WritePacked(target.NetId); + // { + // stream.Write((byte)RpcCalls.SetRole); + // stream.Write((ushort)roleType); + // stream.Write(true); //canOverride + // } + // stream.EndMessage(); + + // RpcSetDisconnected(stream, false, true); + //} + //stream.EndMessage(); + //AmongUsClient.Instance.SendOrDisconnect(stream); + //stream.Recycle(); + } + + private static readonly Dictionary DataDisconnected = []; + public static void RpcSetDisconnected(bool disconnected) + { + foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) + { + if (disconnected) + { + // if player left the game, remember current data + DataDisconnected[playerInfo.PlayerId] = playerInfo.Disconnected; + + playerInfo.Disconnected = true; + playerInfo.IsDead = false; + } + else + { + var data = DataDisconnected.GetValueOrDefault(playerInfo.PlayerId, true); + playerInfo.Disconnected = data; + playerInfo.IsDead = data; + } + + var stream = MessageWriter.Get(SendOption.None); + stream.StartMessage(5); + stream.Write(AmongUsClient.Instance.GameId); + { + stream.StartMessage(1); + stream.WritePacked(playerInfo.NetId); + playerInfo.Serialize(stream, false); + stream.EndMessage(); + } + stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); + } + } private static void AssignCustomRole(CustomRoles role, PlayerControl player) { if (player == null) return; - Main.PlayerStates[player.PlayerId].SetMainRole(role); - //Logger.Info($"Registered Role: {player?.Data?.PlayerName} => {role}", "AssignCustomRoles"); + } +} +[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] +internal class SelectRolesPatch +{ + public static void Prefix() + { + if (!AmongUsClient.Instance.AmHost) return; + + if (GameStates.IsHideNSeek) + { + if (Main.EnableGM.Value) + { + PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); + PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate, false); + PlayerControl.LocalPlayer.Data.IsDead = true; + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); + } + + EAC.OriginalRoles = []; + + GameOptionsSender.AllSenders.Clear(); + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + GameOptionsSender.AllSenders.Add( + new PlayerGameOptionsSender(pc) + ); + } + + EAC.LogAllRoles(); + Utils.SyncAllSettings(); + } } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole)), HarmonyPriority(Priority.High)] @@ -669,7 +821,6 @@ public static class RpcSetRoleReplacer public static bool BlockSetRole = false; public static Dictionary Senders = []; public static Dictionary StoragedData = []; - public static Dictionary DataDisconnected = []; public static Dictionary<(byte seerId, byte targetId), (RoleTypes roleType, CustomRoles customRole)> RoleMap = []; // List of Senders that do not require additional writing because SetRoleRpc has already been written by another process such as Position Desync public static List OverriddenSenderList = []; @@ -679,7 +830,6 @@ public static void Initialize() Senders = []; RoleMap = []; StoragedData = []; - DataDisconnected = []; OverriddenSenderList = []; } public static bool Prefix() @@ -696,12 +846,12 @@ public static void StartReplace() } public static void AssignDesyncRoles() { - // Assign desync roles foreach (var (playerId, role) in RoleAssign.RoleResult.Where(x => x.Value.IsDesyncRole())) - SelectRolesPatch.AssignDesyncRole(role, Utils.GetPlayerById(playerId), Senders, RoleMap, BaseRole: role.GetDYRole()); - - // Set Desync RoleType by "RpcSetRole" - SelectRolesPatch.MakeDesyncSender(Senders, RoleMap); + StartGameHostPatch.AssignDesyncRole(role, Utils.GetPlayerById(playerId), Senders, RoleMap, BaseRole: role.GetDYRole()); + } + public static void SendRpcForDesync() + { + StartGameHostPatch.MakeDesyncSender(Senders, RoleMap); } public static void AssignNormalRoles() { @@ -716,15 +866,21 @@ public static void AssignNormalRoles() foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - if (target.HasDesyncRole()) continue; + if (RoleAssign.RoleResult[target.PlayerId].IsDesyncRole() && !target.IsHost()) continue; RoleMap[(target.PlayerId, playerId)] = (roleType, role); } + if (playerId != PlayerControl.LocalPlayer.PlayerId) + { + // canOverride should be false for the host during assign + player.SetRole(roleType, false); + } + Logger.Info($"Set original role type => {player.GetRealName()}: {role} => {role.GetRoleTypes()}", "AssignNormalRoles"); } } - public static void Release() + public static void SendRpcForNormal() { foreach (var (targetId, sender) in Senders) { @@ -735,19 +891,19 @@ public static void Release() foreach (var (seerId, roleType) in StoragedData) { + if (targetId == seerId || targetId == PlayerControl.LocalPlayer.PlayerId) continue; var seer = Utils.GetPlayerById(seerId); if (seer == null || target == null) continue; - if (targetId == seerId && targetId != PlayerControl.LocalPlayer.PlayerId) continue; try { - // canOverride should be false for the host during assign - seer.SetRole(roleType, false); + var targetClientId = target.GetClientId(); + if (targetClientId == -1) continue; // send rpc set role for others clients - sender.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, target.GetClientId()) + sender.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, targetClientId) .Write((ushort)roleType) - .Write(true) + .Write(true) // canOverride .EndRpc(); } catch @@ -755,87 +911,16 @@ public static void Release() } sender.EndMessage(); } - - BlockSetRole = false; - Senders.Do(kvp => kvp.Value.SendMessage()); - - DummySetRole(); - - EndReplace(); - } - public static void DummySetRole() - { - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - if (pc.PlayerId == PlayerControl.LocalPlayer.PlayerId) continue; - DummySetRole(pc); - } } - public static void DummySetRole(PlayerControl target) + public static void Release() { - if (target == null) return; - - RoleTypes roleType; - int targetClientId = target.GetClientId(); - - if (RoleMap.TryGetValue((target.PlayerId, target.PlayerId), out var roleMap)) - { - roleType = roleMap.roleType; - } - else - { - roleType = StoragedData[target.PlayerId]; - } - - var stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(6); - stream.Write(AmongUsClient.Instance.GameId); - stream.WritePacked(targetClientId); - { - RpcSetDisconnected(stream, true); - - stream.StartMessage(2); - stream.WritePacked(target.NetId); - stream.Write((byte)RpcCalls.SetRole); - stream.Write((ushort)roleType); - stream.Write(true); //canOverrideRole - stream.EndMessage(); - - RpcSetDisconnected(stream, false); - } - stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); + BlockSetRole = false; + Senders.Do(kvp => kvp.Value.SendMessage()); } - private static void EndReplace() + public static void EndReplace() { Senders = null; OverriddenSenderList = null; StoragedData = null; } - public static void RpcSetDisconnected(MessageWriter stream, bool disconnected) - { - foreach (var playerinfo in GameData.Instance.AllPlayers.GetFastEnumerator()) - { - if (disconnected) - { - // if player left the game, remember current data - DataDisconnected[playerinfo.PlayerId] = playerinfo.Disconnected; - - playerinfo.Disconnected = true; - playerinfo.IsDead = false; - } - else - { - var data = DataDisconnected.GetValueOrDefault(playerinfo.PlayerId, true); - playerinfo.Disconnected = data; - playerinfo.IsDead = data; - } - - stream.StartMessage(1); - stream.WritePacked(playerinfo.NetId); - playerinfo.Serialize(stream, false); - stream.EndMessage(); - } - } } \ No newline at end of file diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6d8767b123..15339d7fab 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1903,7 +1903,7 @@ "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", "SayCommandDisabled": "The say command is currently disabled.", "MessageFromModerator": "MODERATOR", - "DeathReason.Kill": "Kill", + "DeathReason.Kill": "Killed", "DeathReason.Vote": "Ejected", "DeathReason.Suicide": "Suicide", "DeathReason.Spell": "Spelled", @@ -2309,6 +2309,8 @@ "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map, the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", + "Warning.NoGameEndIsEnabled": "Warning: {0} is enabled!", + "AntiBlackoutProtectionTitle": "Anti Blackout", "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normally", "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in the last meeting.\n", diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index d76422b80c..f6019bcbd8 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -60,7 +60,7 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled } var ViablePlayer = list.Where(x => x != pc).Shuffle(IRandom.Instance) - .FirstOrDefault(x => x != null && !x.OwnedByHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && + .FirstOrDefault(x => x != null && !x.IsHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && /*All converters */ !x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer) && !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.Is(CustomRoles.Doppelganger) && !x.GetCustomRole().IsImpostor()); diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 13b4acf7d4..353889402f 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -102,7 +102,7 @@ public void OnFixedUpdate(PlayerControl player) float Decreaseby = Mathf.Clamp(modulator / 20 * 0.5f, 0.01f, 0.3f); int charge = DetermineCharge(player); - if (DisplaysCharge.GetBool() && !player.IsModClient() && LastNum[player.PlayerId] != charge) + if (DisplaysCharge.GetBool() && !player.IsModded() && LastNum[player.PlayerId] != charge) { LastNum[player.PlayerId] = charge; long now = Utils.TimeStamp; diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index c3728e28f3..44fcc424b7 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -22,9 +22,12 @@ public static void GhostAssignPatch(PlayerControl player) if (GameStates.IsHideNSeek || Options.CurrentGameMode == CustomGameMode.FFA || player == null + || player.Data == null || player.Data.Disconnected || GhostGetPreviousRole.ContainsKey(player.PlayerId)) return; - if (forceRole.TryGetValue(player.PlayerId, out CustomRoles forcerole)) { + + if (forceRole.TryGetValue(player.PlayerId, out CustomRoles forcerole)) + { Logger.Info($" Debug set {player.GetRealName()}'s role to {forcerole}", "GhostAssignPatch"); player.GetRoleClass()?.OnRemove(player.PlayerId); player.RpcSetCustomRole(forcerole); @@ -34,38 +37,34 @@ public static void GhostAssignPatch(PlayerControl player) return; } - - var getplrRole = player.GetCustomRole(); if (getplrRole is CustomRoles.GM or CustomRoles.Nemesis or CustomRoles.Retributionist or CustomRoles.NiceMini) return; var IsNeutralAllowed = !player.IsAnySubRole(x => x.IsConverted()) || Options.ConvertedCanBecomeGhost.GetBool(); var CheckNeutral = player.GetCustomRole().IsNeutral() && Options.NeutralCanBecomeGhost.GetBool(); var IsCrewmate = ((getplrRole.IsCrewmate() || player.Is(CustomRoles.Admired)) && IsNeutralAllowed) || CheckNeutral; - var IsImpostor = ((getplrRole.IsImpostor()) && (IsNeutralAllowed || player.Is(CustomRoles.Madmate))) || CheckNeutral; + var IsImpostor = (getplrRole.IsImpostor() && (IsNeutralAllowed || player.Is(CustomRoles.Madmate))) || CheckNeutral; if (getplrRole.IsGhostRole() || player.IsAnySubRole(x => x.IsGhostRole() || x == CustomRoles.Gravestone) || !Options.CustomGhostRoleCounts.Any()) return; if (IsImpostor && ImpCount >= Options.MaxImpGhost.GetInt() || IsCrewmate && CrewCount >= Options.MaxCrewGhost.GetInt()) return; - GhostGetPreviousRole.TryAdd(player.PlayerId, getplrRole); - + GhostGetPreviousRole[player.PlayerId] = getplrRole; HauntedList.Clear(); ImpHauntedList.Clear(); CustomRoles ChosenRole = CustomRoles.NotAssigned; - foreach (var ghostRole in getCount.Keys.Where(x => x.GetMode() > 0)) { if (ghostRole.IsCrewmate()) { if (HauntedList.Contains(ghostRole) && getCount[ghostRole] <= 0) - HauntedList.Remove(ghostRole); + HauntedList.Remove(ghostRole); if (HauntedList.Contains(ghostRole) || getCount[ghostRole] <= 0) - continue; + continue; if (ghostRole.GetChance()) HauntedList.Add(ghostRole); } @@ -135,8 +134,6 @@ public static void Add() Options.CustomGhostRoleCounts.Keys.Do(ghostRole => getCount.TryAdd(ghostRole, ghostRole.GetCount())); // Add new count Instance (Optionitem gets constantly refreshed) - - foreach (var role in getCount) { Logger.Info($"Logged: {role.Key} / {role.Value}", "GhostAssignPatch.Add.GetCount"); diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index eb38f98979..a517e56189 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -55,7 +55,7 @@ public static void StartSelect() case CustomGameMode.FFA: foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - RoleResult.Add(pc.PlayerId, CustomRoles.Killer); + RoleResult[pc.PlayerId] = CustomRoles.Killer; } return; } @@ -781,9 +781,9 @@ public static void StartSelect() } if (AllPlayers.Any()) - Logger.Warn("Role assignment error: There are players who have not been assigned a role", "RoleAssign"); + Logger.Warn("Role assignment warirng: There are players who have not been assigned a role", "RoleAssign"); if (FinalRolesList.Any()) - Logger.Warn("Team assignment error: There is an unassigned team", "RoleAssign"); + Logger.Warn("Team assignment warirng: There is an unassigned team", "RoleAssign"); return; RoleAssignInfo GetAssignInfo(CustomRoles role) => Roles.Values.FirstOrDefault(x => x.Any(y => y.Role == role))?.FirstOrDefault(x => x.Role == role); diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index 680c77c7ad..f9a1313839 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -146,7 +146,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount private static void SendRPC(PlayerControl pc) { - if (pc.AmOwner) return; + if (pc.IsHost()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAlchemistTimer, SendOption.Reliable, pc.GetClientId()); writer.Write(FixNextSabo); writer.Write(PotionID); @@ -244,7 +244,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) } else if (remainTime <= 10) { - if (!alchemist.IsModClient()) + if (!alchemist.IsModded()) alchemist.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime), sendInLog: false); } } diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 21076478ae..1f4c6e0fc0 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -64,7 +64,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay deadPlayer.RpcTeleport(deadBodyObject.transform.position); deadPlayer.RpcRevive(); - if (deadPlayer.GetCustomRole().IsGhostRole() || deadPlayer.IsAnySubRole(sub => sub.IsGhostRole())) + if (deadPlayer.HasGhostRole()) { deadPlayer.GetRoleClass().Remove(deadPlayerId); deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).customRole); diff --git a/Roles/Crewmate/Chameleon.cs b/Roles/Crewmate/Chameleon.cs index 867a81ad5b..83a1cd8e86 100644 --- a/Roles/Crewmate/Chameleon.cs +++ b/Roles/Crewmate/Chameleon.cs @@ -147,7 +147,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) if (InvisCooldown.TryGetValue(playerId, out var oldTime) && (oldTime + (long)ChameleonCooldown.GetFloat() - nowTime) < 0) { InvisCooldown.Remove(playerId); - if (!player.IsModClient()) player.Notify(GetString("ChameleonCanVent")); + if (!player.IsModded()) player.Notify(GetString("ChameleonCanVent")); needSync = true; } @@ -175,7 +175,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) } else if (remainTime <= 10) { - if (!chameleon.IsModClient()) + if (!chameleon.IsModded()) chameleon.Notify(string.Format(GetString("ChameleonInvisStateCountdown"), remainTime), sendInLog: false); } } diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs index 08a43d3120..d834991b26 100644 --- a/Roles/Crewmate/Grenadier.cs +++ b/Roles/Crewmate/Grenadier.cs @@ -99,7 +99,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) { MadGrenadierBlinding.Remove(pc.PlayerId); MadGrenadierBlinding.Add(pc.PlayerId, GetTimeStamp()); - Main.AllPlayerControls.Where(x => x.IsModClient()) + Main.AllPlayerControls.Where(x => x.IsModded()) .Where(x => !x.GetCustomRole().IsImpostorTeam() && !x.Is(CustomRoles.Madmate)) .Do(x => x.RPCPlayCustomSound("FlashBang")); } @@ -107,7 +107,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) { GrenadierBlinding.Remove(pc.PlayerId); GrenadierBlinding.Add(pc.PlayerId, GetTimeStamp()); - Main.AllPlayerControls.Where(x => x.IsModClient()) + Main.AllPlayerControls.Where(x => x.IsModded()) .Where(x => x.GetCustomRole().IsImpostor() || (x.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool())) .Do(x => x.RPCPlayCustomSound("FlashBang")); } diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index d11913ecb1..fac7ce7e57 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -78,12 +78,12 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe string minName = ""; foreach (var pc in Main.AllAlivePlayerControls) { - if (pc.PlayerId == target.PlayerId) continue; + if (pc.PlayerId == target.PlayerId || playerIdList.Any(p => p == pc.PlayerId)) continue; var dis = Utils.GetDistance(pc.transform.position, pos); if (dis < minDis && dis < 1.5f) { minDis = dis; - minName = pc.GetRealName(); + minName = pc.GetRealName(clientData: true); } } diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs index 079f983f80..cc8fc9dd61 100644 --- a/Roles/Crewmate/TimeMaster.cs +++ b/Roles/Crewmate/TimeMaster.cs @@ -105,7 +105,7 @@ public override void OnEnterVent(PlayerControl pc, Vent AirConditioning) TimeMasterInProtect.Remove(pc.PlayerId); TimeMasterInProtect.Add(pc.PlayerId, GetTimeStamp()); - if (!pc.IsModClient()) + if (!pc.IsModded()) { pc.RpcGuardAndKill(pc); } diff --git a/Roles/Double/Mini.cs b/Roles/Double/Mini.cs index f972e38623..77e2c9390d 100644 --- a/Roles/Double/Mini.cs +++ b/Roles/Double/Mini.cs @@ -139,7 +139,7 @@ and evil mini can never kill before age 18*/ { SendRPC(); player.Notify(GetString("MiniUp")); - NotifyRoles(); + NotifyRoles(SpecifyTarget: player); } } } diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index 5932c33540..d4cb27741c 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -73,7 +73,7 @@ public override void UnShapeShiftButton(PlayerControl shapeshifter) foreach (var target in Main.AllPlayerControls) { - if (!target.IsModClient()) target.KillFlash(); + if (!target.IsModded()) target.KillFlash(); if (target.PlayerId == shapeshifter.PlayerId) continue; if (!target.IsAlive() || Medic.ProtectList.Contains(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; diff --git a/Roles/Impostor/Butcher.cs b/Roles/Impostor/Butcher.cs index a89a5d57d9..b2dd43a4fc 100644 --- a/Roles/Impostor/Butcher.cs +++ b/Roles/Impostor/Butcher.cs @@ -72,7 +72,7 @@ public override void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl for (int i = 0; i <= 19; i++) { if (GameStates.IsMeeting) break; - if (!target.AmOwner) + if (!target.IsHost()) { target.MurderPlayer(target, ExtendedPlayerControl.ResultFlags); } diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index d85760be6f..7145df7997 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -186,7 +186,7 @@ public override string GetLowerText(PlayerControl seer, PlayerControl seen = nul bool ismeeting = GameStates.IsMeeting || isForMeeting; if (seer == seen && !ismeeting) { - if (!isForHud && seer.IsModClient()) + if (!isForHud && seer.IsModded()) return string.Empty; return GetCharge(); diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 78ff7fe2dd..fbaa48af0b 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -125,7 +125,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) public override bool CheckVote(PlayerControl voter, PlayerControl target) { - if (voter.IsModClient() || !CanBombInMeeting) return true; + if (voter.IsModded() || !CanBombInMeeting) return true; if (!BombIsActive) { @@ -144,7 +144,7 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) // Clear active bombed players on meeting call if ClearBombedOnMeetingCall is enabled. public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - if (_Player != null && (_Player.AmOwner || _Player.IsModClient())) + if (_Player != null && _Player.IsModded()) { HasVoted = true; } @@ -192,10 +192,10 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) { if (BombIsActive) { - if (!pc.IsModClient()) + if (!pc.IsModded()) { string Duration = ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); - if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Item1 != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); + if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Text != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); } if (CurrentBombedPlayers.Any(playerId => !GetPlayerById(playerId).IsAlive())) // If playerId is a null Player clear bomb. @@ -205,11 +205,11 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) // If enabled and if DoubleAgent is last Impostor become set role. if (ChangeRoleToOnLast.GetValue() != 0 && StartedWithMoreThanOneImp && GameStates.IsInTask && !GameStates.IsMeeting && !GameStates.IsExilling) { - if (pc.Is(CustomRoles.DoubleAgent) && Main.AliveImpostorCount < 2) + if (pc.Is(CustomRoles.DoubleAgent) && pc.IsAlive() && Main.AliveImpostorCount < 2) { var Role = CRoleChangeRoles[ChangeRoleToOnLast.GetValue()]; if (ChangeRoleToOnLast.GetValue() == 1) // Random - Role = CRoleChangeRoles[UnityEngine.Random.RandomRangeInt(2, CRoleChangeRoles.Length)]; + Role = CRoleChangeRoles[IRandom.Instance.Next(2, CRoleChangeRoles.Length)]; // If role is not on Impostor team remove all Impostor addons if any. if (!Role.IsImpostorTeam()) @@ -273,7 +273,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo // Set timer for Double Agent Modded Clients. public override string GetLowerText(PlayerControl player, PlayerControl seen, bool isForMeeting = false, bool isForHud = false) { - if (player == null || player != seen || player.IsModClient() && !isForHud) return string.Empty; + if (player == null || player != seen || player.IsModded() && !isForHud) return string.Empty; if (CurrentBombedTime > 0 && CurrentBombedTime < BombExplosionTimer.GetFloat() + 1) return ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); return string.Empty; } diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index 4fb930c3d1..0a19454d6a 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -120,6 +120,11 @@ public override void AfterMeetingTasks() Logger.Info($"Canceled {player.GetNameWithRole()} Eraser bcz already erased.", "Eraser"); return; } + if (player.HasGhostRole()) + { + Logger.Info($"Canceled {player.GetNameWithRole()} because player have ghost role", "Eraser"); + return; + } player.RpcSetCustomRole(GetErasedRole(player.GetCustomRole().GetRoleTypes(), player.GetCustomRole())); player.ResetKillCooldown(); player.SetKillCooldown(); diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index 496fc63430..fb5f57c0c4 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -247,7 +247,7 @@ public override void OnFixedUpdate(PlayerControl penguin) else if (!AbductVictim.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) { var position = penguin.transform.position; - if (!penguin.OwnedByHost()) + if (!penguin.IsHost()) { AbductVictim.RpcTeleport(position, sendInfoInLogs: false); } @@ -256,8 +256,7 @@ public override void OnFixedUpdate(PlayerControl penguin) _ = new LateTask(() => { AbductVictim?.RpcTeleport(position, sendInfoInLogs: false); - } - , 0.25f, ""); + }, 0.25f, "Penguin Teleport ", shoudLog: false); } } } diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs index eb07e3ad96..89a989b60d 100644 --- a/Roles/Impostor/Swooper.cs +++ b/Roles/Impostor/Swooper.cs @@ -46,7 +46,7 @@ public override void Add(byte playerId) } private void SendRPC(PlayerControl pc) { - if (pc.AmOwner) return; + if (pc.IsHost()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, pc.GetClientId()); writer.WriteNetObject(_Player); writer.Write((InvisCooldown.TryGetValue(pc.PlayerId, out var x) ? x : -1).ToString()); @@ -124,7 +124,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) if (InvisCooldown.TryGetValue(playerId, out var oldTime) && (oldTime + (long)SwooperCooldown.GetFloat() - nowTime) < 0) { InvisCooldown.Remove(playerId); - if (!player.IsModClient()) player.Notify(GetString("SwooperCanVent")); + if (!player.IsModded()) player.Notify(GetString("SwooperCanVent")); needSync = true; } @@ -152,7 +152,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) } else if (remainTime <= 10) { - if (!swooper.IsModClient()) + if (!swooper.IsModded()) swooper.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime), sendInLog: false); } } diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 89629ec999..13287cd500 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -140,7 +140,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); foreach (var player in Main.AllAlivePlayerControls) { - if (!player.IsModClient()) + if (!player.IsModded()) player.KillFlash(); if (player == killer) continue; diff --git a/Roles/Neutral/Collector.cs b/Roles/Neutral/Collector.cs index 28110a21db..650d236db7 100644 --- a/Roles/Neutral/Collector.cs +++ b/Roles/Neutral/Collector.cs @@ -89,13 +89,13 @@ private bool CollectDone(PlayerControl player) } return false; } - public static void CollectorVotes(PlayerControl target, PlayerVoteArea ps)//集票者投票给谁 + public static void CollectorVotes(PlayerControl target, PlayerVoteArea ps) { if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Collector)) CollectorVoteFor.TryAdd(target.PlayerId, ps.TargetPlayerId); } public override void AfterMeetingTasks() => calculated = false; - public void CollectAmount(Dictionary VotingData, MeetingHud __instance)//得到集票者收集到的票 + public void CollectAmount(Dictionary VotingData, MeetingHud __instance) { if (calculated) return; int VoteAmount; diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index bf46df93a2..54babf8f34 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -207,12 +207,12 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) catch { MimicCDTimer = 0; } if (MimicCDTimer > 180 || MimicCDTimer < 0) MimicCDTimer = 0; - if (!player.IsModClient()) + if (!player.IsModded()) { var Pname = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Glitch), player.GetRealName(isMeeting: true)); - if (!NameNotifyManager.Notice.TryGetValue(player.PlayerId, out var a) || a.Item1 != Pname) player.Notify(Pname, 1.1f); + if (!NameNotifyManager.Notice.TryGetValue(player.PlayerId, out var a) || a.Text != Pname) player.Notify(Pname, 1.1f); } - if (!player.AmOwner) // For mooded non host players, sync kcd per second + if (player.IsNonHostModdedClient()) // For mooded non host players, sync kcd per second { if (lastRpcSend < Utils.GetTimeStamp()) { diff --git a/Roles/Neutral/Wraith.cs b/Roles/Neutral/Wraith.cs index 415295c7be..150caf91d2 100644 --- a/Roles/Neutral/Wraith.cs +++ b/Roles/Neutral/Wraith.cs @@ -48,7 +48,7 @@ public override void Init() } private void SendRPC(PlayerControl pc) { - if (pc.AmOwner) return; + if (pc.IsHost()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, pc.GetClientId()); writer.WriteNetObject(_Player);//SetWraithTimer writer.Write((InvisTime.TryGetValue(pc.PlayerId, out var x) ? x : -1).ToString()); @@ -107,7 +107,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) if (lastTime.TryGetValue(player.PlayerId, out var time) && time + (long)WraithCooldown.GetFloat() < now) { lastTime.Remove(player.PlayerId); - if (!player.IsModClient()) player.Notify(GetString("WraithCanVent")); + if (!player.IsModded()) player.Notify(GetString("WraithCanVent")); SendRPC(player); } @@ -132,7 +132,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) } else if (remainTime <= 10) { - if (!pc.IsModClient()) pc.Notify(string.Format(GetString("WraithInvisStateCountdown"), remainTime + 1)); + if (!pc.IsModded()) pc.Notify(string.Format(GetString("WraithInvisStateCountdown"), remainTime + 1)); } newList.Add(it.Key, it.Value); } diff --git a/TOHE.csproj b/TOHE.csproj index ca5573ec1e..9fb86b6925 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,7 +32,6 @@ runtime; compile; build; native; contentfiles; analyzers; buildtransitive all - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/main.cs b/main.cs index e78abcc94f..dfd4b722fe 100644 --- a/main.cs +++ b/main.cs @@ -26,7 +26,7 @@ namespace TOHE; [BepInProcess("Among Us.exe")] public class Main : BasePlugin { - // == プログラム設定 / Program Config == + // == Program Config == public const string OriginalForkId = "OriginalTOH"; public static readonly string ModName = "TOHE"; @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0902.210.00080"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 8"; + public const string PluginVersion = "2024.0908.210.00101"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 10 Hotfix 1"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 8 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 10 Hotfix 1 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 @@ -115,6 +115,7 @@ public class Main : BasePlugin public static ConfigEntry BetaBuildURL { get; private set; } public static ConfigEntry LastKillCooldown { get; private set; } public static ConfigEntry LastShapeshifterCooldown { get; private set; } + public static ConfigEntry LastGuardianAngelCooldown { get; private set; } public static ConfigEntry PlayerSpawnTimeOutCooldown { get; private set; } public static OptionBackupData RealOptionsData; @@ -552,6 +553,7 @@ public override void Load() MessageWait = Config.Bind("Other", "MessageWait", 1); LastKillCooldown = Config.Bind("Other", "LastKillCooldown", (float)30); LastShapeshifterCooldown = Config.Bind("Other", "LastShapeshifterCooldown", (float)30); + LastGuardianAngelCooldown = Config.Bind("Other", "LastGuardianAngelCooldown", (float)35); PlayerSpawnTimeOutCooldown = Config.Bind("Other", "PlayerSpawnTimeOutCooldown", (float)3); hasArgumentException = false; diff --git a/nuget.config b/nuget.config index b8a02a9a43..f32ce0d209 100644 --- a/nuget.config +++ b/nuget.config @@ -1,8 +1,9 @@ - - - - + + + + + From ad825a6892c2bd045693d03f855a92c8c7ad2dda Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 8 Sep 2024 14:33:17 +0800 Subject: [PATCH 495/778] Skip modded client --- Modules/CustomRolesHelper.cs | 2 +- Patches/ShipStatusPatch.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 3333615dd3..932d635b40 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -18,7 +18,7 @@ public static class CustomRolesHelper public static readonly Custom_Team[] AllRoleTypes = EnumHelper.GetAllValues(); public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor, Shapeshifter, Crewmate, Engineer, Scientist { - // Vanilla rolesf + // Vanilla roles if (role.IsVanilla()) return role; // Role base diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 0a9c5d7ae9..144dce8485 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -264,8 +264,8 @@ class ShipStatusSpawnPlayerPatch { public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { - // Skip first spawn - if (initialSpawn) return true; + // Skip first spawn and modded clients + if (!AmongUsClient.Instance.AmHost || initialSpawn) return true; Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); Vector2 position = (initialSpawn ? __instance.InitialSpawnCenter : __instance.MeetingSpawnCenter) + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); @@ -279,8 +279,8 @@ class PolusShipStatusSpawnPlayerPatch { public static bool Prefix(PolusShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { - // Skip first spawn - if (initialSpawn) return true; + // Skip first spawn and modded clients + if (!AmongUsClient.Instance.AmHost || initialSpawn) return true; int num1 = Mathf.FloorToInt(numPlayers / 2f); int num2 = player.PlayerId % 15; From 2b5ae6e7497c1340666cdfe7f35a93c42deb1209 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 8 Sep 2024 20:19:31 +0800 Subject: [PATCH 496/778] Port CreateIDLabels & fix custom intro --- Modules/GuessManager.cs | 39 +++++++++++++++++++++++++ Patches/CheckGameEndPatch.cs | 2 ++ Patches/ClientOptionsPatch.cs | 13 ++++++++- Patches/DialogueBoxPatch.cs | 22 ++++++++++++-- Patches/HudPatch.cs | 19 ++++++++++++ Patches/IntroPatch.cs | 16 ++++------ Patches/MeetingHudPatch.cs | 55 +++++++++++++++-------------------- Patches/ShipStatusPatch.cs | 11 +++---- 8 files changed, 128 insertions(+), 49 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index e698c3ac08..77c3475477 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -1044,6 +1044,8 @@ public static void Postfix(MeetingHud __instance) return; } + MeetingHudPopulateButtonsPatch.AlredyCreated = false; + DestroyIDLabels(); UnityEngine.Object.Destroy(textTemplate.gameObject); } } @@ -1069,4 +1071,41 @@ public static void ReceiveRPC(MessageReader reader, PlayerControl pc) GuesserMsg(pc, $"/bt {PlayerId} {GetString(role.ToString())}", true); } + + private static List IDPanels = []; + public static void CreateIDLabels(MeetingHud __instance) + { + DestroyIDLabels(); + if (__instance == null) return; + const int max = 2; + foreach (var pva in __instance.playerStates) + { + if (pva == null) continue; + var levelDisplay = pva.transform.FindChild("PlayerLevel").gameObject; + var panel = UnityEngine.Object.Instantiate(levelDisplay); + panel.gameObject.name = "PlayerIDLabel"; + var panelTransform = panel.transform; + panelTransform.transform.SetParent(levelDisplay.transform); + panelTransform.localPosition = new(0f, -0.90f, levelDisplay.transform.localPosition.z); + var background = panel.GetComponent(); + background.color = Palette.Purple; + background.sortingOrder = max - 1; + var levelLabel = panelTransform.FindChild("LevelLabel").GetComponents()[0]; + levelLabel.DestroyTranslator(); + levelLabel.text = "ID"; + levelLabel.sortingOrder = max; + levelLabel.gameObject.name = "IDLabel"; + var levelNumber = panelTransform.FindChild("LevelNumber").GetComponent(); + levelNumber.text = pva.TargetPlayerId.ToString(); + levelNumber.sortingOrder = max; + levelNumber.gameObject.name = "IDNumber"; + IDPanels.Add(panel); + } + } + + public static void DestroyIDLabels() + { + IDPanels.ForEach(UnityEngine.Object.Destroy); + IDPanels = []; + } } \ No newline at end of file diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 0fab96ece7..8b3b02072a 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -80,6 +80,8 @@ public static bool Prefix() // Update all Notify Roles Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); + GuessManager.DestroyIDLabels(); + Logger.Info("Start end game", "CheckEndCriteria.Prefix"); if (reason == GameOverReason.ImpostorBySabotage && (CustomRoles.Jackal.RoleExist() || CustomRoles.Sidekick.RoleExist()) && Jackal.CanWinBySabotageWhenNoImpAlive.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.GetCustomRole().IsImpostorTeam())) diff --git a/Patches/ClientOptionsPatch.cs b/Patches/ClientOptionsPatch.cs index f1bdb377b8..06a226e726 100644 --- a/Patches/ClientOptionsPatch.cs +++ b/Patches/ClientOptionsPatch.cs @@ -146,12 +146,23 @@ static void SwitchVanillaButtonToggle() #endif } } - +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] +public static class OptionsMenuBehaviourOpenPatch +{ + public static void Postfix() + { + if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) + GuessManager.DestroyIDLabels(); + } +} [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] public static class OptionsMenuBehaviourClosePatch { public static void Postfix() { ClientOptionItem.CustomBackground?.gameObject.SetActive(false); + + if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) + GuessManager.CreateIDLabels(MeetingHud.Instance); } } \ No newline at end of file diff --git a/Patches/DialogueBoxPatch.cs b/Patches/DialogueBoxPatch.cs index ed9fcb0e83..dd18029ea8 100644 --- a/Patches/DialogueBoxPatch.cs +++ b/Patches/DialogueBoxPatch.cs @@ -1,14 +1,29 @@ namespace TOHE.Patches; + [HarmonyPatch(typeof(DialogueBox))] internal class DialogueBoxPatch { + [HarmonyPatch(nameof(DialogueBox.Show)), HarmonyPrefix] + public static bool Show_Prefix(DialogueBox __instance, string dialogue) + { + __instance.target.text = dialogue; + if (Minigame.Instance != null) + Minigame.Instance.Close(); + if (Minigame.Instance != null) + Minigame.Instance.Close(); + __instance.gameObject.SetActive(true); + return false; + } [HarmonyPatch(nameof(DialogueBox.Show)), HarmonyPostfix] - public static void Show_Postfix(DialogueBox __instance, [HarmonyArgument(0)]string dialouge) + public static void Show_Postfix(DialogueBox __instance, string dialogue) { - if (!PlayerControl.LocalPlayer.inVent && dialouge.Contains("tohe") && GameStates.IsInTask) + if (!PlayerControl.LocalPlayer.inVent && dialogue.Contains("tohe") && GameStates.IsInTask) { PlayerControl.LocalPlayer.ForceKillTimerContinue = true; } + + if (GameStates.IsMeeting) + GuessManager.DestroyIDLabels(); } [HarmonyPatch(nameof(DialogueBox.Hide)), HarmonyPostfix] @@ -18,6 +33,9 @@ public static void Hide_Postfix(DialogueBox __instance) { PlayerControl.LocalPlayer.ForceKillTimerContinue = false; } + + if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) + GuessManager.CreateIDLabels(MeetingHud.Instance); } /* diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index d25eb7e1aa..0575f7fd5f 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -264,6 +264,25 @@ public static void Prefix(ref MapOptions opts) } } } +[HarmonyPatch(typeof(MapTaskOverlay), nameof(MapTaskOverlay.Show))] +static class MapTaskOverlayShowPatch +{ + public static void Postfix() + { + if (GameStates.IsMeeting) + GuessManager.DestroyIDLabels(); + } +} + +[HarmonyPatch(typeof(MapTaskOverlay), nameof(MapTaskOverlay.Hide))] +static class MapTaskOverlayHidePatch +{ + public static void Postfix() + { + if (GameStates.IsMeeting && MeetingHud.Instance.state is not MeetingHud.VoteStates.Animating && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) + GuessManager.CreateIDLabels(MeetingHud.Instance); + } +} [HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.SetTaskText))] class TaskPanelBehaviourPatch { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 26b1dc6ff0..23676c0a88 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -21,22 +21,18 @@ public static void Prefix() { if (!AmongUsClient.Instance.AmHost || !GameStates.IsModHost || GameStates.IsHideNSeek) return; - _ = new LateTask(() => - { - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - pc.SetCustomIntro(); - } - }, 0.35f, "Set Custom Intro"); - _ = new LateTask(() => { if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) { StartGameHostPatch.RpcSetDisconnected(disconnected: false); - if (!AmongUsClient.Instance.IsGameOver) - DestroyableSingleton.Instance.SetHudActive(true); + DestroyableSingleton.Instance.SetHudActive(true); + + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + pc.SetCustomIntro(); + } } }, 0.6f, "Set Disconnected"); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 17bc4a29a8..42811db296 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using TMPro; using System; using System.Text; using TOHE.Roles.AddOns.Common; @@ -1145,27 +1146,6 @@ public static void Postfix(MeetingHud __instance) //pva.NameText.text = target.GetRealName(isMeeting: true); pva.NameText.text = pva.NameText.text.ApplyNameColorData(seer, target, true); - // Guesser Mode // - if (Options.GuesserMode.GetBool()) - { - if (Options.CrewmatesCanGuess.GetBool() && seer.GetCustomRole().IsCrewmate() && !seer.Is(CustomRoles.Judge) && !seer.Is(CustomRoles.Lookout) && !seer.Is(CustomRoles.Swapper) && !seer.Is(CustomRoles.Inspector)) - if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; - if (Options.ImpostorsCanGuess.GetBool() && (seer.GetCustomRole().IsImpostor() || seer.GetCustomRole().IsMadmate()) && !seer.Is(CustomRoles.Councillor)) - if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; - if (Options.NeutralKillersCanGuess.GetBool() && seer.GetCustomRole().IsNK()) - if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; - if (Options.NeutralApocalypseCanGuess.GetBool() && seer.GetCustomRole().IsNA()) - if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; - if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) - if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; - - } - //if (seer.KnowDeathReason(target)) // sb.Append($"『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』"); @@ -1184,16 +1164,16 @@ public static void Postfix(MeetingHud __instance) pva.NameText.text = tempNemeText; } - foreach (var SeerSubRole in seer.GetCustomSubRoles().ToArray()) - { - switch (SeerSubRole) - { - case CustomRoles.Guesser: - if (!seer.Data.IsDead && !target.Data.IsDead) - pva.NameText.text = ColorString(GetRoleColor(CustomRoles.Guesser), target.PlayerId.ToString()) + " " + pva.NameText.text; - break; - } - } + //foreach (var SeerSubRole in seer.GetCustomSubRoles().ToArray()) + //{ + // switch (SeerSubRole) + // { + // case CustomRoles.Guesser: + // if (!seer.Data.IsDead && !target.Data.IsDead) + // pva.NameText.text = ColorString(GetRoleColor(CustomRoles.Guesser), target.PlayerId.ToString()) + " " + pva.NameText.text; + // break; + // } + //} //bool isLover = false; foreach (var TargetSubRole in target.GetCustomSubRoles().ToArray()) @@ -1219,6 +1199,19 @@ public static void Postfix(MeetingHud __instance) } } } +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateButtons))] +class MeetingHudPopulateButtonsPatch +{ + public static bool AlredyCreated = false; + public static void Postfix(MeetingHud __instance) + { + if (AlredyCreated) return; + AlredyCreated = true; + + // Create all ID Label + GuessManager.CreateIDLabels(__instance); + } +} [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] class MeetingHudUpdatePatch { diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 144dce8485..6fcab40906 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -256,8 +256,9 @@ public static void Postfix() } /* - Since SnapTo is unstable on the server side and after a meeting all players sometimes do not appear on the table - So better to use RpcTeleport + // Since SnapTo is unstable on the server side, + // after a meeting, sometimes not all players appear on the table, + // it's better to manually teleport them */ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.SpawnPlayer))] class ShipStatusSpawnPlayerPatch @@ -265,10 +266,10 @@ class ShipStatusSpawnPlayerPatch public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { // Skip first spawn and modded clients - if (!AmongUsClient.Instance.AmHost || initialSpawn) return true; + if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true; Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); - Vector2 position = (initialSpawn ? __instance.InitialSpawnCenter : __instance.MeetingSpawnCenter) + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); + Vector2 position = __instance.MeetingSpawnCenter + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); player.RpcTeleport(position, sendInfoInLogs: false); return false; @@ -280,7 +281,7 @@ class PolusShipStatusSpawnPlayerPatch public static bool Prefix(PolusShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) { // Skip first spawn and modded clients - if (!AmongUsClient.Instance.AmHost || initialSpawn) return true; + if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true; int num1 = Mathf.FloorToInt(numPlayers / 2f); int num2 = player.PlayerId % 15; From b6eef02a3c26f25bd1ad14fd455d57ae989fb740 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 8 Sep 2024 20:21:28 +0800 Subject: [PATCH 497/778] Hmm --- Modules/GuessManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 77c3475477..30ce4858d0 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -1072,6 +1072,7 @@ public static void ReceiveRPC(MessageReader reader, PlayerControl pc) GuesserMsg(pc, $"/bt {PlayerId} {GetString(role.ToString())}", true); } + // From EHR (By Gurge44 https://github.com/Gurge44/EndlessHostRoles) private static List IDPanels = []; public static void CreateIDLabels(MeetingHud __instance) { From d738b9d663424dd014c6fc2e39401c6c888e8451 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 9 Sep 2024 01:57:22 +0800 Subject: [PATCH 498/778] Some improvements & merge RPC for arrow --- Modules/AdminProvider.cs | 2 +- Modules/AntiBlackout.cs | 4 +- Modules/Camouflague.cs | 14 +- Modules/ChatManager.cs | 4 +- Modules/CustomWinnerHolder.cs | 2 +- Modules/ExtendedPlayerControl.cs | 247 +++++++++++++---------- Modules/GameState.cs | 3 +- Modules/HazelExtensions.cs | 49 +++++ Modules/LocateArrow.cs | 109 +++++++---- Modules/NameColorManager.cs | 2 +- Modules/RPC.cs | 42 +--- Modules/TargetArrow.cs | 124 ++++++++---- Modules/Utils.cs | 7 +- Patches/ChatBubblePatch.cs | 2 +- Patches/ExilePatch.cs | 4 +- Patches/PlayerControlPatch.cs | 4 +- Patches/onGameStartedPatch.cs | 5 +- Roles/(Ghosts)/Crewmate/Ghastly.cs | 305 ++++++++++++++--------------- Roles/AddOns/Common/Radar.cs | 85 ++------ Roles/Crewmate/Altruist.cs | 19 +- Roles/Crewmate/Coroner.cs | 56 ------ Roles/Crewmate/Mortician.cs | 25 --- Roles/Crewmate/Snitch.cs | 7 - Roles/Crewmate/Spiritualist.cs | 17 +- Roles/Crewmate/Tracefinder.cs | 25 --- Roles/Impostor/BountyHunter.cs | 2 - Roles/Impostor/EvilTracker.cs | 6 +- Roles/Neutral/Amnesiac.cs | 25 --- Roles/Neutral/Solsticer.cs | 16 +- Roles/Neutral/Vulture.cs | 26 --- 30 files changed, 593 insertions(+), 645 deletions(-) create mode 100644 Modules/HazelExtensions.cs diff --git a/Modules/AdminProvider.cs b/Modules/AdminProvider.cs index 0af34bd89c..6cfa012000 100644 --- a/Modules/AdminProvider.cs +++ b/Modules/AdminProvider.cs @@ -55,7 +55,7 @@ public static SortedDictionary CalculateAdmin() totalPlayers++; numDeadBodies++; // If it the impostor's dead body - if (Utils.GetPlayerById(deadBody.ParentId)?.Is(Custom_Team.Impostor) == true) + if (deadBody.ParentId.GetPlayer()?.Is(Custom_Team.Impostor) == true) { numImpostors++; } diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 6e06c20eda..4ec21299a4 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -243,8 +243,8 @@ public static void SetRealPlayerRoles() // skip host if (seerId == 0) continue; - var seer = Utils.GetPlayerById(seerId); - var target = Utils.GetPlayerById(targetId); + var seer = seerId.GetPlayer(); + var target = targetId.GetPlayer(); if (seer == null || target == null) continue; if (seer.IsModded()) continue; diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index 696c01f9e5..be77fb22aa 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -164,10 +164,10 @@ public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bo if (!(AmongUsClient.Instance.AmHost && (Options.CommsCamouflage.GetBool() || Camouflager.HasEnabled))) return; if (target == null) return; - var id = target.PlayerId; + var targetId = target.PlayerId; // if player dead, and Camouflage active, return - if (IsCamouflage && Main.PlayerStates[id].IsDead) + if (IsCamouflage && Main.PlayerStates[targetId].IsDead) { return; } @@ -182,13 +182,13 @@ public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bo if (!IsCamouflage || ForceRevert) { // if player are a shapeshifter, change to the id of your current Outfit - if (Main.CheckShapeshift.TryGetValue(id, out var shapeshifting) && shapeshifting && !RevertToDefault) + if (Main.CheckShapeshift.TryGetValue(targetId, out var shapeshifting) && shapeshifting && !RevertToDefault) { - id = Main.ShapeshiftTarget[id]; + targetId = Main.ShapeshiftTarget[targetId]; } - bool Hasovveride = Main.OvverideOutfit.TryGetValue(id, out var RealOutfit); + bool Hasovveride = Main.OvverideOutfit.TryGetValue(targetId, out var RealOutfit); // if game not end and Something clone skins if (!GameEnd && Hasovveride) @@ -201,11 +201,11 @@ public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bo // if game end, set normal name if (GameEnd && Hasovveride) { - Utils.GetPlayerById(id)?.RpcSetName(RealOutfit.name); + targetId.GetPlayer()?.RpcSetName(RealOutfit.name); } // Set Outfit - newOutfit = PlayerSkins[id]; + newOutfit = PlayerSkins[targetId]; } } diff --git a/Modules/ChatManager.cs b/Modules/ChatManager.cs index fd4bb94543..9a1560c6b7 100644 --- a/Modules/ChatManager.cs +++ b/Modules/ChatManager.cs @@ -247,7 +247,7 @@ public static void SendPreviousMessagesToAll() var entry = chatHistory[i]; var senderId = entry.Keys.First(); var senderMessage = entry[senderId]; - var senderPlayer = Utils.GetPlayerById(senderId); + var senderPlayer = senderId.GetPlayer(); if (senderPlayer == null) continue; var playerDead = !senderPlayer.IsAlive(); @@ -273,7 +273,7 @@ public static void SendPreviousMessagesToAll() } foreach (var playerId in LastSystemChatMsg.Keys.ToArray()) { - var pc = Utils.GetPlayerById(playerId); + var pc = playerId.GetPlayer(); if (pc == null && playerId != byte.MaxValue) continue; var title = "" + GetString("LastMessageReplay") + ""; Utils.SendMessage(LastSystemChatMsg[playerId], playerId, title: title, noReplay: true); diff --git a/Modules/CustomWinnerHolder.cs b/Modules/CustomWinnerHolder.cs index 771bde02db..2ce81e9f5d 100644 --- a/Modules/CustomWinnerHolder.cs +++ b/Modules/CustomWinnerHolder.cs @@ -51,7 +51,7 @@ public static void ResetAndSetWinner(CustomWinner winner) } public static bool CheckForConvertedWinner(byte playerId) { - foreach (var role in Utils.GetPlayerById(playerId).GetCustomSubRoles().ToArray()) + foreach (var role in playerId.GetPlayer()?.GetCustomSubRoles().ToArray()) { if (!(role == CustomRoles.Madmate || role == CustomRoles.Admired || role.IsConverted())) continue; switch (role) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 695bee69a1..774c14d57c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -12,6 +12,7 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -64,108 +65,179 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* writer.Write(true); AmongUsClient.Instance.FinishRpcImmediately(writer); } + + public static (RoleTypes RoleType, CustomRoles CustomRole) GetRoleMap(this PlayerControl player, byte targetId = byte.MaxValue) => Utils.GetRoleMap(player.PlayerId, targetId); + + /// + /// Revives the player if the given roletype is alive and player is dead. + /// + public static void RpcRevive(this PlayerControl player) + { + if (player == null) return; + if (!player.Data.IsDead) + { + Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); + return; + } + + Main.PlayerStates[player.PlayerId].IsDead = false; + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; + player.RpcChangeRoleBasis(player.GetRoleMap().CustomRole, true); + player.ResetKillCooldown(); + player.SyncSettings(); + player.SetKillCooldown(); + player.RpcResetAbilityCooldown(); + player.SyncGeneralOptions(); + } /// /// Changes the Role Basis of player during the game /// /// The custom role to change and auto set role type for others public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false) { - if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null) return; var playerId = player.PlayerId; - var playerRole = Utils.GetRoleMap(playerId).customRole; + var playerClientId = player.GetClientId(); + var playerRole = Utils.GetRoleMap(playerId).CustomRole; var newRoleType = newCustomRole.GetRoleTypes(); RoleTypes remeberRoleType; - // When player change desync role to normal role - if (playerRole.IsDesyncRole() && !newCustomRole.IsDesyncRole()) - { - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - var seerClientId = seer.GetClientId(); - if (seerClientId == -1) continue; - - var isSelf = player.PlayerId == seer.PlayerId; - if (!isSelf && seer.HasDesyncRole() && !seer.IsHost()) - { - remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; - } - else - remeberRoleType = newRoleType; + var oldRoleIsDesync = playerRole.IsDesyncRole(); + var newRoleIsDesync = newCustomRole.IsDesyncRole(); - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seerClientId); - } - } - // When player change normal role to desync role - else if (!playerRole.IsDesyncRole() && newCustomRole.IsDesyncRole()) + switch (oldRoleIsDesync, newRoleIsDesync) { - var isHost = player.IsHost(); - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - var seerClientId = seer.GetClientId(); - if (seerClientId == -1) continue; - - var isSelf = player.PlayerId == seer.PlayerId; - if (isSelf) + // Desync role to normal role + case (true, false): { - if (isHost) - remeberRoleType = RoleTypes.Crewmate; - else - remeberRoleType = RoleTypes.Impostor; - - // For Desync Shapeshifter - if (newCustomRole.GetDYRole() is RoleTypes.Shapeshifter) + foreach (var seer in Main.AllPlayerControls) { - remeberRoleType = RoleTypes.Shapeshifter; + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + var self = player.PlayerId == seer.PlayerId; + + if (!self && seer.HasDesyncRole() && !seer.IsHost()) + remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + else remeberRoleType = newRoleType; + + // Set role type for seer + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); + + if (self) continue; + + var (seerRoleType, seerCustomRole) = seer.GetRoleMap(); + if (seer.IsAlive()) + { + if (seerCustomRole.IsDesyncRole()) + remeberRoleType = RoleTypes.Scientist; + else + remeberRoleType = seerRoleType; + } + else + { + var playerIsKiller = playerRole.IsImpostor() || newRoleIsDesync; + + remeberRoleType = RoleTypes.CrewmateGhost; + if (!playerIsKiller && seer.Is(Custom_Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; + + RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? RoleTypes.Scientist : seerRoleType, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); + continue; + } + + // Set role type for player + RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); } + + break; } - else + // Normal role to desync role + case (false, true): { - if (!isHost && seer.HasDesyncRole()) + foreach (var seer in Main.AllPlayerControls) { - remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + var self = player.PlayerId == seer.PlayerId; + + if (self) + { + remeberRoleType = player.IsHost() ? RoleTypes.Crewmate : RoleTypes.Impostor; + + // For Desync Shapeshifter + if (newCustomRole.GetDYRole() is RoleTypes.Shapeshifter) + remeberRoleType = RoleTypes.Shapeshifter; + } + else + { + if (!player.IsHost() && seer.HasDesyncRole()) remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + else remeberRoleType = newRoleType; + } + + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); + + if (self) continue; + + var seerCustomRole = seer.GetRoleMap().CustomRole; + if (seer.IsAlive()) + { + if (newCustomRole.IsDesyncRole()) + remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + } + else + { + var playerIsKiller = playerRole.IsImpostor() || newRoleIsDesync; + + remeberRoleType = RoleTypes.CrewmateGhost; + if (!playerIsKiller && seer.Is(Custom_Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; + + RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); + continue; + } + + // Set role type for player + RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); } - else - remeberRoleType = newRoleType; - } - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seerClientId); - } - } - // When player change desync role to desync role - // Or player change normal role to normal role - else - { - var playerIsDesync = player.HasDesyncRole(); - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - var seerClientId = seer.GetClientId(); - if (seerClientId == -1) continue; - if ((playerIsDesync || seer.HasDesyncRole()) && seer.PlayerId != playerId) - { - remeberRoleType = Utils.GetRoleMap(seer.PlayerId, playerId).roleType; + break; } - else - remeberRoleType = newRoleType; + // Desync role to desync role + // Normal role to normal role + default: + { + var playerIsDesync = player.HasDesyncRole(); + foreach (var seer in Main.AllPlayerControls) + { + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; - RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seerClientId); - } + if ((playerIsDesync || seer.HasDesyncRole()) && seer.PlayerId != playerId) + remeberRoleType = Utils.GetRoleMap(seer.PlayerId, playerId).RoleType; + else remeberRoleType = newRoleType; + + RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); + } + + break; + } } if (loggerRoleMap) { - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) + foreach (var ((seerId, targetId), (roleType, customRole)) in RpcSetRoleReplacer.RoleMap) { - foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - var (roleType, customRole) = Utils.GetRoleMap(seer.PlayerId, target.PlayerId); - Logger.Info($"seer {seer?.Data?.PlayerName}-{seer.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {roleType}, {customRole}", "Role Map"); - } + Logger.Info($"seer {Utils.GetPlayerInfoById(seerId)?.PlayerName}-{seerId}, target {Utils.GetPlayerInfoById(targetId)?.PlayerName}-{targetId} => {roleType}, {customRole}", "Role Map"); } } + + Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis"); } public static void RpcExile(this PlayerControl player) { @@ -322,25 +394,6 @@ public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, AmongUsClient.Instance.FinishRpcImmediately(writer); } /// - /// Revives the player if the given roletype is alive and player is dead. - /// - public static void RpcRevive(this PlayerControl player) - { - if (player == null) return; - if (player.Data.IsDead == false) - { - Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); - return; - } - - Main.PlayerStates[player.PlayerId].IsDead = false; - player.SetDeathReason(PlayerState.DeathReason.etc); - player.RpcChangeRoleBasis(Utils.GetRoleMap(player.PlayerId).customRole, true); - player.SetKillCooldown(); - player.RpcResetAbilityCooldown(); - player.SyncGeneralOptions(); - } - /// /// ONLY to be used when killer surely may kill the target, please check with killer.RpcCheckAndMurder(target, check: true) for indirect kill. /// public static void RpcMurderPlayer(this PlayerControl killer, PlayerControl target) @@ -1060,10 +1113,6 @@ public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, Glow.Remove(Killed.PlayerId); Glow.Add(target.PlayerId); break; - case CustomRoles.Radar: - Radar.Remove(Killed.PlayerId); - Radar.Add(target.PlayerId); - break; case CustomRoles.Rebirth: Rebirth.Remove(Killed.PlayerId); Rebirth.Add(target.PlayerId); @@ -1094,7 +1143,7 @@ public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPla reporter.RpcStartMeeting(target); } public static bool IsHost(this InnerNetObject innerObject) => innerObject.OwnerId == AmongUsClient.Instance.HostId; - public static bool IsHost(this byte id) => Utils.GetPlayerById(id)?.OwnerId == AmongUsClient.Instance.HostId; + public static bool IsHost(this byte playerId) => playerId.GetPlayer()?.OwnerId == AmongUsClient.Instance.HostId; public static bool IsModded(this PlayerControl player) => player != null && (player.AmOwner || player.IsHost() || Main.playerVersion.ContainsKey(player.GetClientId())); public static bool IsNonHostModdedClient(this PlayerControl pc) => !pc.IsHost() && Main.playerVersion.ContainsKey(pc.GetClientId()); /// @@ -1400,18 +1449,18 @@ public static void SetRealKiller(this PlayerControl target, PlayerControl killer public static PlayerControl GetRealKiller(this PlayerControl target) { var killerId = Main.PlayerStates[target.Data.PlayerId].GetRealKiller(); - return killerId == byte.MaxValue ? null : Utils.GetPlayerById(killerId); + return killerId == byte.MaxValue ? null : killerId.GetPlayer(); } public static PlayerControl GetRealKiller(this PlayerControl target, out CustomRoles killerRole) { var killerId = Main.PlayerStates[target.Data.PlayerId].GetRealKiller(); killerRole = Main.PlayerStates[target.Data.PlayerId].RoleofKiller; - return killerId == byte.MaxValue ? null : Utils.GetPlayerById(killerId); + return killerId == byte.MaxValue ? null : killerId.GetPlayer(); } public static PlayerControl GetRealKillerById(this byte targetId) { var killerId = Main.PlayerStates[targetId].GetRealKiller(); - return killerId == byte.MaxValue ? null : Utils.GetPlayerById(killerId); + return killerId == byte.MaxValue ? null : killerId.GetPlayer(); } public static PlainShipRoom GetPlainShipRoom(this PlayerControl pc) { diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 39e51f8d1b..2a068392e3 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -39,7 +39,8 @@ public void SetMainRole(CustomRoles role) countTypes = role.GetCountTypes(); RoleClass = role.CreateRoleClass(); - var pc = GetPlayerById(PlayerId); + var pc = PlayerId.GetPlayer(); + if (pc == null) return; if (role == CustomRoles.Opportunist) { diff --git a/Modules/HazelExtensions.cs b/Modules/HazelExtensions.cs new file mode 100644 index 0000000000..114ce6739e --- /dev/null +++ b/Modules/HazelExtensions.cs @@ -0,0 +1,49 @@ +using Hazel; +using UnityEngine; + +//Thanks EHR - https://github.com/Gurge44/EndlessHostRoles/blob/main/Modules/Extensions/HazelExtensions.cs + + +namespace TOHE.Modules +{ + public static class HazelExtensions + { + public static void Write(this MessageWriter writer, Vector2 vector) => NetHelpers.WriteVector2(vector, writer); + + public static void Write(this MessageWriter writer, Vector3 vector) + { + writer.Write(vector.x); + writer.Write(vector.y); + writer.Write(vector.z); + } + + public static void Write(this MessageWriter writer, Color color) + { + writer.Write(color.r); + writer.Write(color.g); + writer.Write(color.b); + writer.Write(color.a); + } + + // ------------------------------------------------------------------------------------------------------------------------- + + public static Vector2 ReadVector2(this MessageReader reader) => NetHelpers.ReadVector2(reader); + + public static Vector3 ReadVector3(this MessageReader reader) + { + float x = reader.ReadSingle(); + float y = reader.ReadSingle(); + float z = reader.ReadSingle(); + return new(x, y, z); + } + + public static Color ReadColor(this MessageReader reader) + { + float r = reader.ReadSingle(); + float g = reader.ReadSingle(); + float b = reader.ReadSingle(); + float a = reader.ReadSingle(); + return new(r, g, b, a); + } + } +} \ No newline at end of file diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index 6cac528e84..5f21324863 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -1,26 +1,15 @@ +using Hazel; using UnityEngine; +using TOHE.Modules; namespace TOHE; static class LocateArrow { - class ArrowInfo(byte from, Vector3 to) - { - public byte From = from; - public Vector3 To = to; - - public bool Equals(ArrowInfo obj) - { - return From == obj.From && To == obj.To; - } - public override string ToString() - { - return $"(From:{From} To:{To})"; - } - } - static readonly Dictionary LocateArrows = []; - static readonly string[] Arrows = [ + + static readonly string[] Arrows = + [ "↑", "↗", "→", @@ -36,44 +25,82 @@ public static void Init() { LocateArrows.Clear(); } + + public static void SendRPC(int index, byte seer, Vector3 vector3) + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable); + writer.Write(false); + writer.WritePacked(index); + writer.Write(seer); + writer.Write(vector3); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + Add(reader.ReadByte(), reader.ReadVector3()); + break; + case 2: + Remove(reader.ReadByte(), reader.ReadVector3()); + break; + case 3: + RemoveAllTarget(reader.ReadByte()); + break; + } + } + /// /// Register a new target arrow object /// /// - /// - /// + /// public static void Add(byte seer, Vector3 locate) { var arrowInfo = new ArrowInfo(seer, locate); if (!LocateArrows.Any(a => a.Key.Equals(arrowInfo))) + { LocateArrows[arrowInfo] = "・"; + SendRPC(1, seer, locate); + Logger.Info($"New locate arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {locate}", "LocateArrow"); + } } + /// /// Delete target /// /// - /// + /// public static void Remove(byte seer, Vector3 locate) { var arrowInfo = new ArrowInfo(seer, locate); var removeList = new List(LocateArrows.Keys.Where(k => k.Equals(arrowInfo))); - foreach (var a in removeList.ToArray()) + foreach (ArrowInfo a in removeList.ToArray()) { LocateArrows.Remove(a); } + + SendRPC(2, seer, locate); + Logger.Info($"Removed locate arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {locate}", "LocateArrow"); } + /// - /// Delete all targets of the same type + /// Delete all targets for the specified seer /// /// public static void RemoveAllTarget(byte seer) { var removeList = new List(LocateArrows.Keys.Where(k => k.From == seer)); - foreach (var arrowInfo in removeList.ToArray()) + foreach (ArrowInfo arrowInfo in removeList.ToArray()) { LocateArrows.Remove(arrowInfo); } + + SendRPC(3, seer, Vector3.up); + Logger.Info($"Removed all locate arrows for: {seer} ({seer.GetPlayer()?.GetRealName()})", "LocateArrow"); } + /// /// Get all visible target arrows /// @@ -81,14 +108,9 @@ public static void RemoveAllTarget(byte seer) /// public static string GetArrows(PlayerControl seer) { - var arrows = ""; - foreach (var arrowInfo in LocateArrows.Keys.Where(ai => ai.From == seer.PlayerId).ToArray()) - { - arrows += LocateArrows[arrowInfo]; - } - return arrows; + return LocateArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + LocateArrows[arrowInfo]) ?? string.Empty; } - public static bool HasLocateArrows(PlayerControl seer) => LocateArrows.Keys.Any(a => a.From == seer.PlayerId); + /// /// Check target arrow every FixedUpdate /// Issue NotifyRoles when there are updates @@ -101,7 +123,7 @@ public static void OnFixedUpdate(PlayerControl seer) var seerId = seer.PlayerId; var seerIsDead = !seer.IsAlive(); - var arrowList = new List(LocateArrows.Keys.Where(a => a.From == seer.PlayerId)); + var arrowList = new List(LocateArrows.Keys.Where(a => a.From == seerId)); if (arrowList.Count == 0) return; var update = false; @@ -114,12 +136,13 @@ public static void OnFixedUpdate(PlayerControl seer) update = true; continue; } - //Get the target direction vector + + // Take the direction vector of the target var dir = loc - seer.transform.position; int index; if (dir.magnitude < 2) { - //Display dots when close + // Display a dot when close index = 8; } else @@ -131,6 +154,7 @@ public static void OnFixedUpdate(PlayerControl seer) var angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5; index = ((int)(angle / 45)) % 8; } + var arrow = Arrows[index]; if (LocateArrows[arrowInfo] != arrow) { @@ -138,9 +162,26 @@ public static void OnFixedUpdate(PlayerControl seer) update = true; } } - if (update && !seer.AmOwner) + + if (update && !seer.IsModded()) { Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false); } } -} + + class ArrowInfo(byte from, Vector3 to) + { + public byte From = from; + public Vector3 To = to; + + public bool Equals(ArrowInfo obj) + { + return From == obj.From && To == obj.To; + } + + public override string ToString() + { + return $"(From:{From} To:{To})"; + } + } +} \ No newline at end of file diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index ce1b13cc82..93df28dabb 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -111,7 +111,7 @@ public static void Add(byte seerId, byte targetId, string colorCode = "") { if (colorCode == "") { - var target = Utils.GetPlayerById(targetId); + var target = targetId.GetPlayer(); if (target == null) return; colorCode = target.GetRoleColorCode(); } diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 0a7a1ee2cd..0e856e54a4 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -15,7 +15,7 @@ namespace TOHE; -enum CustomRPC : byte // 198/255 USED +enum CustomRPC : byte // 193/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 @@ -52,6 +52,7 @@ enum CustomRPC : byte // 198/255 USED SyncShieldPersonDiedFirst, RemoveSubRole, SyncGeneralOptions, + Arrow, //Roles SetBountyTarget, @@ -99,24 +100,15 @@ enum CustomRPC : byte // 198/255 USED SetConcealerTimer, SetMedicalerProtectList, SyncPsychicRedList, - SetMorticianArrow, - SetAmnesaicArrows, - SetTracefinderArrow, PresidentEnd, PresidentReveal, SetBKTimer, SetCursedSoulCurseLimit, SetInvestgatorLimit, - SyncInvestigator, // Unused SetOverseerRevealedPlayer, SetOverseerTimer, - SetCoronerArrow, SpurtSync, - SetCoronerkKillerArrow, - SetVultureArrow, - SetRadarArrow, SyncVultureBodyAmount, - //SetTrackerTarget, SpyRedNameSync, SpyRedNameRemove, SetChameleonTimer, @@ -404,9 +396,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.EndGame: RPC.EndGame(reader); break; - case CustomRPC.SetRadarArrow: - Radar.ReceiveRPC(reader); - break; case CustomRPC.PlaySound: byte playerID = reader.ReadByte(); Sounds sound = (Sounds)reader.ReadByte(); @@ -433,6 +422,12 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SyncRoleSkill: RPC.SyncRoleSkillReader(reader); break; + case CustomRPC.Arrow: + { + if (reader.ReadBoolean()) TargetArrow.ReceiveRPC(reader); + else LocateArrow.ReceiveRPC(reader); + break; + } case CustomRPC.SetBountyTarget: BountyHunter.ReceiveRPC(reader); break; @@ -624,15 +619,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SyncFFANameNotify: FFAManager.ReceiveRPCSyncNameNotify(reader); break; - case CustomRPC.SetMorticianArrow: - Mortician.ReceiveRPC(reader); - break; - case CustomRPC.SetAmnesaicArrows: - Amnesiac.ReceiveRPC(reader); - break; - case CustomRPC.SetTracefinderArrow: - Tracefinder.ReceiveRPC(reader); - break; case CustomRPC.SyncNameNotify: NameNotifyManager.ReceiveRPC(reader); break; @@ -687,15 +673,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c Logger.Info($"Player {target.GetNameWithRole()} used /dump", "RPC_DumpLogger"); } break; - case CustomRPC.SetCoronerArrow: - Coroner.ReceiveRPC(reader); - break; - case CustomRPC.SetCoronerkKillerArrow: - Coroner.ReceiveRPCKiller(reader); - break; - case CustomRPC.SetVultureArrow: - Vulture.ReceiveRPC(reader); - break; case CustomRPC.SyncVultureBodyAmount: Vulture.ReceiveBodyRPC(reader); break; @@ -1007,9 +984,6 @@ public static void SetCustomRole(byte targetId, CustomRoles role) case CustomRoles.Aware: Aware.Add(targetId); break; - case CustomRoles.Radar: - Radar.Add(targetId); - break; case CustomRoles.Glow: Glow.Add(targetId); break; diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index aa944e5d7b..59d635f3bd 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -1,3 +1,4 @@ +using Hazel; using System; using UnityEngine; @@ -5,23 +6,10 @@ namespace TOHE; static class TargetArrow { - class ArrowInfo(byte from, byte to) - { - public byte From = from; - public byte To = to; - - public bool Equals(ArrowInfo obj) - { - return From == obj.From && To == obj.To; - } - public override string ToString() - { - return $"(From:{From} To:{To})"; - } - } - static readonly Dictionary TargetArrows = []; - static readonly string[] Arrows = [ + + static readonly string[] Arrows = + [ "↑", "↗", "→", @@ -37,18 +25,48 @@ public static void Init() { TargetArrows.Clear(); } + + public static void SendRPC(int index, byte seer, byte target = byte.MaxValue) + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable); + writer.Write(true); + writer.WritePacked(index); + writer.Write(seer); + writer.Write(target); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + Add(reader.ReadByte(), reader.ReadByte()); + break; + case 2: + Remove(reader.ReadByte(), reader.ReadByte()); + break; + case 3: + RemoveAllTarget(reader.ReadByte()); + break; + } + } + /// /// Register a new target arrow object /// /// /// - /// public static void Add(byte seer, byte target) { var arrowInfo = new ArrowInfo(seer, target); if (!TargetArrows.Any(a => a.Key.Equals(arrowInfo))) + { TargetArrows[arrowInfo] = "・"; + SendRPC(1, seer, target); + Logger.Info($"New target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow"); + } } + /// /// Delete target /// @@ -58,38 +76,55 @@ public static void Remove(byte seer, byte target) { var arrowInfo = new ArrowInfo(seer, target); var removeList = new List(TargetArrows.Keys.Where(k => k.Equals(arrowInfo))); - foreach (var a in removeList.ToArray()) + foreach (ArrowInfo a in removeList.ToArray()) { TargetArrows.Remove(a); } + + SendRPC(2, seer, target); + Logger.Info($"Removed target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow"); } + /// - /// Delete all targets of the same type + /// Delete all targets for the specified seer /// /// public static void RemoveAllTarget(byte seer) { var removeList = new List(TargetArrows.Keys.Where(k => k.From == seer)); - foreach (var arrowInfo in removeList.ToArray()) + foreach (ArrowInfo arrowInfo in removeList.ToArray()) { TargetArrows.Remove(arrowInfo); } + SendRPC(3, seer); + Logger.Info($"Removed all target arrows for {seer} ({seer.GetPlayer()?.GetRealName()})", "TargetArrow"); } + /// - /// Get all visible target arrows + /// Get all visible target arrows for the specified seer to the specified target(s) /// /// + /// /// public static string GetArrows(PlayerControl seer, params byte[] targets) { - var arrows = string.Empty; - foreach (var arrowInfo in TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId && targets.Contains(ai.To)).ToArray()) - { - arrows += TargetArrows[arrowInfo]; - } - return arrows; + return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId && targets.Contains(ai.To)).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]) ?? string.Empty; + } + public static string GetArrows(PlayerControl seer) + { + return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]) ?? string.Empty; + } + + /// + /// Get all visible target arrows for the specified seer + /// + /// + /// + public static string GetAllArrows(PlayerControl seer) + { + return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]) ?? string.Empty; } - public static bool HasTargetArrows(PlayerControl seer) => TargetArrows.Keys.Any(a => a.From == seer.PlayerId); + /// /// Check target arrow every FixedUpdate /// Issue NotifyRoles when there are updates @@ -102,26 +137,27 @@ public static void OnFixedUpdate(PlayerControl seer) var seerId = seer.PlayerId; var seerIsDead = !seer.IsAlive(); - var arrowList = new List(TargetArrows.Keys.Where(a => a.From == seer.PlayerId)); + var arrowList = new List(TargetArrows.Keys.Where(a => a.From == seerId)); if (!arrowList.Any()) return; var update = false; foreach (var arrowInfo in arrowList.ToArray()) { var targetId = arrowInfo.To; - var target = Utils.GetPlayerById(targetId); - if (seerIsDead || !target.IsAlive() && !seer.Is(CustomRoles.Spiritualist)) + var target = targetId.GetPlayer(); + if (seerIsDead || (!target.IsAlive() && !seer.Is(CustomRoles.Spiritualist))) { TargetArrows.Remove(arrowInfo); update = true; continue; } - //Get the target direction vector + + // Take the direction vector of the target var dir = target.transform.position - seer.transform.position; int index; if (dir.magnitude < 2) { - //Display dots when close + // Display a dot when close index = 8; } else @@ -133,6 +169,7 @@ public static void OnFixedUpdate(PlayerControl seer) var angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5; index = ((int)(angle / 45)) % 8; } + var arrow = Arrows[index]; if (TargetArrows[arrowInfo] != arrow) { @@ -140,9 +177,26 @@ public static void OnFixedUpdate(PlayerControl seer) update = true; } } - if (update && !seer.AmOwner) + + if (update && !seer.IsModded()) { Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false); } } -} + + class ArrowInfo(byte from, byte to) + { + public readonly byte From = from; + public readonly byte To = to; + + public bool Equals(ArrowInfo obj) + { + return From == obj.From && To == obj.To; + } + + public override string ToString() + { + return $"(From:{From} To:{To})"; + } + } +} \ No newline at end of file diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 4e91eb8800..b97078cded 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -569,7 +569,7 @@ public static string GetVitalText(byte playerId, bool RealKillerColor = false) return deathReason; } - public static (RoleTypes roleType, CustomRoles customRole) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) + public static (RoleTypes RoleType, CustomRoles CustomRole) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) { if (targetId == byte.MaxValue) targetId = seerId; @@ -1716,6 +1716,7 @@ public static PlayerControl GetPlayerById(int PlayerId) { return Main.AllPlayerControls.FirstOrDefault(pc => pc.PlayerId == PlayerId); } + public static PlayerControl GetPlayer(this byte id) => GetPlayerById(id); public static List GetPlayerListByIds(this IEnumerable PlayerIdList) { var PlayerList = PlayerIdList?.ToList().Select(x => GetPlayerById(x)).ToList(); @@ -1928,12 +1929,10 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SelfSuffix.Append(seerRoleClass?.GetLowerText(seer, seer, isForMeeting: isForMeeting)); SelfSuffix.Append(CustomRoleManager.GetLowerTextOthers(seer, seer, isForMeeting: isForMeeting)); - if (Radar.IsEnable) - SelfSuffix.Append(Radar.GetPlayerArrow(seer, isForMeeting: isForMeeting)); - SelfSuffix.Append(seerRoleClass?.GetSuffix(seer, seer, isForMeeting: isForMeeting)); SelfSuffix.Append(CustomRoleManager.GetSuffixOthers(seer, seer, isForMeeting: isForMeeting)); + SelfSuffix.Append(Radar.GetPlayerArrow(seer, isForMeeting: isForMeeting)); SelfSuffix.Append(Spurt.GetSuffix(seer, isformeeting: isForMeeting)); diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 0fd648b3bf..598f71b305 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -18,7 +18,7 @@ class ChatBubbleSetNamePatch public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDead, [HarmonyArgument(2)] bool voted) { var seer = PlayerControl.LocalPlayer; - var target = Utils.GetPlayerById(__instance.playerInfo.PlayerId); + var target = __instance.playerInfo.Object; if (GameStates.IsInGame && !voted && seer.PlayerId == target.PlayerId) __instance.NameText.color = seer.GetRoleColor(); diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 2e944e3281..5c381a87ab 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -149,10 +149,10 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { Main.AfterMeetingDeathPlayers.Do(x => { - var player = Utils.GetPlayerById(x.Key); + var player = x.Key.GetPlayer(); var state = Main.PlayerStates[x.Key]; - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} died with {x.Value}", "AfterMeetingDeath"); + Logger.Info($"{player?.GetNameWithRole().RemoveHtmlTags()} died with {x.Value}", "AfterMeetingDeath"); state.deathReason = x.Value; state.SetDead(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 6445d5f934..0b7acc9f11 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1285,12 +1285,10 @@ public static Task DoPostfix(PlayerControl __instance) Suffix.Append(CustomRoleManager.GetLowerTextOthers(seer, target, false, false)); - - if (Radar.IsEnable) Suffix.Append(Radar.GetPlayerArrow(seer, target, isForMeeting: false)); - Suffix.Append(seerRoleClass?.GetSuffix(seer, target, false)); Suffix.Append(CustomRoleManager.GetSuffixOthers(seer, target, false)); + Suffix.Append(Radar.GetPlayerArrow(seer, isForMeeting: false)); if (seerRole.IsImpostor() && target.GetPlayerTaskState().IsTaskFinished) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 0fbaaa567b..c8fe5ef77d 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -208,7 +208,6 @@ public static void Postfix(AmongUsClient __instance) Diseased.Init(); Clumsy.Init(); Aware.Init(); - Radar.Init(); Glow.Init(); Sleuth.Init(); Bait.Init(); @@ -227,6 +226,7 @@ public static void Postfix(AmongUsClient __instance) Rainbow.Init(); Rebirth.Init(); Evader.Init(); + Radar.Init(); //FFA FFAManager.Init(); @@ -490,9 +490,6 @@ public static System.Collections.IEnumerator AssignRoles() case CustomRoles.Aware: Aware.Add(pc.PlayerId); break; - case CustomRoles.Radar: - Radar.Add(pc.PlayerId); - break; case CustomRoles.Glow: Glow.Add(pc.PlayerId); break; diff --git a/Roles/(Ghosts)/Crewmate/Ghastly.cs b/Roles/(Ghosts)/Crewmate/Ghastly.cs index 1fde909ff0..183c99aad0 100644 --- a/Roles/(Ghosts)/Crewmate/Ghastly.cs +++ b/Roles/(Ghosts)/Crewmate/Ghastly.cs @@ -6,194 +6,193 @@ using UnityEngine; using TOHE.Roles.Double; -namespace TOHE.Roles._Ghosts_.Crewmate +namespace TOHE.Roles._Ghosts_.Crewmate; + +internal class Ghastly : RoleBase { - internal class Ghastly : RoleBase + //===========================SETUP================================\\ + private const int Id = 22060; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Ghastly); + public override CustomRoles ThisRoleBase => CustomRoles.GuardianAngel; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateGhosts; + //==================================================================\\ + + private static OptionItem PossessCooldown; + private static OptionItem MaxPossesions; + private static OptionItem PossessDur; + private static OptionItem GhastlySpeed; + private static OptionItem GhastlyKillAllies; + + private (byte, byte) killertarget = (byte.MaxValue, byte.MaxValue); + private readonly Dictionary LastTime = []; + private bool KillerIsChosen = false; + + public override void SetupCustomOption() { - //===========================SETUP================================\\ - private const int Id = 22060; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Ghastly); - public override CustomRoles ThisRoleBase => CustomRoles.GuardianAngel; - public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateGhosts; - //==================================================================\\ - - private static OptionItem PossessCooldown; - private static OptionItem MaxPossesions; - private static OptionItem PossessDur; - private static OptionItem GhastlySpeed; - private static OptionItem GhastlyKillAllies; - - private (byte, byte) killertarget = (byte.MaxValue, byte.MaxValue); - private readonly Dictionary LastTime = []; - private bool KillerIsChosen = false; - - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ghastly); - PossessCooldown = FloatOptionItem.Create(Id + 10, "GhastlyPossessCD", new(2.5f, 120f, 2.5f), 35f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) - .SetValueFormat(OptionFormat.Seconds); - MaxPossesions = IntegerOptionItem.Create(Id + 11, "GhastlyMaxPossessions", new(1, 99, 1), 10, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) - .SetValueFormat(OptionFormat.Players); - PossessDur = IntegerOptionItem.Create(Id + 12, "GhastlyPossessionDuration", new(5, 120, 5), 40, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) - .SetValueFormat(OptionFormat.Seconds); - GhastlySpeed = FloatOptionItem.Create(Id + 13, "GhastlySpeed", new(1.5f, 5f, 0.5f), 2f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) - .SetValueFormat(OptionFormat.Multiplier); - GhastlyKillAllies = BooleanOptionItem.Create(Id + 14, "GhastlyKillAllies", false, TabGroup.CrewmateRoles, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]); - } - public override void Add(byte playerId) - { - AbilityLimit = MaxPossesions.GetInt(); + SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ghastly); + PossessCooldown = FloatOptionItem.Create(Id + 10, "GhastlyPossessCD", new(2.5f, 120f, 2.5f), 35f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) + .SetValueFormat(OptionFormat.Seconds); + MaxPossesions = IntegerOptionItem.Create(Id + 11, "GhastlyMaxPossessions", new(1, 99, 1), 10, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) + .SetValueFormat(OptionFormat.Players); + PossessDur = IntegerOptionItem.Create(Id + 12, "GhastlyPossessionDuration", new(5, 120, 5), 40, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) + .SetValueFormat(OptionFormat.Seconds); + GhastlySpeed = FloatOptionItem.Create(Id + 13, "GhastlySpeed", new(1.5f, 5f, 0.5f), 2f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]) + .SetValueFormat(OptionFormat.Multiplier); + GhastlyKillAllies = BooleanOptionItem.Create(Id + 14, "GhastlyKillAllies", false, TabGroup.CrewmateRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ghastly]); + } + public override void Add(byte playerId) + { + AbilityLimit = MaxPossesions.GetInt(); - CustomRoleManager.OnFixedUpdateOthers.Add(OnFixUpdateOthers); - } + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixUpdateOthers); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.GuardianAngelCooldown = PossessCooldown.GetFloat(); + AURoleOptions.ProtectionDurationSeconds = 0f; + } + public override bool OnCheckProtect(PlayerControl angel, PlayerControl target) + { + if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) { - AURoleOptions.GuardianAngelCooldown = PossessCooldown.GetFloat(); - AURoleOptions.ProtectionDurationSeconds = 0f; + angel.Notify(ColorString(GetRoleColor(CustomRoles.Gangster), GetString("CantPosses"))); + return true; } - public override bool OnCheckProtect(PlayerControl angel, PlayerControl target) + if (AbilityLimit <= 0) { - if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) - { - angel.Notify(ColorString(GetRoleColor(CustomRoles.Gangster), GetString("CantPosses"))); - return true; - } - if (AbilityLimit <= 0) - { - angel.Notify(GetString("GhastlyNoMorePossess")); - return false; - } + angel.Notify(GetString("GhastlyNoMorePossess")); + return false; + } - var killer = killertarget.Item1; - var Target = killertarget.Item2; + var killer = killertarget.Item1; + var Target = killertarget.Item2; - if (!KillerIsChosen && !CheckConflicts(target)) - { - angel.Notify(GetString("GhastlyCannotPossessTarget")); - return false; - } + if (!KillerIsChosen && !CheckConflicts(target)) + { + angel.Notify(GetString("GhastlyCannotPossessTarget")); + return false; + } - if (!KillerIsChosen && target.PlayerId != killer) - { - TargetArrow.Remove(killer, Target); - LastTime.Remove(killer); - killer = target.PlayerId; - Target = byte.MaxValue; - KillerIsChosen = true; + if (!KillerIsChosen && target.PlayerId != killer) + { + TargetArrow.Remove(killer, Target); + LastTime.Remove(killer); + killer = target.PlayerId; + Target = byte.MaxValue; + KillerIsChosen = true; - angel.Notify($"\n{GetString("GhastlyChooseTarget")}\n"); - } - else if (KillerIsChosen && Target == byte.MaxValue && target.PlayerId != killer) - { - Target = target.PlayerId; - AbilityLimit--; - SendSkillRPC(); - LastTime.Add(killer, GetTimeStamp()); + angel.Notify($"\n{GetString("GhastlyChooseTarget")}\n"); + } + else if (KillerIsChosen && Target == byte.MaxValue && target.PlayerId != killer) + { + Target = target.PlayerId; + AbilityLimit--; + SendSkillRPC(); + LastTime.Add(killer, GetTimeStamp()); - KillerIsChosen = false; - GetPlayerById(killer)?.Notify(GetString("GhastlyYouvePosses")); - angel.Notify($"\n〘{string.Format(GetString("GhastlyPossessedUser"), "" + GetPlayerById(killer).GetRealName())} 〙\n"); + KillerIsChosen = false; + GetPlayerById(killer)?.Notify(GetString("GhastlyYouvePosses")); + angel.Notify($"\n〘{string.Format(GetString("GhastlyPossessedUser"), "" + GetPlayerById(killer).GetRealName())} 〙\n"); - TargetArrow.Add(killer, Target); - angel.RpcGuardAndKill(target); - angel.RpcResetAbilityCooldown(); + TargetArrow.Add(killer, Target); + angel.RpcGuardAndKill(target); + angel.RpcResetAbilityCooldown(); - Logger.Info($" chosen {target.GetRealName()} for : {GetPlayerById(killer).GetRealName()}", "GhastlyTarget"); - } - else if (target.PlayerId == killer) - { - angel.Notify(GetString("GhastlyCannotPossessTarget")); - } + Logger.Info($" chosen {target.GetRealName()} for : {GetPlayerById(killer).GetRealName()}", "GhastlyTarget"); + } + else if (target.PlayerId == killer) + { + angel.Notify(GetString("GhastlyCannotPossessTarget")); + } - killertarget = (killer, Target); + killertarget = (killer, Target); - return false; + return false; + } + private bool CheckConflicts(PlayerControl target) + { + return target != null && (!GhastlyKillAllies.GetBool() || target.GetCountTypes() != _Player.GetCountTypes()); + } + public override void OnFixedUpdate(PlayerControl pc) + { + var speed = Main.AllPlayerSpeed[pc.PlayerId]; + if (speed != GhastlySpeed.GetFloat()) + { + Main.AllPlayerSpeed[pc.PlayerId] = GhastlySpeed.GetFloat(); + pc.MarkDirtySettings(); } - private bool CheckConflicts(PlayerControl target) + } + public void OnFixUpdateOthers(PlayerControl player) + { + if (killertarget.Item1 == player.PlayerId + && LastTime.TryGetValue(player.PlayerId, out var now) && now + PossessDur.GetInt() <= GetTimeStamp()) { - return target != null && (!GhastlyKillAllies.GetBool() || target.GetCountTypes() != _Player.GetCountTypes()); + _Player?.Notify(string.Format($"\n{ GetString("GhastlyExpired")}\n", player.GetRealName())); + TargetArrow.Remove(killertarget.Item1, killertarget.Item2); + LastTime.Remove(player.PlayerId); + KillerIsChosen = false; + killertarget = (byte.MaxValue, byte.MaxValue); } - public override void OnFixedUpdate(PlayerControl pc) + + } + public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) + { + var tuple = killertarget; + if (tuple.Item1 == killer.PlayerId && tuple.Item2 != byte.MaxValue) { - var speed = Main.AllPlayerSpeed[pc.PlayerId]; - if (speed != GhastlySpeed.GetFloat()) + if (tuple.Item2 != target.PlayerId) { - Main.AllPlayerSpeed[pc.PlayerId] = GhastlySpeed.GetFloat(); - pc.MarkDirtySettings(); + killer.Notify(GetString("GhastlyNotUrTarget")); + return true; } - } - public void OnFixUpdateOthers(PlayerControl player) - { - if (killertarget.Item1 == player.PlayerId - && LastTime.TryGetValue(player.PlayerId, out var now) && now + PossessDur.GetInt() <= GetTimeStamp()) + else { - _Player?.Notify(string.Format($"\n{ GetString("GhastlyExpired")}\n", player.GetRealName())); + _Player?.Notify(string.Format($"\n{GetString("GhastlyExpired")}\n", killer.GetRealName())); TargetArrow.Remove(killertarget.Item1, killertarget.Item2); - LastTime.Remove(player.PlayerId); + LastTime.Remove(killer.PlayerId); KillerIsChosen = false; killertarget = (byte.MaxValue, byte.MaxValue); } - - } - public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) - { - var tuple = killertarget; - if (tuple.Item1 == killer.PlayerId && tuple.Item2 != byte.MaxValue) - { - if (tuple.Item2 != target.PlayerId) - { - killer.Notify(GetString("GhastlyNotUrTarget")); - return true; - } - else - { - _Player?.Notify(string.Format($"\n{GetString("GhastlyExpired")}\n", killer.GetRealName())); - TargetArrow.Remove(killertarget.Item1, killertarget.Item2); - LastTime.Remove(killer.PlayerId); - KillerIsChosen = false; - killertarget = (byte.MaxValue, byte.MaxValue); - } - } - return false; } + return false; + } - public override string GetLowerTextOthers(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) - { - var IsMeeting = GameStates.IsMeeting || isForMeeting; - if (IsMeeting || (seer != seen && seer.IsAlive())) return ""; + public override string GetLowerTextOthers(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + { + var IsMeeting = GameStates.IsMeeting || isForMeeting; + if (IsMeeting || (seer != seen && seer.IsAlive())) return ""; - var killer = killertarget.Item1; - var target = killertarget.Item2; + var killer = killertarget.Item1; + var target = killertarget.Item2; - if (killer == seen.PlayerId && target != byte.MaxValue) - { - var arrows = TargetArrow.GetArrows(GetPlayerById(killer), target); - var tar = GetPlayerById(target).GetRealName(); - if (tar == null) return ""; + if (killer == seen.PlayerId && target != byte.MaxValue) + { + var arrows = TargetArrow.GetArrows(GetPlayerById(killer), target); + var tar = GetPlayerById(target).GetRealName(); + if (tar == null) return ""; - var colorstring = ColorString(GetRoleColor(CustomRoles.Ghastly), "" + tar + arrows); - return colorstring; - } + var colorstring = ColorString(GetRoleColor(CustomRoles.Ghastly), "" + tar + arrows); + return colorstring; + } - return ""; - } - public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + return ""; + } + public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + { + var tuple = killertarget; + if (DeadPlayer.PlayerId == tuple.Item1 || DeadPlayer.PlayerId == tuple.Item2) { - var tuple = killertarget; - if (DeadPlayer.PlayerId == tuple.Item1 || DeadPlayer.PlayerId == tuple.Item2) - { - _Player?.Notify(string.Format($"\n{GetString("GhastlyExpired")}\n", Utils.GetPlayerById(killertarget.Item1))); - TargetArrow.Remove(killertarget.Item1, killertarget.Item2); - LastTime.Remove(DeadPlayer.PlayerId); - KillerIsChosen = false; - killertarget = (byte.MaxValue, byte.MaxValue); - } + _Player?.Notify(string.Format($"\n{GetString("GhastlyExpired")}\n", Utils.GetPlayerById(killertarget.Item1))); + TargetArrow.Remove(killertarget.Item1, killertarget.Item2); + LastTime.Remove(DeadPlayer.PlayerId); + KillerIsChosen = false; + killertarget = (byte.MaxValue, byte.MaxValue); } - - public override string GetProgressText(byte playerId, bool cooms) => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Ghastly).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); - } + + public override string GetProgressText(byte playerId, bool cooms) => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Ghastly).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + } diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index 1f9b36335b..df5811792d 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -1,14 +1,10 @@ -using Hazel; -using UnityEngine; -using static TOHE.Options; - +using static TOHE.Options; namespace TOHE.Roles.AddOns.Common; public class Radar : IAddon { private const int Id = 28200; - public static bool IsEnable = false; public AddonTypes Type => AddonTypes.Helpful; private static readonly Dictionary ClosestPlayer = []; @@ -21,79 +17,32 @@ public void SetupCustomOption() public static void Init() { ClosestPlayer.Clear(); - IsEnable = false; - } - public static void Add(byte playerId) - { - ClosestPlayer[playerId] = byte.MaxValue; - IsEnable = true; } - public static void Remove(byte playerId) - { - ClosestPlayer.Remove(playerId); - } - - private static void SendRPC(byte playerId, byte previousClosest) + public void OnFixedUpdateLowLoad(PlayerControl seer) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRadarArrow, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(previousClosest); - writer.Write(ClosestPlayer[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte radarId = reader.ReadByte(); - byte previousClosest = reader.ReadByte(); - TargetArrow.Remove(radarId, previousClosest); - byte closest = reader.ReadByte(); - TargetArrow.Add(radarId, closest); - ClosestPlayer[radarId] = closest; - } - - public void OnFixedUpdateLowLoad(PlayerControl radarPC) - { - if (!IsEnable || !radarPC.IsAlive() || !radarPC.Is(CustomRoles.Radar) || !GameStates.IsInTask) return; + if (!seer.Is(CustomRoles.Radar) || seer.inVent || !seer.IsAlive() || !GameStates.IsInTask) return; if (Main.AllAlivePlayerControls.Length <= 1) return; - if (!ClosestPlayer.ContainsKey(radarPC.PlayerId)) ClosestPlayer[radarPC.PlayerId] = byte.MaxValue; - byte previousClosest = ClosestPlayer[radarPC.PlayerId]; - - byte closestPlayerId = byte.MaxValue; - float closestDistance = Mathf.Infinity; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + PlayerControl closest = Main.AllAlivePlayerControls.Where(x => x.PlayerId != seer.PlayerId).MinBy(x => Utils.GetDistance(seer.GetCustomPosition(), x.GetCustomPosition())); + if (ClosestPlayer.TryGetValue(seer.PlayerId, out var targetId)) { - if (pc == radarPC) - continue; - - float distance = Utils.GetDistance(radarPC.GetCustomPosition(), pc.GetCustomPosition()); - - if (distance < closestDistance) + if (targetId != closest.PlayerId) { - closestDistance = distance; - closestPlayerId = pc.PlayerId; + ClosestPlayer[seer.PlayerId] = closest.PlayerId; + TargetArrow.Remove(seer.PlayerId, targetId); + TargetArrow.Add(seer.PlayerId, closest.PlayerId); } } - - if (closestPlayerId == previousClosest) return; - - TargetArrow.Remove(radarPC.PlayerId, previousClosest); - if (closestPlayerId == byte.MaxValue) return; - ClosestPlayer[radarPC.PlayerId] = closestPlayerId; - TargetArrow.Add(radarPC.PlayerId, closestPlayerId); - SendRPC(radarPC.PlayerId, previousClosest); - Logger.Info($"Radar: {radarPC.PlayerId} Target: {closestPlayerId}", "Radar Target"); + else + { + ClosestPlayer[seer.PlayerId] = closest.PlayerId; + TargetArrow.Add(seer.PlayerId, closest.PlayerId); + } } - public static string GetPlayerArrow(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) + public static string GetPlayerArrow(PlayerControl seer, bool isForMeeting = false) { - if (isForMeeting || seer == null) return string.Empty; - if (!seer.Is(CustomRoles.Radar) || !ClosestPlayer.ContainsKey(seer.PlayerId)) return string.Empty; - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - string arrow = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Radar), TargetArrow.GetArrows(seer, ClosestPlayer[seer.PlayerId])); - return arrow; + if (isForMeeting || !seer.Is(CustomRoles.Radar)) return string.Empty; + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Radar), TargetArrow.GetArrows(seer)); } } diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 1f4c6e0fc0..e030b58343 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -41,6 +41,10 @@ public override void Init() RevivedPlayerId = byte.MaxValue; AllRevivedPlayerId.Clear(); } + public override void Add(byte playerId) + { + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); + } public override void ApplyGameOptions(IGameOptions opt, byte playerId) { @@ -67,7 +71,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay if (deadPlayer.HasGhostRole()) { deadPlayer.GetRoleClass().Remove(deadPlayerId); - deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).customRole); + deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).CustomRole); deadPlayer.GetRoleClass().Add(deadPlayerId); } @@ -97,12 +101,19 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay return true; } - + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) + { + if (inMeeting) return; + // if Revived Player is dead again, clear RevivedPlayerId + if (RevivedPlayerId == target.PlayerId) + { + RevivedPlayerId = byte.MaxValue; + } + } public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (RevivedPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; - Logger.Info($"{TargetArrow.GetArrows(seer)}", "Altruist"); - return Utils.ColorString(Utils.HexToColor("9b0202"), TargetArrow.GetArrows(seer, RevivedPlayerId)); + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Altruist), TargetArrow.GetArrows(seer));; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) diff --git a/Roles/Crewmate/Coroner.cs b/Roles/Crewmate/Coroner.cs index 852c9ed17d..7c8fecd9a8 100644 --- a/Roles/Crewmate/Coroner.cs +++ b/Roles/Crewmate/Coroner.cs @@ -60,19 +60,6 @@ public override void Remove(byte playerId) CoronerTargets.Remove(playerId); } - private static void SendRPC(byte playerId, bool add, Vector3 loc = new()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCoronerArrow, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(add); - if (add) - { - writer.Write(loc.x); - writer.Write(loc.y); - writer.Write(loc.z); - } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } private void SendRPCLimit(byte playerId, int operate, byte targetId = 0xff) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); @@ -97,44 +84,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) if (opt == 1) UnreportablePlayers.Add(tid); } } - private static void SendRPCKiller(byte playerId, byte killerId, bool add) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCoronerkKillerArrow, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(killerId); - writer.Write(add); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - } - public static void ReceiveRPCKiller(MessageReader reader) - { - byte playerId = reader.ReadByte(); - byte killerId = reader.ReadByte(); - bool add = reader.ReadBoolean(); - if (add) - { - CoronerTargets[playerId].Add(killerId); - TargetArrow.Add(playerId, killerId); - } - else - { - CoronerTargets[playerId].Remove(killerId); - TargetArrow.Remove(playerId, killerId); - } - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - bool add = reader.ReadBoolean(); - if (add) - LocateArrow.Add(playerId, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); - else - { - LocateArrow.RemoveAllTarget(playerId); - if (CoronerTargets.ContainsKey(playerId)) CoronerTargets[playerId].Clear(); - } - } public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { @@ -173,13 +122,11 @@ private bool FindKiller(PlayerControl pc, NetworkedPlayerInfo deadBody, PlayerCo } LocateArrow.Remove(pc.PlayerId, deadBody.Object.transform.position); - SendRPC(pc.PlayerId, false); if (AbilityLimit >= 1) { CoronerTargets[pc.PlayerId].Add(killer.PlayerId); TargetArrow.Add(pc.PlayerId, killer.PlayerId); - SendRPCKiller(pc.PlayerId, killer.PlayerId, add: true); pc.Notify(GetString("CoronerTrackRecorded")); AbilityLimit -= 1; @@ -208,7 +155,6 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf foreach (var apc in _playerIdList.ToArray()) { LocateArrow.RemoveAllTarget(apc); - SendRPC(apc, false); } foreach (var bloodhound in CoronerTargets) @@ -216,7 +162,6 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf foreach (var tar in bloodhound.Value.ToArray()) { TargetArrow.Remove(bloodhound.Key, tar); - SendRPCKiller(bloodhound.Key, tar, add: false); } CoronerTargets[bloodhound.Key].Clear(); @@ -232,7 +177,6 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe var player = GetPlayerById(pc); if (player == null || !player.IsAlive()) continue; LocateArrow.Add(pc, target.transform.position); - SendRPC(pc, true, target.transform.position); } } diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index fac7ce7e57..c38eabedfb 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -46,29 +46,6 @@ public override void Remove(byte playerId) { playerIdList.Remove(playerId); } - private static void SendRPC(byte playerId, bool add, Vector3 loc = new()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetMorticianArrow, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(add); - if (add) - { - writer.Write(loc.x); - writer.Write(loc.y); - writer.Write(loc.z); - } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - bool add = reader.ReadBoolean(); - if (add) - LocateArrow.Add(playerId, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); - else - LocateArrow.RemoveAllTarget(playerId); - } private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { if (inMeeting || target.IsDisconnected()) return; @@ -93,7 +70,6 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe var player = Utils.GetPlayerById(pc); if (player == null || !player.IsAlive()) continue; LocateArrow.Add(pc, target.transform.position); - SendRPC(pc, true, target.transform.position); } } public override void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target) @@ -101,7 +77,6 @@ public override void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo targ foreach (var apc in playerIdList) { LocateArrow.RemoveAllTarget(apc); - SendRPC(apc, false); } if (pc == null || target == null || target.Object == null || !pc.Is(CustomRoles.Mortician) || pc.PlayerId == target.PlayerId) return; diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index a5c98ab7b9..e8343a7801 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -167,12 +167,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) switch (RpcTypeId) { case 0: - foreach (var target in Main.AllAlivePlayerControls) - { - if (!IsSnitchTarget(target)) continue; - - TargetArrow.Add(target.PlayerId, snitchId); - } IsExposed[snitchId] = true; break; case 1: @@ -183,7 +177,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) if (!IsSnitchTarget(target)) continue; var targetId = target.PlayerId; - TargetArrow.Add(snitchId, targetId); if (!TargetList.Contains(targetId)) { diff --git a/Roles/Crewmate/Spiritualist.cs b/Roles/Crewmate/Spiritualist.cs index 8890993c13..3de667a61b 100644 --- a/Roles/Crewmate/Spiritualist.cs +++ b/Roles/Crewmate/Spiritualist.cs @@ -113,24 +113,25 @@ public override void AfterMeetingTasks() public override string GetSuffix(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) { - if (!seer.Is(CustomRoles.Spiritualist) || !seer.IsAlive()) return ""; - if (target != null && seer.PlayerId != target.PlayerId) return ""; - if (GameStates.IsMeeting) return ""; + if (!seer.Is(CustomRoles.Spiritualist) || !seer.IsAlive()) return string.Empty; + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + if (GameStates.IsMeeting) return string.Empty; if (SpiritualistTarget != byte.MaxValue && ShowArrow(seer.PlayerId)) { return Utils.ColorString(seer.GetRoleColor(), TargetArrow.GetArrows(seer, SpiritualistTarget)); } - return ""; + return string.Empty; } public static void RemoveTarget(byte player) { if (SpiritualistTarget != player) return; - foreach (var spiritualist in playerIdList) - { - TargetArrow.Remove(spiritualist, SpiritualistTarget); - } + if (AmongUsClient.Instance.AmHost) + foreach (var spiritualist in playerIdList) + { + TargetArrow.Remove(spiritualist, SpiritualistTarget); + } SpiritualistTarget = byte.MaxValue; } diff --git a/Roles/Crewmate/Tracefinder.cs b/Roles/Crewmate/Tracefinder.cs index 4d8c4e9638..bf554f332d 100644 --- a/Roles/Crewmate/Tracefinder.cs +++ b/Roles/Crewmate/Tracefinder.cs @@ -56,40 +56,17 @@ public override void Remove(byte playerId) { playerIdList.Remove(playerId); } - private static void SendRPC(byte playerId, bool add, Vector3 loc = new()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetTracefinderArrow, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(add); - if (add) - { - writer.Write(loc.x); - writer.Write(loc.y); - writer.Write(loc.z); - } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } public override void ApplyGameOptions(IGameOptions opt, byte playerid) { AURoleOptions.ScientistCooldown = VitalsCooldown.GetFloat(); AURoleOptions.ScientistBatteryCharge = VitalsDuration.GetFloat(); } - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - bool add = reader.ReadBoolean(); - if (add) - LocateArrow.Add(playerId, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); - else - LocateArrow.RemoveAllTarget(playerId); - } public override void OnReportDeadBody(PlayerControl GODZILLA_VS, NetworkedPlayerInfo KINGKONG) { foreach (var apc in playerIdList) { LocateArrow.RemoveAllTarget(apc); - SendRPC(apc, false); } } @@ -114,8 +91,6 @@ public static void CheckDeadBody(PlayerControl killer, PlayerControl target, boo var player = Utils.GetPlayerById(pc); if (player == null || !player.IsAlive()) continue; LocateArrow.Add(pc, tempPositionTarget); - SendRPC(pc, true, tempPositionTarget); - Utils.NotifyRoles(SpecifySeer: player); } } }, delay, "Get Arrow Tracefinder"); diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index 66fcad690e..46bd2a7d5f 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -83,9 +83,7 @@ public static void ReceiveRPC(MessageReader reader) { byte bountyId = reader.ReadByte(); byte targetId = reader.ReadByte(); - Targets[bountyId] = targetId; - if (ShowTargetArrow) TargetArrow.Add(bountyId, targetId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) diff --git a/Roles/Impostor/EvilTracker.cs b/Roles/Impostor/EvilTracker.cs index e3deb5c820..f7d5ed64f5 100644 --- a/Roles/Impostor/EvilTracker.cs +++ b/Roles/Impostor/EvilTracker.cs @@ -81,7 +81,8 @@ public override void Add(byte playerId) if (targetId != playerId && target.Is(Custom_Team.Impostor)) { ImpostorsId[playerId].Add(targetId); - TargetArrow.Add(playerId, targetId); + if (AmongUsClient.Instance.AmHost) + TargetArrow.Add(playerId, targetId); } } } @@ -153,7 +154,8 @@ private static void SetTarget(byte trackerId = byte.MaxValue, byte targetId = by if (CurrentTargetMode != TargetMode.Always) CanSetTarget[trackerId] = false; // Target cannot be re-set - TargetArrow.Add(trackerId, targetId); + if (AmongUsClient.Instance.AmHost) + TargetArrow.Add(trackerId, targetId); } if (!AmongUsClient.Instance.AmHost) return; diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 9c1fb850e6..7810ac3477 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -89,29 +89,6 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) } public override Sprite ReportButtonSprite => CustomButton.Get("Amnesiac"); - private static void SendRPC(byte playerId, bool add, Vector3 loc = new()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAmnesaicArrows, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(add); - if (add) - { - writer.Write(loc.x); - writer.Write(loc.y); - writer.Write(loc.z); - } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - bool add = reader.ReadBoolean(); - if (add) - LocateArrow.Add(playerId, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); - else - LocateArrow.RemoveAllTarget(playerId); - } private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { if (inMeeting || Main.MeetingIsStarted) return; @@ -121,7 +98,6 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe if (!player.IsAlive()) continue; LocateArrow.Add(pc, target.transform.position); - SendRPC(pc, true, target.transform.position); } } @@ -142,7 +118,6 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl foreach (var apc in playerIdList.ToArray()) { LocateArrow.RemoveAllTarget(apc); - SendRPC(apc, false); } if (__instance.Is(CustomRoles.Amnesiac)) { diff --git a/Roles/Neutral/Solsticer.cs b/Roles/Neutral/Solsticer.cs index ee53ffb6cb..ff63d731f4 100644 --- a/Roles/Neutral/Solsticer.cs +++ b/Roles/Neutral/Solsticer.cs @@ -120,12 +120,9 @@ private void ActiveWarning(PlayerControl pc) { TargetArrow.Add(target.PlayerId, pc.PlayerId); } - if (AmongUsClient.Instance.AmHost) - { - warningActived = true; - SendRPC(); - Utils.NotifyRoles(ForceLoop: true); - } + warningActived = true; + SendRPC(); + Utils.NotifyRoles(ForceLoop: true); } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { @@ -210,7 +207,6 @@ private void SendRPC() writer.Write(0); writer.Write(0); } - writer.Write(warningActived); writer.Write(playerid); AmongUsClient.Instance.FinishRpcImmediately(writer); } @@ -219,7 +215,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) Logger.Info("syncsolsticer", "solsticer"); int AllCount = reader.ReadInt32(); int CompletedCount = reader.ReadInt32(); - warningActived = reader.ReadBoolean(); playerid = reader.ReadByte(); if (AllCount != byte.MaxValue && CompletedCount != byte.MaxValue) @@ -228,11 +223,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) taskState.AllTasksCount = AllCount; taskState.CompletedTasksCount = CompletedCount; } - - if (warningActived) - { - ActiveWarning(Utils.GetPlayerById(playerid)); - } } public static bool OtherKnowSolsticer(PlayerControl target) => target.Is(CustomRoles.Solsticer) && EveryOneKnowSolsticer.GetBool(); diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index 3e94b38300..6b9c6e7e5d 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -80,19 +80,6 @@ public override void ApplyGameOptions(IGameOptions opt, byte id) AURoleOptions.EngineerInVentMaxTime = 0f; } - private static void SendRPC(byte playerId, bool add, Vector3 loc = new()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetVultureArrow, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(add); - if (add) - { - writer.Write(loc.x); - writer.Write(loc.y); - writer.Write(loc.z); - } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } private static void SendBodyRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncVultureBodyAmount, SendOption.Reliable, -1); @@ -100,15 +87,6 @@ private static void SendBodyRPC(byte playerId) writer.Write(BodyReportCount[playerId]); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - bool add = reader.ReadBoolean(); - if (add) - LocateArrow.Add(playerId, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); - else - LocateArrow.RemoveAllTarget(playerId); - } public static void ReceiveBodyRPC(MessageReader reader) { byte playerId = reader.ReadByte(); @@ -175,7 +153,6 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf foreach (var apc in playerIdList) { LocateArrow.RemoveAllTarget(apc); - SendRPC(apc, false); } } private static void OnEatDeadBody(PlayerControl pc, NetworkedPlayerInfo target) @@ -188,7 +165,6 @@ private static void OnEatDeadBody(PlayerControl pc, NetworkedPlayerInfo target) foreach (var apc in playerIdList) { LocateArrow.Remove(apc, target.Object.transform.position); - SendRPC(apc, false); } } SendBodyRPC(pc.PlayerId); @@ -207,7 +183,6 @@ public override void AfterMeetingTasks() { AbilityLeftInRound[apc] = MaxEaten.GetInt(); LastReport[apc] = GetTimeStamp(); - SendRPC(apc, false); } SendBodyRPC(player.PlayerId); } @@ -254,7 +229,6 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe var player = GetPlayerById(pc); if (player == null || !player.IsAlive()) continue; LocateArrow.Add(pc, target.transform.position); - SendRPC(pc, true, target.transform.position); } } public override string GetSuffix(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) From 50b8deb87c5abd9440bc8703572c21a0721aed80 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 9 Sep 2024 02:20:52 +0800 Subject: [PATCH 499/778] Clear using & only host --- Modules/ExtendedPlayerControl.cs | 1 - Modules/LocateArrow.cs | 1 + Modules/TargetArrow.cs | 1 + Patches/MeetingHudPatch.cs | 1 - Roles/Crewmate/Mortician.cs | 3 +-- Roles/Crewmate/Tracefinder.cs | 1 - Roles/Neutral/Amnesiac.cs | 1 - 7 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 774c14d57c..c2cb919b3a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -12,7 +12,6 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; namespace TOHE; diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index 5f21324863..6659bbbe8d 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -28,6 +28,7 @@ public static void Init() public static void SendRPC(int index, byte seer, Vector3 vector3) { + if (!AmongUsClient.Instance.AmHost) return; var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable); writer.Write(false); writer.WritePacked(index); diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index 59d635f3bd..d57b30e4b4 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -28,6 +28,7 @@ public static void Init() public static void SendRPC(int index, byte seer, byte target = byte.MaxValue) { + if (!AmongUsClient.Instance.AmHost) return; var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable); writer.Write(true); writer.WritePacked(index); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 42811db296..cb8c2588ad 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using TMPro; using System; using System.Text; using TOHE.Roles.AddOns.Common; diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index c38eabedfb..95b8238b9a 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -1,5 +1,4 @@ -using Hazel; -using TOHE.Roles.Core; +using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.MeetingHudStartPatch; diff --git a/Roles/Crewmate/Tracefinder.cs b/Roles/Crewmate/Tracefinder.cs index bf554f332d..40fe8b566d 100644 --- a/Roles/Crewmate/Tracefinder.cs +++ b/Roles/Crewmate/Tracefinder.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using Hazel; using System; using TOHE.Roles.Core; using UnityEngine; diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 7810ac3477..6d414ad524 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -1,4 +1,3 @@ -using Hazel; using UnityEngine; using static TOHE.Translator; using static TOHE.Options; From 91f06360ccc8001b070c5b4e7f8b134f49b26044 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 9 Sep 2024 02:45:52 +0800 Subject: [PATCH 500/778] Add RpcSetCustomRole in RpcRevive --- Modules/ExtendedPlayerControl.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index c2cb919b3a..dc642eeab0 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -79,9 +79,11 @@ public static void RpcRevive(this PlayerControl player) return; } + var customRole = player.GetRoleMap().CustomRole; Main.PlayerStates[player.PlayerId].IsDead = false; Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; - player.RpcChangeRoleBasis(player.GetRoleMap().CustomRole, true); + player.RpcSetCustomRole(customRole); + player.RpcChangeRoleBasis(customRole, true); player.ResetKillCooldown(); player.SyncSettings(); player.SetKillCooldown(); From 5e1d928d3abdb34ddd269957e99ca95bacbc08e6 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 9 Sep 2024 02:47:37 +0800 Subject: [PATCH 501/778] Check ghost role --- Modules/ExtendedPlayerControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index dc642eeab0..5040894937 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -82,7 +82,10 @@ public static void RpcRevive(this PlayerControl player) var customRole = player.GetRoleMap().CustomRole; Main.PlayerStates[player.PlayerId].IsDead = false; Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; - player.RpcSetCustomRole(customRole); + + if (player.HasGhostRole()) + player.RpcSetCustomRole(customRole); + player.RpcChangeRoleBasis(customRole, true); player.ResetKillCooldown(); player.SyncSettings(); From b0e1ebc5310d518e075179452148ea2e8d885471 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 9 Sep 2024 03:00:30 +0800 Subject: [PATCH 502/778] Fix sync settings not work --- Patches/IntroPatch.cs | 14 ++++++++++---- Patches/onGameStartedPatch.cs | 8 -------- main.cs | 6 +++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 23676c0a88..002b54bf43 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -47,6 +47,12 @@ public static void Prefix() // Assign tasks after assign all roles, as it should be ShipStatus.Instance.Begin(); + GameOptionsSender.AllSenders.Clear(); + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); + } + Utils.SyncAllSettings(); } } @@ -311,7 +317,7 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections exeTeam.Add(PlayerControl.LocalPlayer); foreach (var execution in Executioner.Target.Values) { - PlayerControl executing = Utils.GetPlayerById(execution); + PlayerControl executing = execution.GetPlayer(); exeTeam.Add(executing); } teamToDisplay = exeTeam; @@ -322,7 +328,7 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections lawyerTeam.Add(PlayerControl.LocalPlayer); foreach (var help in Lawyer.Target.Values) { - PlayerControl helping = Utils.GetPlayerById(help); + PlayerControl helping = help.GetPlayer(); lawyerTeam.Add(helping); } teamToDisplay = lawyerTeam; @@ -562,7 +568,7 @@ public static void Prefix() { Main.UnShapeShifter.Do(x => { - var PC = Utils.GetPlayerById(x); + var PC = x.GetPlayer(); var firstPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(firstPlayer, false); PC.RpcRejectShapeshift(); @@ -656,7 +662,7 @@ public static void Postfix() { GhostRoleAssign.forceRole.Do(x => { - var plr = Utils.GetPlayerById(x.Key); + var plr = x.Key.GetPlayer(); plr.RpcExile(); Main.PlayerStates[x.Key].SetDead(); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index c8fe5ef77d..ee71a9dd34 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -577,16 +577,8 @@ public static System.Collections.IEnumerator AssignRoles() break; } - GameOptionsSender.AllSenders.Clear(); - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); - } - EAC.LogAllRoles(); - Utils.CountAlivePlayers(sendLog: true, checkGameEnd: false); - Utils.SyncAllSettings(); Logger.Msg("Ended", "AssignRoles"); } diff --git a/main.cs b/main.cs index dfd4b722fe..3da1e53feb 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0908.210.00101"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 10 Hotfix 1"; + public const string PluginVersion = "2024.0909.210.00110"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 11"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 10 Hotfix 1 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 11 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 02ef8b3ebb93995d043081425f75162c26454698 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:01:09 -0400 Subject: [PATCH 503/778] Update Baker.cs --- Roles/Neutral/Baker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 2709e69bed..e54b80e180 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -79,7 +79,7 @@ private static (int, int) BreadedPlayerCount(byte playerId) public static void SendRPC(PlayerControl player, PlayerControl target) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WritePacked((int)CustomRoles.Baker); + writer.WriteNetObject(_Player); writer.Write(player.PlayerId); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); From 4fd0c59c1f5edb2de71824ca8e8448ac176d3025 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:43:42 +0800 Subject: [PATCH 504/778] Fix EAC dumb moment --- Modules/EAC.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/EAC.cs b/Modules/EAC.cs index f6b96c0b41..7b644eb0e5 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -659,7 +659,7 @@ public static void HandleCheat(PlayerControl pc, string text) break; case 3: foreach (var apc in Main.AllPlayerControls.Where(x => x.PlayerId != pc?.Data?.PlayerId).ToArray()) - Utils.SendMessage(string.Format(GetString("Message.NoticeByEAC"), pc?.Data?.PlayerName, text), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("MessageFromEAC"))); + Utils.SendMessage(string.Format(GetString("Message.NoticeByEAC"), pc?.Data?.PlayerName, text), apc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("MessageFromEAC"))); break; case 4: if (!BanManager.TempBanWhiteList.Contains(pc.GetClient().GetHashedPuid())) From 5b69ed135a896677ec88fd066edc7d10443a3d7f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 15 Sep 2024 20:44:37 +0800 Subject: [PATCH 505/778] Fix bugs --- Modules/AntiBlackout.cs | 2 +- Modules/CustomRolesHelper.cs | 9 +++- Modules/LocateArrow.cs | 7 +-- Modules/OptionSaver.cs | 11 +++- Modules/RPC.cs | 4 -- Modules/TargetArrow.cs | 11 ++-- Patches/GameSettingMenuPatch.cs | 15 +++--- Patches/PlayerControlPatch.cs | 27 ++++++++++ Roles/Core/AssignManager/AddonAssign.cs | 34 +++++++----- Roles/Core/CustomRoleManager.cs | 2 +- Roles/Crewmate/Deceiver.cs | 4 +- Roles/Crewmate/Enigma.cs | 2 +- Roles/Crewmate/Medium.cs | 2 + Roles/Crewmate/Oracle.cs | 6 +-- Roles/Crewmate/Psychic.cs | 69 ++++++++++--------------- Roles/Crewmate/Retributionist.cs | 1 + Roles/Double/Mini.cs | 1 + Roles/Impostor/Chronomancer.cs | 4 +- Roles/Impostor/Crewpostor.cs | 1 + Roles/Impostor/Hangman.cs | 7 ++- Roles/Impostor/Lightning.cs | 2 - Roles/Impostor/Nemesis.cs | 1 + Roles/Neutral/Huntsman.cs | 4 +- Roles/Neutral/Opportunist.cs | 2 +- Roles/Neutral/PlagueDoctor.cs | 1 + Roles/Neutral/Pursuer.cs | 2 +- Roles/Neutral/Quizmaster.cs | 2 +- 27 files changed, 138 insertions(+), 95 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 4ec21299a4..bb9823f467 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -271,7 +271,6 @@ public static void SetRealPlayerRoles() target.RpcSetRoleDesync(changedRoleType, seer.GetClientId()); } - ResetAllCooldown(); } private static void ResetAllCooldown() { @@ -292,6 +291,7 @@ public static void ResetAfterMeeting() { SkipTasks = false; ExilePlayerId = -1; + ResetAllCooldown(); } public static void Reset() { diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 340b62f6e6..ffbc3c1aa3 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -38,9 +38,13 @@ public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor } public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill button (Non-Impostor) - => (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter) && !role.IsImpostor() || role is CustomRoles.Killer // FFA + { + if (role is CustomRoles.Killer) return RoleTypes.Impostor; // FFA + + return (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter) && !role.IsImpostor() ? role.GetStaticRoleClass().ThisRoleBase.GetRoleTypes() : RoleTypes.GuardianAngel; + } /* Needs recode, awaiting phantom role base*/ public static bool HasImpKillButton(this PlayerControl player, bool considerVanillaShift = false) @@ -613,7 +617,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Spy) || pc.Is(CustomRoles.Necromancer) || pc.Is(CustomRoles.Demon) - || pc.Is(CustomRoles.Shaman)) + || pc.Is(CustomRoles.Shaman) + || pc.Is(CustomRoles.Opportunist) && Opportunist.OppoImmuneToAttacksWhenTasksDone.GetBool()) return false; break; diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index 6659bbbe8d..d4ccf319ec 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -26,10 +26,11 @@ public static void Init() LocateArrows.Clear(); } - public static void SendRPC(int index, byte seer, Vector3 vector3) + public static void SendRPC(int index, byte seerId, Vector3 vector3) { - if (!AmongUsClient.Instance.AmHost) return; - var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable); + var seer = Utils.GetPlayerById(seerId); + if (!AmongUsClient.Instance.AmHost || seer == null) return; + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable, seer.GetClientId()); writer.Write(false); writer.WritePacked(index); writer.Write(seer); diff --git a/Modules/OptionSaver.cs b/Modules/OptionSaver.cs index e01edf537e..83b7008ffd 100644 --- a/Modules/OptionSaver.cs +++ b/Modules/OptionSaver.cs @@ -83,8 +83,15 @@ public static void Save() { if (AmongUsClient.Instance != null && !AmongUsClient.Instance.AmHost) return; - var jsonString = JsonSerializer.Serialize(GenerateOptionsData(), new JsonSerializerOptions { WriteIndented = true, }); - File.WriteAllText(OptionSaverFileInfo.FullName, jsonString); + try + { + var jsonString = JsonSerializer.Serialize(GenerateOptionsData(), new JsonSerializerOptions { WriteIndented = true, }); + File.WriteAllText(OptionSaverFileInfo.FullName, jsonString); + } + catch (System.Exception error) + { + Logger.Error($"Error: {error}", "OptionSaver.Save"); + } } /// Read options from json file public static void Load() diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 0e856e54a4..abbd44f770 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -99,7 +99,6 @@ enum CustomRPC : byte // 193/255 USED SetMarkedPlayer, SetConcealerTimer, SetMedicalerProtectList, - SyncPsychicRedList, PresidentEnd, PresidentReveal, SetBKTimer, @@ -564,9 +563,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetMedicalerProtectList: Medic.ReceiveRPCForProtectList(reader); break; - case CustomRPC.SyncPsychicRedList: - Psychic.ReceiveRPC(reader); - break; case CustomRPC.SyncGeneralOptions: byte paciefID = reader.ReadByte(); //playerstate: diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index d57b30e4b4..9bd2dde139 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -26,14 +26,15 @@ public static void Init() TargetArrows.Clear(); } - public static void SendRPC(int index, byte seer, byte target = byte.MaxValue) + public static void SendRPC(int index, byte seerId, byte targetId = byte.MaxValue) { - if (!AmongUsClient.Instance.AmHost) return; - var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable); + var seer = Utils.GetPlayerById(seerId); + if (!AmongUsClient.Instance.AmHost || seer == null) return; + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable, seer.GetClientId()); writer.Write(true); writer.WritePacked(index); - writer.Write(seer); - writer.Write(target); + writer.Write(seerId); + writer.Write(targetId); AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void ReceiveRPC(MessageReader reader) diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index cb7dce2666..18c57c3a48 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -159,7 +159,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) (PLabel.fontSizeMax, PLabel.fontSizeMin) = (size, size); var TempMinus = GameObject.Find("MinusButton").gameObject; - var GMinus = GameObject.Instantiate(__instance.GamePresetsButton.gameObject, preset.transform); + var GMinus = Object.Instantiate(__instance.GamePresetsButton.gameObject, preset.transform); GMinus.gameObject.SetActive(true); GMinus.transform.localScale = new Vector3(0.08f, 0.4f, 1f); @@ -197,7 +197,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) - var PlusFab = GameObject.Instantiate(GMinus, preset.transform); + var PlusFab = Object.Instantiate(GMinus, preset.transform); var PLuLabel = PlusFab.transform.Find("FontPlacer/Text_TMP").GetComponent(); PLuLabel.alignment = TextAlignmentOptions.Center; PLuLabel.DestroyTranslator(); @@ -225,11 +225,12 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) GameSettingsLabel.text = GetString($"{Options.CurrentGameMode}"); var FreeChatField = DestroyableSingleton.Instance.freeChatField; - var TextField = GameObject.Instantiate(FreeChatField, ParentLeftPanel.parent); + var TextField = Object.Instantiate(FreeChatField, ParentLeftPanel.parent); TextField.transform.localScale = new Vector3(0.3f, 0.59f, 1); TextField.transform.localPosition = new Vector3(-2.07f, -2.57f, -5f); TextField.textArea.outputText.transform.localScale = new Vector3(3.5f, 2f, 1f); TextField.textArea.outputText.font = PLuLabel.font; + TextField.name = "InputField"; InputField = TextField; @@ -241,11 +242,11 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) Object.Destroy(button.FindChild("Disabled").FindChild("Icon").GetComponent()); Object.Destroy(button.transform.FindChild("Text").GetComponent()); - button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconActive.png", 100f); - button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconHover.png", 100f); - button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIcon.png", 100f); + button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconActive.png", 100f); + button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIconHover.png", 100f); + button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.SearchIcon.png", 100f); - if (DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.Russian) + if (DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.Russian) { Vector3 FixedScale = new(0.7f, 1f, 1f); button.FindChild("Normal").FindChild("Background").transform.localScale = FixedScale; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 0b7acc9f11..8ca3c2200b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -30,6 +30,12 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC Logger.Info("CheckProtect occurs: " + __instance.GetNameWithRole() + "=>" + target.GetNameWithRole(), "CheckProtect"); var angel = __instance; + if (AntiBlackout.SkipTasks) + { + Logger.Info("Checking while AntiBlackOut protect, guard protect was canceled", "CheckProtect"); + return false; + } + if (target.Data.IsDead) // bad protect return false; @@ -618,6 +624,11 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont logger.Info("Cancel shapeshifting in meeting"); return false; } + if (AntiBlackout.SkipTasks) + { + Logger.Info("Checking while AntiBlackOut protect, shapeshift was canceled", "CheckShapeshift"); + return false; + } if (!(instance.Is(CustomRoles.ShapeshifterTOHE) || instance.Is(CustomRoles.Shapeshifter)) && target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) { instance.RpcGuardAndKill(instance); @@ -929,6 +940,7 @@ class FixedUpdateInNormalGamePatch private static readonly StringBuilder Suffix = new(120); private static readonly Dictionary BufferTime = []; private static int LevelKickBufferTime = 20; + private static bool ChatOpen; public static async void Postfix(PlayerControl __instance) { @@ -952,6 +964,21 @@ public static async void Postfix(PlayerControl __instance) } } + if (GameStates.IsMeeting) + { + switch (ChatOpen) + { + case false when DestroyableSingleton.Instance.Chat.IsOpenOrOpening: + ChatOpen = true; + break; + case true when DestroyableSingleton.Instance.Chat.IsClosedOrClosing: + ChatOpen = false; + if (GameStates.IsVoting) + GuessManager.CreateIDLabels(MeetingHud.Instance); + break; + } + } + try { await DoPostfix(__instance); diff --git a/Roles/Core/AssignManager/AddonAssign.cs b/Roles/Core/AssignManager/AddonAssign.cs index 5d5c8fadfe..f070b3caf2 100644 --- a/Roles/Core/AssignManager/AddonAssign.cs +++ b/Roles/Core/AssignManager/AddonAssign.cs @@ -112,23 +112,31 @@ public static void StartSortAndAssign() } public static void AssignSubRoles(CustomRoles role, int RawCount = -1) { - var allPlayers = Main.AllAlivePlayerControls.Where(x => CustomRolesHelper.CheckAddonConfilct(role, x)).ToList(); - if (!allPlayers.Any()) return; - var count = Math.Clamp(RawCount, 0, allPlayers.Count); - if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); - if (count <= 0) return; - for (var i = 0; i < count; i++) + try { - // if the number of all players is 0 + var checkAllPlayers = Main.AllAlivePlayerControls.Where(x => CustomRolesHelper.CheckAddonConfilct(role, x)); + var allPlayers = checkAllPlayers.ToList(); if (!allPlayers.Any()) return; + var count = Math.Clamp(RawCount, 0, allPlayers.Count); + if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); + if (count <= 0) return; + for (var i = 0; i < count; i++) + { + // if the number of all players is 0 + if (!allPlayers.Any()) return; - // Select player - var player = allPlayers.RandomElement(); - allPlayers.Remove(player); + // Select player + var player = allPlayers.RandomElement(); + allPlayers.Remove(player); - // Set Add-on - Main.PlayerStates[player.PlayerId].SetSubRole(role); - Logger.Info($"Registered Add-on: {player?.Data?.PlayerName} = {player.GetCustomRole()} + {role}", $"Assign {role}"); + // Set Add-on + Main.PlayerStates[player.PlayerId].SetSubRole(role); + Logger.Info($"Registered Add-on: {player?.Data?.PlayerName} = {player.GetCustomRole()} + {role}", $"Assign {role}"); + } + } + catch (Exception error) + { + Logger.Warn($"Add-On {role} get error after check addon confilct for: {error}", "AssignSubRoles"); } } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index ac222f7e22..6c7e1f80b3 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -67,7 +67,7 @@ public static RoleBase CreateRoleClass(this CustomRoles role) return (RoleBase)Activator.CreateInstance(role.GetStaticRoleClass().GetType()); // Converts this.RoleBase back to its type and creates an unique one. } - public static bool HasDesyncRole(this PlayerControl player) => player != null && (player.GetRoleClass().IsDesyncRole || Main.DesyncPlayerList.Contains(player.Data.PlayerId)); + public static bool HasDesyncRole(this PlayerControl player) => player != null && (player.GetRoleClass().IsDesyncRole || Main.DesyncPlayerList.Contains(player.Data.PlayerId) || player.Is(CustomRoles.Killer)); /// /// If the role protect others players diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index 94159a2a9d..49ec259e58 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -49,8 +49,8 @@ private bool IsClient(byte playerId) private bool CanSeel => AbilityLimit > 0; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (killer == null || target == null) return true; - if (target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.SerialKiller)) return true; + if (killer == null || target == null) return false; + if (target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.SerialKiller)) return false; if (!(CanBeClient(target) && CanSeel)) return false; diff --git a/Roles/Crewmate/Enigma.cs b/Roles/Crewmate/Enigma.cs index c6ad64e813..f1225c7289 100644 --- a/Roles/Crewmate/Enigma.cs +++ b/Roles/Crewmate/Enigma.cs @@ -499,7 +499,7 @@ private static string GetStage2Clue(int level) { int rangeStart = level - 15; int rangeEnd = level + 15; - return string.Format(GetString("EnigmaClueLevel3"), rangeStart, rangeEnd >= 100 ? 100 : rangeEnd); + return string.Format(GetString("EnigmaClueLevel3"), rangeStart, rangeEnd >= 100 ? rangeEnd + 53 : rangeEnd); } private static string GetStage3Clue(int level) diff --git a/Roles/Crewmate/Medium.cs b/Roles/Crewmate/Medium.cs index 5b608d99f9..20180d64f7 100644 --- a/Roles/Crewmate/Medium.cs +++ b/Roles/Crewmate/Medium.cs @@ -157,6 +157,8 @@ public override string GetProgressText(byte playerId, bool comms) } public override void OnOthersMeetingHudStart(PlayerControl pc) { + if (!_Player.IsAlive()) return; + //Self if (ContactPlayer.ContainsValue(pc.PlayerId)) AddMsg(string.Format(GetString("MediumNotifySelf"), Main.AllPlayerNames[ContactPlayer.Where(x => x.Value == pc.PlayerId).FirstOrDefault().Key], AbilityLimit), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Medium), GetString("MediumTitle"))); diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index e240e195b7..76990d7608 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -112,17 +112,17 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } else { - if (target.GetCustomRole().IsImpostor() && !target.Is(CustomRoles.Trickster)) text = "Impostor"; + if (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster)) text = "Impostor"; else if (target.GetCustomRole().IsNeutral()) text = "Neutral"; else text = "Crewmate"; } if (FailChance.GetInt() > 0) { - int random_number_1 = HashRandom.Next(1, 100); + int random_number_1 = IRandom.Instance.Next(1, 100); if (random_number_1 <= FailChance.GetInt()) { - int random_number_2 = HashRandom.Next(1, 3); + int random_number_2 = IRandom.Instance.Next(1, 3); if (text == "Crewmate") { if (random_number_2 == 1) text = "Neutral"; diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index 46c1575f46..89dda19580 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -1,5 +1,6 @@ using Hazel; -using System; +using InnerNet; +using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Utils; @@ -9,9 +10,8 @@ internal class Psychic : RoleBase { //===========================SETUP================================\\ private const int Id = 9400; - private static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); - + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Psychic); + public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ @@ -24,7 +24,7 @@ internal class Psychic : RoleBase private static OptionItem NCshowEvil; private static OptionItem NAshowEvil; - private static readonly HashSet RedPlayer = []; + private readonly HashSet RedPlayer = []; public override void SetupCustomOption() { @@ -40,22 +40,26 @@ public override void SetupCustomOption() } public override void Init() { - playerIdList.Clear(); RedPlayer.Clear(); } public override void Add(byte playerId) { - playerIdList.Add(playerId); + _ = new LateTask(() => + { + if (!Fresh.GetBool()) + GetRedName(); + }, 2f, $"Get Red Name for {_state.PlayerId}"); } - private static void SendRPC() + private void SendRPC() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncPsychicRedList, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(_Player); writer.Write(RedPlayer.Count); foreach (var pc in RedPlayer) writer.Write(pc); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static void ReceiveRPC(MessageReader reader) + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { int count = reader.ReadInt32(); RedPlayer.Clear(); @@ -63,7 +67,7 @@ public static void ReceiveRPC(MessageReader reader) for (int i = 0; i < count; i++) RedPlayer.Add(reader.ReadByte()); } - public static bool IsRedForPsy(PlayerControl target, PlayerControl seer) + public bool IsRedForPsy(PlayerControl target, PlayerControl seer) { if (target == null || seer == null) return false; var targetRole = target.GetCustomRole(); @@ -75,12 +79,12 @@ public override void OnReportDeadBody(PlayerControl reported, NetworkedPlayerInf if (Fresh.GetBool() || RedPlayer == null || RedPlayer.Count < 1) GetRedName(); } - public static void GetRedName() + private void GetRedName() { - if (!HasEnabled || !AmongUsClient.Instance.AmHost) return; + if (!_Player.IsAlive() || !AmongUsClient.Instance.AmHost) return; List BadListPc = Main.AllAlivePlayerControls.Where(x => - x.Is(Custom_Team.Impostor) && !x.Is(CustomRoles.Trickster) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || x.Is(CustomRoles.Recruit) || x.Is(CustomRoles.Charmed) || x.Is(CustomRoles.Infected) || !x.Is(CustomRoles.Admired) || x.Is(CustomRoles.Contagious) || + x.Is(Custom_Team.Impostor) && !x.Is(CustomRoles.Trickster) || !x.Is(CustomRoles.Admired) || x.IsAnySubRole(x => x.IsConverted()) || (x.GetCustomRole().IsCrewKiller() && CkshowEvil.GetBool()) || (x.GetCustomRole().IsNE() && NEshowEvil.GetBool()) || (x.GetCustomRole().IsNC() && NCshowEvil.GetBool()) || @@ -88,40 +92,23 @@ public static void GetRedName() (x.GetCustomRole().IsNA() && NAshowEvil.GetBool()) ).ToList(); - List BadList = []; - BadListPc.Do(x => BadList.Add(x.PlayerId)); + var randomBadPlayer = BadListPc.RandomElement(); List AllList = []; - Main.AllAlivePlayerControls.Where(x => !BadList.Contains(x.PlayerId) && !x.Is(CustomRoles.Psychic)).Do(x => AllList.Add(x.PlayerId)); + Main.AllAlivePlayerControls.Where(x => randomBadPlayer.PlayerId != x.PlayerId && x.PlayerId != _Player.PlayerId).Do(x => AllList.Add(x.PlayerId)); - int ENum = 1; - for (int i = 1; i < CanSeeNum.GetInt(); i++) - if (IRandom.Instance.Next(0, 100) < 18) ENum++; - int BNum = CanSeeNum.GetInt() - ENum; - ENum = Math.Min(ENum, BadList.Count); - BNum = Math.Min(BNum, AllList.Count); + int CountRed = CanSeeNum.GetInt() - 1; + RedPlayer.Add(randomBadPlayer.PlayerId); - if (ENum < 1) goto EndOfSelect; - - RedPlayer.Clear(); - for (int i = 0; i < ENum && BadList.Count >= 1; i++) + for (int i = 0; i < CountRed; i++) { - RedPlayer.Add(BadList[IRandom.Instance.Next(0, BadList.Count)]); - BadList.RemoveAll(RedPlayer.Contains); - } + if (!AllList.Any()) break; - AllList.RemoveAll(RedPlayer.Contains); - for (int i = 0; i < BNum && AllList.Count >= 1; i++) - { - RedPlayer.Add(AllList[IRandom.Instance.Next(0, AllList.Count)]); - AllList.RemoveAll(RedPlayer.Contains); + var randomPlayer = AllList.RandomElement(); + RedPlayer.Add(randomPlayer); + AllList.Remove(randomPlayer); } - EndOfSelect: - - Logger.Info($"需要{CanSeeNum.GetInt()}个红名,其中需要{ENum}个邪恶。计算后显示红名{RedPlayer.Count}个", "Psychic"); - RedPlayer.Do(x => Logger.Info($"红名:{x}: {Main.AllPlayerNames[x]}", "Psychic")); - SendRPC(); //RPC同步红名名单 - + SendRPC(); } public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) diff --git a/Roles/Crewmate/Retributionist.cs b/Roles/Crewmate/Retributionist.cs index 63055d77bf..fa1b374686 100644 --- a/Roles/Crewmate/Retributionist.cs +++ b/Roles/Crewmate/Retributionist.cs @@ -156,6 +156,7 @@ public static bool RetributionistMsgCheck(PlayerControl pc, string msg, bool isU else if (pc.RpcCheckAndMurder(target, true) == false) { pc.ShowInfoMessage(isUI, GetString("GuessImmune")); + Logger.Info($"Guess Immune target {target.PlayerId} have role {target.GetCustomRole()}", "Retributionist"); return true; } diff --git a/Roles/Double/Mini.cs b/Roles/Double/Mini.cs index 77e2c9390d..3b4c288c44 100644 --- a/Roles/Double/Mini.cs +++ b/Roles/Double/Mini.cs @@ -5,6 +5,7 @@ using static TOHE.Utils; namespace TOHE.Roles.Double; + internal class Mini : RoleBase { //===========================SETUP================================\\ diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index 7145df7997..db9244204e 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -131,7 +131,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override void OnFixedUpdate(PlayerControl pc) { - if (GameStates.IsMeeting) return; + if (GameStates.IsMeeting || !Main.introDestroyed) return; var oldChargedTime = ChargedTime; if (LastCD != GetCharge()) @@ -191,6 +191,6 @@ public override string GetLowerText(PlayerControl seer, PlayerControl seen = nul return GetCharge(); } - return ""; + return string.Empty; } } \ No newline at end of file diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index ae6b241f98..a31f83f35d 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -94,6 +94,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override bool CanUseKillButton(PlayerControl pc) => false; + public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target) ? Utils.GetRoleColorCode(CustomRoles.Crewpostor) : string.Empty; public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (AlliesKnowCrewpostor.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Crewpostor)) diff --git a/Roles/Impostor/Hangman.cs b/Roles/Impostor/Hangman.cs index 1d78468ea1..aaa9967700 100644 --- a/Roles/Impostor/Hangman.cs +++ b/Roles/Impostor/Hangman.cs @@ -1,8 +1,10 @@ using AmongUs.GameOptions; +using UnityEngine; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; -using UnityEngine; +using TOHE.Roles.Double; using static TOHE.Options; + namespace TOHE.Roles.Impostor; internal class Hangman : RoleBase @@ -33,6 +35,9 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { + if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) + return true; + if (target.IsTransformedNeutralApocalypse()) return true; diff --git a/Roles/Impostor/Lightning.cs b/Roles/Impostor/Lightning.cs index 2c29a60a11..25d1142eb7 100644 --- a/Roles/Impostor/Lightning.cs +++ b/Roles/Impostor/Lightning.cs @@ -172,8 +172,6 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (isForMeeting) return string.Empty; - target ??= seer; return (!seer.IsAlive() && seer != target && IsGhost(target)) || IsGhost(target) ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lightning), "■") : string.Empty; } diff --git a/Roles/Impostor/Nemesis.cs b/Roles/Impostor/Nemesis.cs index 0d7ce0b68c..25a83a9e43 100644 --- a/Roles/Impostor/Nemesis.cs +++ b/Roles/Impostor/Nemesis.cs @@ -146,6 +146,7 @@ public static bool NemesisMsgCheck(PlayerControl pc, string msg, bool isUI = fal else if (pc.RpcCheckAndMurder(target, true) == false) { pc.ShowInfoMessage(isUI, GetString("GuessImmune")); + Logger.Info($"Guess Immune target {target.PlayerId} have role {target.GetCustomRole()}", "Nemesis"); return true; } diff --git a/Roles/Neutral/Huntsman.cs b/Roles/Neutral/Huntsman.cs index eeffede1a2..d3bb707309 100644 --- a/Roles/Neutral/Huntsman.cs +++ b/Roles/Neutral/Huntsman.cs @@ -90,8 +90,8 @@ public override void OnReportDeadBody(PlayerControl Ronaldo, NetworkedPlayerInfo public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { float tempkcd = KCD; - if (Targets.Contains(target.PlayerId)) Math.Clamp(KCD -= SuccessKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); - else Math.Clamp(KCD += FailureKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); + if (Targets.Contains(target.PlayerId)) KCD = Math.Clamp(tempkcd -= SuccessKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); + else KCD = Math.Clamp(tempkcd += FailureKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); if (KCD != tempkcd) { killer.ResetKillCooldown(); diff --git a/Roles/Neutral/Opportunist.cs b/Roles/Neutral/Opportunist.cs index 3353218b75..d608d34c0a 100644 --- a/Roles/Neutral/Opportunist.cs +++ b/Roles/Neutral/Opportunist.cs @@ -14,7 +14,7 @@ internal class Opportunist : RoleBase //==================================================================\\ public override bool HasTasks(NetworkedPlayerInfo player, CustomRoles role, bool ForRecompute) => !ForRecompute; - private static OptionItem OppoImmuneToAttacksWhenTasksDone; + public static OptionItem OppoImmuneToAttacksWhenTasksDone; public override void SetupCustomOption() { diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index e5b4ba9c74..9f6bcaf199 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -291,6 +291,7 @@ private void CheckWin() if (player.Is(CustomRoles.PlagueDoctor)) continue; player.SetDeathReason(PlayerState.DeathReason.Infected); player.RpcMurderPlayer(player); + player.SetRealKiller(_Player); } } } diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index 0066405714..17d8c97105 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -54,7 +54,7 @@ public bool IsClient(byte playerId) public bool CanSeel(byte playerId) => AbilityLimit > 0; public override bool OnCheckMurderAsKiller(PlayerControl pc, PlayerControl target) { - if (pc == null || target == null || !pc.Is(CustomRoles.Pursuer)) return true; + if (pc == null || target == null || !pc.Is(CustomRoles.Pursuer)) return false; if (target.Is(CustomRoles.Pestilence) || target.Is(CustomRoles.SerialKiller)) return false; if (!(CanBeClient(target) && CanSeel(pc.PlayerId))) return false; diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 68e65044a3..533a633e84 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -667,7 +667,7 @@ public override void FixUnsetAnswers() } else { - string thatAnswer = PossibleAnswers[rnd.Next(0, PossibleAnswers.Count)]; + string thatAnswer = PossibleAnswers.RandomElement(); if (thatAnswer == "None") prefix = "Quizmaster."; Answers.Add(prefix + thatAnswer); PossibleAnswers.Remove(thatAnswer); From 42d0b60aa5b573755247ecc8dc1410726656750d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 15 Sep 2024 20:48:09 +0800 Subject: [PATCH 506/778] Change --- GameModes/FFAManager.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index 99282ee4e3..3e73662587 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -218,11 +218,13 @@ public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) bool mark = false; var nowKCD = Main.AllPlayerKillCooldown[killer.PlayerId]; byte EffectType; - if (!GameStates.AirshipIsActive) EffectType = (byte)HashRandom.Next(0, 10); - else EffectType = (byte)HashRandom.Next(4, 10); + var random = IRandom.Instance; + + if (!GameStates.AirshipIsActive) EffectType = (byte)random.Next(0, 10); + else EffectType = (byte)random.Next(4, 10); if (EffectType <= 7) // Buff { - byte EffectID = (byte)HashRandom.Next(0, 3); + byte EffectID = (byte)random.Next(0, 3); if (GameStates.AirshipIsActive) EffectID = 2; switch (EffectID) { @@ -260,7 +262,7 @@ public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) } else if (EffectType == 8) // De-Buff { - byte EffectID = (byte)HashRandom.Next(0, 3); + byte EffectID = (byte)random.Next(0, 3); if (GameStates.AirshipIsActive) EffectID = 1; switch (EffectID) { From 9d00933663faf49637c24d10713bc64357649fad Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 15 Sep 2024 22:15:37 +0800 Subject: [PATCH 507/778] Sabotage Sound for Inhibitor and Saboteur --- Patches/IntroPatch.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 002b54bf43..71ee7de65a 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -409,6 +409,8 @@ public static void Postfix(IntroCutscene __instance) PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); break; + case CustomRoles.Saboteur: + case CustomRoles.Inhibitor: case CustomRoles.Mechanic: case CustomRoles.Provocateur: PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.SabotageSound; From 739bdf21a64556d42c1ac055636f1004ec3818df Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 15 Sep 2024 22:32:10 +0800 Subject: [PATCH 508/778] Add comments --- Patches/VentSystemPatch.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 427f3fdd88..30b863cbd8 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -67,6 +67,8 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst int vents = 0; foreach (var vent in ShipStatus.Instance.AllVents) { + // For blocking specific vents need patch this in RoleBase or CustomRoleManager + // For now we just use CanUseVent for block all vents if (!pc.CanUseVent()) ++vents; } From 83212f182b0be6ed2e6efecf9ae614012fdb1d9d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 15 Sep 2024 22:35:50 +0800 Subject: [PATCH 509/778] Fix bug --- Patches/HudPatch.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index d655b3142b..1d955ce97e 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -119,13 +119,12 @@ public static void Postfix(HudManager __instance) __instance.KillButton.ToggleVisible(false); } - bool CanUseVent = player.CanUseVent(); - __instance.ImpostorVentButton.ToggleVisible(CanUseVent); - player.Data.Role.CanVent = CanUseVent; + __instance.ImpostorVentButton.ToggleVisible(player.CanUseImpostorVentButton()); + player.Data.Role.CanVent = player.CanUseVent(); // Sometimes sabotage button was visible for non-host modded clients - if (!AmongUsClient.Instance.AmHost) - __instance.SabotageButton.ToggleVisible(player.CanUseSabotage()); + if (!AmongUsClient.Instance.AmHost && player.CanUseSabotage()) + __instance.SabotageButton.Hide(); } else { From 854abe7aa7b191519e06950daf79f59cbceb9320 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 15 Sep 2024 22:46:47 +0800 Subject: [PATCH 510/778] Some changes --- Patches/DleksPatch.cs | 2 +- Patches/VentSystemPatch.cs | 8 +++++--- Roles/Neutral/Pursuer.cs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Patches/DleksPatch.cs b/Patches/DleksPatch.cs index 7d780a7e97..e4d5d894ff 100644 --- a/Patches/DleksPatch.cs +++ b/Patches/DleksPatch.cs @@ -87,7 +87,7 @@ public static class VentSetButtonsPatch { public static bool ShowButtons = false; // Fix arrows buttons in vent on Dleks map and "Index was outside the bounds of the array" errors - private static bool Prefix(Vent __instance, [HarmonyArgument(0)] ref bool enabled) + private static bool Prefix(/*Vent __instance, */[HarmonyArgument(0)] ref bool enabled) { // if map is Dleks if (GameStates.DleksIsActive && Main.IntroDestroyed) diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 30b863cbd8..bcc6c02813 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -83,6 +83,8 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst writer.WritePacked(maxVents); foreach (var vent in pc.GetVentsFromClosest()) { + // For blocking specific vents need patch this in RoleBase or CustomRoleManager + // For now we just use CanUseVent for block all vents if (!pc.CanUseVent()) { writer.Write(AllPlayers[blockedVents].PlayerId); @@ -93,10 +95,10 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst break; } writer.WritePacked(__instance.PlayersInsideVents.Count); - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + foreach (var (playerId, ventId) in __instance.PlayersInsideVents) { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); + writer.Write(playerId); + writer.Write(ventId); } writer.EndMessage(); } diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index 0066405714..12bd5c8e0c 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -51,12 +51,12 @@ public bool IsClient(byte playerId) } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(true); public bool CanBeClient(PlayerControl pc) => pc != null && pc.IsAlive() && !GameStates.IsMeeting && !IsClient(pc.PlayerId); - public bool CanSeel(byte playerId) => AbilityLimit > 0; + public bool CanSeel() => AbilityLimit > 0; public override bool OnCheckMurderAsKiller(PlayerControl pc, PlayerControl target) { if (pc == null || target == null || !pc.Is(CustomRoles.Pursuer)) return true; if (target.Is(CustomRoles.Pestilence) || target.Is(CustomRoles.SerialKiller)) return false; - if (!(CanBeClient(target) && CanSeel(pc.PlayerId))) return false; + if (!(CanBeClient(target) && CanSeel())) return false; AbilityLimit--; SendSkillRPC(); From 0d730fdc297726be89a83fc42ef04305d7649ffe Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 01:03:10 +0800 Subject: [PATCH 511/778] Add support blocks specific vents --- Modules/ExtendedPlayerControl.cs | 5 ++++- Patches/HudPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 2 +- Patches/ShipStatusPatch.cs | 11 +++++----- Patches/VentSystemPatch.cs | 35 +++++++++++++++++++++++--------- Patches/onGameStartedPatch.cs | 5 +++-- Roles/Core/CustomRoleManager.cs | 5 +++++ 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 59dbf66bc1..2e73303b94 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -680,7 +680,10 @@ public static bool HasKillButton(this PlayerControl pc) _ => false }; } - public static bool CanUseVent(this PlayerControl player) => player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer; + public static bool CanUseVents(this PlayerControl player) => player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer; + public static bool CantUseVent(this PlayerControl player, int ventId) => CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); + public static bool HasAnyBlockedVent(this PlayerControl player) => CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); + public static bool CanUseImpostorVentButton(this PlayerControl pc) { if (!pc.IsAlive()) return false; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 1d955ce97e..4b9ef450d5 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -120,7 +120,7 @@ public static void Postfix(HudManager __instance) } __instance.ImpostorVentButton.ToggleVisible(player.CanUseImpostorVentButton()); - player.Data.Role.CanVent = player.CanUseVent(); + player.Data.Role.CanVent = player.CanUseVents(); // Sometimes sabotage button was visible for non-host modded clients if (!AmongUsClient.Instance.AmHost && player.CanUseSabotage()) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 3991013d19..8db5e97ed3 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1546,7 +1546,7 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) var playerRoleClass = __instance.myPlayer.GetRoleClass(); // Prevent vanilla players from enter vents if their current role does not allow it - if (!__instance.myPlayer.CanUseVent() || (playerRoleClass != null && playerRoleClass.CheckBootFromVent(__instance, id)) + if (!__instance.myPlayer.CanUseVents() || (playerRoleClass != null && playerRoleClass.CheckBootFromVent(__instance, id)) ) { try diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 866b3458b0..df439d38e6 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -272,27 +272,26 @@ public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] MessageWri // Original methods short num = 0; - while ((int)num < SystemTypeHelpers.AllTypes.Length) + while (num < SystemTypeHelpers.AllTypes.Length) { - SystemTypes systemTypes = SystemTypeHelpers.AllTypes[(int)num]; + SystemTypes systemTypes = SystemTypeHelpers.AllTypes[num]; if (systemTypes is SystemTypes.Ventilation) { // Skip Ventilation here // Further new systems should skip original methods here and add new patches below. - num += 1; + num++; continue; } - ISystemType systemType; - if (__instance.Systems.TryGetValue(systemTypes, out systemType) && systemType.IsDirty) // initialState used here in vanilla code. Removed it. + if (__instance.Systems.TryGetValue(systemTypes, out ISystemType systemType) && systemType.IsDirty) // initialState used here in vanilla code. Removed it. { __result = true; writer.StartMessage((byte)systemTypes); systemType.Serialize(writer, initialState); writer.EndMessage(); } - num += 1; + num++; } // Ventilation part diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index bcc6c02813..bbbaefe54b 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -14,7 +14,7 @@ public static void Postfix(VentilationSystem __instance) { if (!AmongUsClient.Instance.AmHost) return; if (!Main.IntroDestroyed) return; - foreach (var pc in PlayerControl.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (pc.BlockVentInteraction()) { @@ -28,7 +28,7 @@ public static void Postfix(VentilationSystem __instance) /// public static bool BlockVentInteraction(this PlayerControl pc) { - if (!pc.AmOwner && !pc.IsModClient() && !pc.Data.IsDead && !pc.CanUseVent()) + if (!pc.AmOwner && pc.IsAlive() && (!pc.CanUseVents() || pc.HasAnyBlockedVent())) { return true; } @@ -37,9 +37,10 @@ public static bool BlockVentInteraction(this PlayerControl pc) public static void SerializeV2(VentilationSystem __instance, PlayerControl player = null) { - foreach (var pc in PlayerControl.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (pc.AmOwner || (player != null && pc != player)) continue; + if (pc.BlockVentInteraction()) { pc.RpcCloseVent(__instance); @@ -67,13 +68,11 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst int vents = 0; foreach (var vent in ShipStatus.Instance.AllVents) { - // For blocking specific vents need patch this in RoleBase or CustomRoleManager - // For now we just use CanUseVent for block all vents - if (!pc.CanUseVent()) + if (pc.CantUseVent(vent.Id)) ++vents; } List AllPlayers = []; - foreach (var playerInfo in GameData.Instance.AllPlayers) + foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) { if (playerInfo != null && !playerInfo.Disconnected) AllPlayers.Add(playerInfo); @@ -83,9 +82,7 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst writer.WritePacked(maxVents); foreach (var vent in pc.GetVentsFromClosest()) { - // For blocking specific vents need patch this in RoleBase or CustomRoleManager - // For now we just use CanUseVent for block all vents - if (!pc.CanUseVent()) + if (pc.CantUseVent(vent.Id)) { writer.Write(AllPlayers[blockedVents].PlayerId); writer.Write((byte)vent.Id); @@ -132,3 +129,21 @@ private static void RpcSerializeVent(this PlayerControl pc, VentilationSystem __ writer.Recycle(); } } +[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.IsVentCurrentlyBeingCleaned))] +static class VentSystemIsVentCurrentlyBeingCleanedPatch +{ + // Patch block use vent for host becouse host always skips RpcSerializeVent + public static bool Prefix([HarmonyArgument(0)] int id, ref bool __result) + { + if (!AmongUsClient.Instance.AmHost) return true; + + if (PlayerControl.LocalPlayer.CantUseVent(id)) + { + __result = true; + return false; + } + + // Run original code if host not have bloked vent + return true; + } +} \ No newline at end of file diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index a67abb5282..4c43b2b697 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -123,8 +123,8 @@ public static void Postfix(AmongUsClient __instance) RPC.SyncAllPlayerNames(); GhostRoleAssign.Init(); - Camouflage.Init(); + CustomRoleManager.Initialize(); var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId); if (invalidColor.Any()) @@ -181,7 +181,9 @@ public static void Postfix(AmongUsClient __instance) ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = 0; + CustomRoleManager.BlockedVentsList[pc.PlayerId] = []; pc.cosmetics.nameText.text = pc.name; @@ -235,7 +237,6 @@ public static void Postfix(AmongUsClient __instance) FFAManager.Init(); FallFromLadder.Reset(); - CustomRoleManager.Initialize(); CustomWinnerHolder.Reset(); AntiBlackout.Reset(); NameNotifyManager.Reset(); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 1ec0670f3c..76c7241f0f 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -479,12 +479,15 @@ public static string GetSuffixOthers(PlayerControl seer, PlayerControl seen, boo return sb.ToString(); } + public static readonly Dictionary> BlockedVentsList = []; + public static void Initialize() { OtherCollectionsSet = false; OnFixedUpdateOthers.Clear(); OnFixedUpdateLowLoadOthers.Clear(); CheckDeadBodyOthers.Clear(); + BlockedVentsList.Clear(); } public static void Add() @@ -493,6 +496,8 @@ public static void Add() LowerOthers = AllEnabledRoles.Select(lower => (Func)lower.GetLowerTextOthers).FilterDuplicates(); SuffixOthers = AllEnabledRoles.Select(suffix => (Func)suffix.GetSuffixOthers).FilterDuplicates(); OtherCollectionsSet = true; + + BlockedVentsList[PlayerControl.LocalPlayer.PlayerId].Add(1); } // ADDONS //////////////////////////// From 77c1ca8031965974ceaa0fd2ccbe7393f893e2ab Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 01:05:09 +0800 Subject: [PATCH 512/778] Remove --- Roles/Core/CustomRoleManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 76c7241f0f..c4ecdd7938 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -496,8 +496,6 @@ public static void Add() LowerOthers = AllEnabledRoles.Select(lower => (Func)lower.GetLowerTextOthers).FilterDuplicates(); SuffixOthers = AllEnabledRoles.Select(suffix => (Func)suffix.GetSuffixOthers).FilterDuplicates(); OtherCollectionsSet = true; - - BlockedVentsList[PlayerControl.LocalPlayer.PlayerId].Add(1); } // ADDONS //////////////////////////// From 8cdf6b7d50673fbb38b2219591ab8585aae69092 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 01:45:50 +0800 Subject: [PATCH 513/778] Some fix --- Modules/ExtendedPlayerControl.cs | 2 +- Patches/IntroPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 2e73303b94..9f11ca60a4 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -681,7 +681,7 @@ public static bool HasKillButton(this PlayerControl pc) }; } public static bool CanUseVents(this PlayerControl player) => player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer; - public static bool CantUseVent(this PlayerControl player, int ventId) => CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); + public static bool CantUseVent(this PlayerControl player, int ventId) => !player.CanUseVents() || CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); public static bool HasAnyBlockedVent(this PlayerControl player) => CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); public static bool CanUseImpostorVentButton(this PlayerControl pc) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d7d50bde48..a19f839c67 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -631,7 +631,7 @@ public static void Postfix() } bool shouldPerformVentInteractions = false; - foreach (var pc in PlayerControl.AllPlayerControls) + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { if (pc.BlockVentInteraction()) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 8db5e97ed3..b22349a8ce 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1218,7 +1218,7 @@ public static Task DoPostfix(PlayerControl __instance) if (Rainbow.isEnabled) Rainbow.OnFixedUpdate(); - if (!lowLoad && Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + if (Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) && !player.IsMushroomMixupActive() && Main.GameIsLoaded) { foreach (var UnShapeshifterId in Main.UnShapeShifter) @@ -1235,6 +1235,7 @@ public static Task DoPostfix(PlayerControl __instance) UnShapeshifter.RpcShapeshift(randomPlayer, false); UnShapeshifter.RpcRejectShapeshift(); UnShapeshifter.ResetPlayerOutfit(); + Utils.NotifyRoles(SpecifyTarget: UnShapeshifter); Logger.Info($"Revert to shapeshifting state for: {player.GetRealName()}", "UnShapeShifer_FixedUpdate"); } } From 8cc983fd4c48f75c54fe1d1cfdca3652a03f5bfa Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:45:57 -0400 Subject: [PATCH 514/778] Solsticer can't get Rebirth --- Modules/CustomRolesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 81e3235570..6c602e7615 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -740,7 +740,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Rebirth: if (pc.Is(CustomRoles.Doppelganger) || pc.Is(CustomRoles.Jester) - || pc.Is(CustomRoles.Zombie)) return false; + || pc.Is(CustomRoles.Zombie) + || pc.Is(CustomRoles.Solsticer)) return false; break; case CustomRoles.Youtuber: From c5cf737c3dee50e02d4e445efd8c5a15b0a3b06d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 13:01:31 +0800 Subject: [PATCH 515/778] RpcSetVentInteraction for Arsonist and Revolutionist --- Modules/ExtendedPlayerControl.cs | 6 +++--- Patches/PlayerControlPatch.cs | 2 ++ Roles/Neutral/Arsonist.cs | 16 +++++++++++++--- Roles/Neutral/Revolutionist.cs | 9 +++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 9f11ca60a4..1682972c7a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -680,9 +680,9 @@ public static bool HasKillButton(this PlayerControl pc) _ => false }; } - public static bool CanUseVents(this PlayerControl player) => player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer; - public static bool CantUseVent(this PlayerControl player, int ventId) => !player.CanUseVents() || CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); - public static bool HasAnyBlockedVent(this PlayerControl player) => CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); + public static bool CanUseVents(this PlayerControl player) => player != null && (player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer); + public static bool CantUseVent(this PlayerControl player, int ventId) => player == null || !player.CanUseVents() || CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); + public static bool HasAnyBlockedVent(this PlayerControl player) => player != null && CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); public static bool CanUseImpostorVentButton(this PlayerControl pc) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b22349a8ce..8d099cf6ad 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1608,6 +1608,8 @@ public static void Postfix(PlayerPhysics __instance, [HarmonyArgument(0)] int id if (!AmongUsClient.Instance.AmHost) return; player.GetRoleClass()?.OnExitVent(player, id); + + _ = new LateTask(() => { player?.RpcSetVentInteraction(); }, 0.8f, $"Set vent interaction after exit vent {player?.PlayerId}", shoudLog: false); } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index 63fd0deef2..c183b5d59b 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -2,6 +2,7 @@ using UnityEngine; using Hazel; using TOHE.Modules; +using TOHE.Roles.Core; using TOHE.Roles.AddOns.Common; using static TOHE.Options; using static TOHE.Utils; @@ -57,6 +58,8 @@ public override void Add(byte playerId) foreach (var ar in Main.AllPlayerControls) IsDoused.Add((playerId, ar.PlayerId), false); + + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } private static void SendCurrentDousingTargetRPC(byte arsonistId, byte targetId) @@ -67,7 +70,7 @@ private static void SendCurrentDousingTargetRPC(byte arsonistId, byte targetId) } else { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentDousingTarget, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentDousingTarget, SendOption.Reliable); writer.Write(arsonistId); writer.Write(targetId); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -84,7 +87,7 @@ public static void ReceiveCurrentDousingTargetRPC(MessageReader reader) private static void SendSetDousedPlayerRPC(PlayerControl player, PlayerControl target, bool isDoused) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDousedPlayer, SendOption.Reliable, -1);//RPCによる同期 + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDousedPlayer, SendOption.Reliable); writer.Write(player.PlayerId); writer.Write(target.PlayerId); writer.Write(isDoused); @@ -117,10 +120,17 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t ArsonistTimer.Add(killer.PlayerId, (target, 0f)); NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); SendCurrentDousingTargetRPC(killer.PlayerId, target.PlayerId); + killer.RpcSetVentInteraction(); } return false; } - + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) + { + if (!_Player.IsAlive() || target.PlayerId == _Player.PlayerId || inMeeting || Main.MeetingIsStarted) return; + + _Player.RpcSetVentInteraction(); + _ = new LateTask(() => { NotifyRoles(SpecifySeer: _Player, ForceLoop: false); }, 1f, $"Update name for Arsonist {_Player?.PlayerId}", shoudLog: false); + } public override void OnFixedUpdate(PlayerControl player) { if (ArsonistTimer.TryGetValue(player.PlayerId, out var arsonistTimerData)) diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs index f34c278997..66198793e9 100644 --- a/Roles/Neutral/Revolutionist.cs +++ b/Roles/Neutral/Revolutionist.cs @@ -71,6 +71,7 @@ public override void Add(byte playerId) PlayerIds.Add(playerId); CustomRoleManager.OnFixedUpdateOthers.Add(OnFixUpdateOthers); + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); foreach (var ar in Main.AllPlayerControls) IsDraw.Add((playerId, ar.PlayerId), false); @@ -195,9 +196,17 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t RevolutionistTimer.TryAdd(killer.PlayerId, (target, 0f)); NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); SetCurrentDrawTargetRPC(killer.PlayerId, target.PlayerId); + killer.RpcSetVentInteraction(); } return false; } + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) + { + if (!_Player.IsAlive() || target.PlayerId == _Player.PlayerId || inMeeting || Main.MeetingIsStarted) return; + + _Player.RpcSetVentInteraction(); + _ = new LateTask(() => { NotifyRoles(SpecifySeer: _Player, ForceLoop: false); }, 1f, $"Update name for Revolutionist {_Player?.PlayerId}", shoudLog: false); + } private static void OnFixUpdateOthers(PlayerControl player) // jesus christ { if (RevolutionistTimer.TryGetValue(player.PlayerId, out var revolutionistTimerData)) From 0f4acb7627298c57476183f94ced2e20344ed02d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 13:28:42 +0800 Subject: [PATCH 516/778] Block movement on vents for Jester --- Resources/Lang/en_US.json | 1 + Roles/Core/RoleBase.cs | 1 + Roles/Neutral/Jester.cs | 62 +++++++++++++++++++++++++++++++-------- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d4f680c140..e67cbb440a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1448,6 +1448,7 @@ "CanKill": "Can Kill", "KillCooldown": "Kill Cooldown", "CanVent": "Can Vent", + "CantMoveOnVents": "Can't Move On Vents", "ImpostorVision": "Has Impostor Vision", "CanUseSabotage": "Can Sabotage", "CanKillImpostors": "Can Kill Impostors", diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 6164e808e6..5fac09e0f0 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -471,6 +471,7 @@ public enum GeneralOption CanKill, KillCooldown, CanVent, + CantMoveOnVents, ImpostorVision, CanUseSabotage, diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index a26858d438..733623c981 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using TOHE.Roles.Core; using static TOHE.Options; namespace TOHE.Roles.Neutral; @@ -10,29 +11,34 @@ internal class Jester : RoleBase private static readonly HashSet PlayerIds = []; public static bool HasEnabled => PlayerIds.Any(); - public override CustomRoles ThisRoleBase => JesterCanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate; + public override CustomRoles ThisRoleBase => CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ - private static OptionItem JesterCanUseButton; - private static OptionItem JesterHasImpostorVision; - private static OptionItem JesterCanVent; - private static OptionItem MeetingsNeededForJesterWin; + private static OptionItem CanUseMeetingButton; + private static OptionItem HasImpostorVision; + private static OptionItem CanVent; + private static OptionItem CantMoveInVents; + private static OptionItem MeetingsNeededForWin; private static OptionItem HideJesterVote; public static OptionItem SunnyboyChance; + private readonly HashSet RememberBlockedVents = []; + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jester); - JesterCanUseButton = BooleanOptionItem.Create(Id + 2, GeneralOption.CanUseMeetingButton, false, TabGroup.NeutralRoles, false) + CanUseMeetingButton = BooleanOptionItem.Create(Id + 2, GeneralOption.CanUseMeetingButton, false, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - JesterCanVent = BooleanOptionItem.Create(Id + 3, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false) + CanVent = BooleanOptionItem.Create(Id + 3, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - JesterHasImpostorVision = BooleanOptionItem.Create(Id + 4, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false) + CantMoveInVents = BooleanOptionItem.Create(Id + 10, GeneralOption.CantMoveOnVents, true, TabGroup.NeutralRoles, false) + .SetParent(CanVent); + HasImpostorVision = BooleanOptionItem.Create(Id + 4, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); HideJesterVote = BooleanOptionItem.Create(Id + 5, GeneralOption.HideVote, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - MeetingsNeededForJesterWin = IntegerOptionItem.Create(Id + 6, "MeetingsNeededForWin", new(0, 10, 1), 0, TabGroup.NeutralRoles, false) + MeetingsNeededForWin = IntegerOptionItem.Create(Id + 6, "MeetingsNeededForWin", new(0, 10, 1), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Times); SunnyboyChance = IntegerOptionItem.Create(Id + 7, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles, false) @@ -42,6 +48,7 @@ public override void SetupCustomOption() public override void Init() { PlayerIds.Clear(); + RememberBlockedVents.Clear(); } public override void Add(byte playerId) { @@ -52,14 +59,43 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.EngineerCooldown = 1f; AURoleOptions.EngineerInVentMaxTime = 0f; - opt.SetVision(JesterHasImpostorVision.GetBool()); + opt.SetVision(HasImpostorVision.GetBool()); } public override bool HideVote(PlayerVoteArea votedPlayer) => HideJesterVote.GetBool(); - public override bool OnCheckStartMeeting(PlayerControl reporter) => JesterCanUseButton.GetBool(); + public override bool OnCheckStartMeeting(PlayerControl reporter) => CanUseMeetingButton.GetBool(); + + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + ResetBlockedVent(); + } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + foreach (var vent in ShipStatus.Instance.AllVents) + { + if (vent.Id == ventId) continue; + + RememberBlockedVents.Add(vent.Id); + CustomRoleManager.BlockedVentsList[physics.myPlayer.PlayerId].Add(vent.Id); + } + } + public override void OnExitVent(PlayerControl pc, int ventId) + { + ResetBlockedVent(); + } + private void ResetBlockedVent() + { + if (_Player == null) return; + + foreach (var ventId in RememberBlockedVents) + { + CustomRoleManager.BlockedVentsList[_Player.PlayerId].Remove(ventId); + } + RememberBlockedVents.Clear(); + } public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) { - if (MeetingsNeededForJesterWin.GetInt() <= Main.MeetingsPassed) + if (MeetingsNeededForWin.GetInt() <= Main.MeetingsPassed) { if (isMeetingHud) { @@ -87,6 +123,6 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn } } else if (CEMode.GetInt() == 2 && isMeetingHud) - name += string.Format(Translator.GetString("JesterMeetingLoose"), MeetingsNeededForJesterWin.GetInt() + 1); + name += string.Format(Translator.GetString("JesterMeetingLoose"), MeetingsNeededForWin.GetInt() + 1); } } From fcf09f4b8440816bf7c672303554b479ab93e989 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 19:22:54 +0800 Subject: [PATCH 517/778] Forgot add --- Roles/Neutral/Jester.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 733623c981..cf1c0b57e7 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -70,6 +70,8 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf } public override void OnCoEnterVent(PlayerPhysics physics, int ventId) { + if (!CantMoveInVents.GetBool()) return; + foreach (var vent in ShipStatus.Instance.AllVents) { if (vent.Id == ventId) continue; @@ -84,7 +86,7 @@ public override void OnExitVent(PlayerControl pc, int ventId) } private void ResetBlockedVent() { - if (_Player == null) return; + if (!CantMoveInVents.GetBool() || _Player == null) return; foreach (var ventId in RememberBlockedVents) { From 19f47337cbc0c59daa85e57f8ba5da95cb1c6d89 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 19:31:44 +0800 Subject: [PATCH 518/778] Move --- Roles/Neutral/Jester.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index cf1c0b57e7..ec7d5d1a54 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -64,10 +64,6 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override bool HideVote(PlayerVoteArea votedPlayer) => HideJesterVote.GetBool(); public override bool OnCheckStartMeeting(PlayerControl reporter) => CanUseMeetingButton.GetBool(); - public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) - { - ResetBlockedVent(); - } public override void OnCoEnterVent(PlayerPhysics physics, int ventId) { if (!CantMoveInVents.GetBool()) return; @@ -84,6 +80,10 @@ public override void OnExitVent(PlayerControl pc, int ventId) { ResetBlockedVent(); } + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + ResetBlockedVent(); + } private void ResetBlockedVent() { if (!CantMoveInVents.GetBool() || _Player == null) return; From a44f206c58a43bf2a276407b84e8e8357e51b6d8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 19:58:19 +0800 Subject: [PATCH 519/778] Some changes --- Patches/IntroPatch.cs | 38 +++++++++++++--------------------- Patches/ShipStatusPatch.cs | 8 +++---- Roles/Impostor/Chronomancer.cs | 2 +- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 3f21c29132..9b3f7fddf3 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -551,9 +551,11 @@ public static void Prefix() { if (AmongUsClient.Instance.AmHost && !AmongUsClient.Instance.IsGameOver) { - // Host is Desync Shapeshifter + // Host is desync role if (PlayerControl.LocalPlayer.HasDesyncRole()) { + PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) { // Set all players as killable players @@ -621,12 +623,6 @@ public static void Postfix() CustomRoleManager.Add(); - var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); - if (amDesyncImpostor) - { - PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; - } - if (AmongUsClient.Instance.AmHost) { if (GameStates.IsNormalGame && !GameStates.AirshipIsActive) @@ -686,29 +682,23 @@ public static void Postfix() { Logger.Error($"Error: {error}", "FFA chat visible"); } - } - var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); - if (amDesyncImpostor) - { - PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; - } + bool shouldPerformVentInteractions = false; + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + if (pc.BlockVentInteraction()) + { + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + shouldPerformVentInteractions = true; + } + } - bool shouldPerformVentInteractions = false; - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - if (pc.BlockVentInteraction()) + if (shouldPerformVentInteractions) { - VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - shouldPerformVentInteractions = true; + Utils.SetAllVentInteractions(); } } - if (shouldPerformVentInteractions) - { - Utils.SetAllVentInteractions(); - } - } Logger.Info("OnDestroy", "IntroCutscene"); } } diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index ea3945c0fa..bba910b213 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -1,11 +1,11 @@ using Hazel; using System; using UnityEngine; -using TOHE.Roles.AddOns.Common; -using TOHE.Roles.Neutral; +using TOHE.Patches; using TOHE.Roles.Core; +using TOHE.Roles.Neutral; +using TOHE.Roles.AddOns.Common; using static TOHE.Translator; -using TOHE.Patches; namespace TOHE; @@ -247,7 +247,7 @@ public static void Postfix() { Logger.CurrentMethod(); - if (RolesIsAssigned && !Main.introDestroyed && GameStates.IsNormalGame) + if (RolesIsAssigned && !Main.IntroDestroyed && GameStates.IsNormalGame) { foreach (var player in Main.AllPlayerControls) { diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index db9244204e..2c1429cbc6 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -131,7 +131,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override void OnFixedUpdate(PlayerControl pc) { - if (GameStates.IsMeeting || !Main.introDestroyed) return; + if (GameStates.IsMeeting || !Main.IntroDestroyed) return; var oldChargedTime = ChargedTime; if (LastCD != GetCharge()) From e927e4fb50c5cbdce0aa0001a9e1fe1de684058f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 16 Sep 2024 20:02:31 +0800 Subject: [PATCH 520/778] Remove csv --- FodyWeavers.xml | 8 ++------ TOHE.csproj | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/FodyWeavers.xml b/FodyWeavers.xml index 7d3b013344..5029e70602 100644 --- a/FodyWeavers.xml +++ b/FodyWeavers.xml @@ -1,7 +1,3 @@  - - - Csv - - - + + \ No newline at end of file diff --git a/TOHE.csproj b/TOHE.csproj index ca5573ec1e..6d48b8224c 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -32,7 +32,6 @@ runtime; compile; build; native; contentfiles; analyzers; buildtransitive all - all runtime; build; native; contentfiles; analyzers; buildtransitive From f183d6995c59154358abc5e4cc65c518cb22a7a0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 00:24:36 +0800 Subject: [PATCH 521/778] Fix errors in disabled vents --- Modules/ExtendedPlayerControl.cs | 7 ++++++- Patches/ExilePatch.cs | 15 +++++++++++++++ Patches/VentSystemPatch.cs | 28 +++++++++++++++++++++++++--- Resources/Lang/en_US.json | 2 +- Roles/Core/RoleBase.cs | 14 +++++++------- Roles/Neutral/Jester.cs | 7 +++++-- 6 files changed, 59 insertions(+), 14 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index fcb1a94a05..64e5b38aec 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -558,6 +558,11 @@ public static void RpcSpecificRejectShapeshift(this PlayerControl player, Player } } } + public static Vent GetClosestVent(this PlayerControl player) + { + var pos = player.GetCustomPosition(); + return ShipStatus.Instance.AllVents.Where(x => x != null).MinBy(x => Vector2.Distance(pos, x.transform.position)); + } public static List GetVentsFromClosest(this PlayerControl player) { Vector2 playerpos = player.transform.position; @@ -1001,7 +1006,7 @@ public static bool HasKillButton(this PlayerControl pc) }; } public static bool CanUseVents(this PlayerControl player) => player != null && (player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer); - public static bool CantUseVent(this PlayerControl player, int ventId) => player == null || !player.CanUseVents() || CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); + public static bool CantUseVent(this PlayerControl player, int ventId) => player == null || !player.CanUseVents() || (CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId)); public static bool HasAnyBlockedVent(this PlayerControl player) => player != null && CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); public static bool CanUseImpostorVentButton(this PlayerControl pc) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 5c381a87ab..6a68d45224 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -1,5 +1,6 @@ using AmongUs.Data; using System; +using TOHE.Patches; using TOHE.Roles.Core; using TOHE.Roles.Neutral; @@ -107,6 +108,20 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) Utils.SyncAllSettings(); Utils.NotifyRoles(NoCache: true); + bool shouldPerformVentInteractions = false; + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + if (pc.BlockVentInteraction()) + { + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + shouldPerformVentInteractions = true; + } + } + if (shouldPerformVentInteractions) + { + Utils.SetAllVentInteractions(); + } + if (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA) { _ = new LateTask(() => diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index bbbaefe54b..b38aacdc03 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -6,6 +6,28 @@ namespace TOHE.Patches; // Patches here is also activated from ShipStatus.Serialize and IntroCutScene // through Utils.SetAllVentInteractions +[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.PerformVentOp))] +static class PerformVentOpPatch +{ + public static bool Prefix(VentilationSystem __instance, [HarmonyArgument(0)] byte playerId, [HarmonyArgument(1)] VentilationSystem.Operation op/*, [HarmonyArgument(2)] byte ventId*/, [HarmonyArgument(3)] SequenceBuffer seqBuffer) + { + if (!AmongUsClient.Instance.AmHost) return true; + if (Utils.GetPlayerById(playerId) == null) return true; + switch (op) + { + case VentilationSystem.Operation.Move: + if (!__instance.PlayersInsideVents.ContainsKey(playerId)) + { + seqBuffer.BumpSid(); + return false; + } + + break; + } + + return true; + } +} [HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.Deteriorate))] static class VentSystemDeterioratePatch { @@ -92,10 +114,10 @@ private static void RpcCloseVent(this PlayerControl pc, VentilationSystem __inst break; } writer.WritePacked(__instance.PlayersInsideVents.Count); - foreach (var (playerId, ventId) in __instance.PlayersInsideVents) + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) { - writer.Write(playerId); - writer.Write(ventId); + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); } writer.EndMessage(); } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6bd4868b50..17b32a0ab7 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1455,7 +1455,7 @@ "CanKill": "Can Kill", "KillCooldown": "Kill Cooldown", "CanVent": "Can Vent", - "CantMoveOnVents": "Can't Move On Vents", + "CantMoveOnVents": "Can't Move On Vents (May work unstable in some maps)", "ImpostorVision": "Has Impostor Vision", "CanUseSabotage": "Can Sabotage", "CanHaveAccessToVitals": "Can Have Access To Vitals", diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index d371d94738..9a48d3601e 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -162,25 +162,25 @@ public virtual void OnOthersTaskComplete(PlayerControl pc, PlayerTask task) /// public virtual bool OnCheckProtect(PlayerControl angel, PlayerControl target) => angel != null && target != null; - /// - /// A method for activating actions where the role starts playing an animation when entering a vent - /// - public virtual void OnEnterVent(PlayerControl pc, Vent vent) - { } /// /// When role need force boot from vent /// public virtual bool CheckBootFromVent(PlayerPhysics physics, int ventId) => physics == null; /// - /// A method for activating actions when role is already in vent + /// A method for activating actions where the others roles starts playing an animation when entering a vent /// public virtual bool OnCoEnterVentOthers(PlayerPhysics physics, int ventId) => physics == null; /// - /// A method for activating actions when role is already in vent + /// A method for activating actions where the role starts playing an animation when entering a vent /// public virtual void OnCoEnterVent(PlayerPhysics physics, int ventId) { } /// + /// A method for activating actions when role is already in vent + /// + public virtual void OnEnterVent(PlayerControl pc, Vent vent) + { } + /// /// A generic method to activate actions once (CustomRole)player exists vent. /// public virtual void OnExitVent(PlayerControl pc, int ventId) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index ec7d5d1a54..abea70f8a6 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -68,13 +68,16 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) { if (!CantMoveInVents.GetBool()) return; + var player = physics.myPlayer; foreach (var vent in ShipStatus.Instance.AllVents) { - if (vent.Id == ventId) continue; + // Skip current vent or ventid 5 in Dleks to prevent stuck + if (vent.Id == ventId || (GameStates.DleksIsActive && ventId is 5 && vent.Id is 6)) continue; RememberBlockedVents.Add(vent.Id); - CustomRoleManager.BlockedVentsList[physics.myPlayer.PlayerId].Add(vent.Id); + CustomRoleManager.BlockedVentsList[player.PlayerId].Add(vent.Id); } + _ = new LateTask(player.RpcSetVentInteraction, 1f, "Check"); } public override void OnExitVent(PlayerControl pc, int ventId) { From eed64b83a965aa98abb900a802cd26b7e3941089 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 00:25:38 +0800 Subject: [PATCH 522/778] Change --- Roles/Neutral/Jester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index abea70f8a6..127b41d74b 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -77,7 +77,7 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) RememberBlockedVents.Add(vent.Id); CustomRoleManager.BlockedVentsList[player.PlayerId].Add(vent.Id); } - _ = new LateTask(player.RpcSetVentInteraction, 1f, "Check"); + player.RpcSetVentInteraction(); } public override void OnExitVent(PlayerControl pc, int ventId) { From e8af845fca22b90220b33933ad9a0edfbd3c3072 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 20:37:23 +0800 Subject: [PATCH 523/778] Some fix --- Modules/DelayNetworkedData.cs | 4 ++-- Modules/GuessManager.cs | 1 - Patches/HudPatch.cs | 2 +- Patches/MeetingHudPatch.cs | 13 ------------- Roles/Neutral/Jester.cs | 14 ++++---------- 5 files changed, 7 insertions(+), 27 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index ca9055abcd..a00cab54fa 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -12,7 +12,7 @@ public class InnerNetClientPatch [HarmonyPrefix] public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId) { - if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true; + if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return true; // We make sure other stuffs like playercontrol and Lobby behavior is spawned properly // Then we spawn networked data for new clients MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); @@ -141,7 +141,7 @@ public static bool SendAllStreamedObjectsPrefix(InnerNetClient __instance, ref b public static void FixedUpdatePostfix(InnerNetClient __instance) { // Send a networked data pre 2 fixed update should be a good practice? - if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return; + if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (!__instance.AmHost || __instance.Streams == null) return; if (timer == 0) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 49cd354b23..c56a7676e4 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -1045,7 +1045,6 @@ public static void Postfix(MeetingHud __instance) return; } - MeetingHudPopulateButtonsPatch.AlredyCreated = false; DestroyIDLabels(); UnityEngine.Object.Destroy(textTemplate.gameObject); } diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 0bde45929d..65017e8fbf 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -279,7 +279,7 @@ static class MapTaskOverlayHidePatch { public static void Postfix() { - if (GameStates.IsMeeting && MeetingHud.Instance.state is not MeetingHud.VoteStates.Animating && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) + if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) GuessManager.CreateIDLabels(MeetingHud.Instance); } } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index cb8c2588ad..20441e316d 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1198,19 +1198,6 @@ public static void Postfix(MeetingHud __instance) } } } -[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateButtons))] -class MeetingHudPopulateButtonsPatch -{ - public static bool AlredyCreated = false; - public static void Postfix(MeetingHud __instance) - { - if (AlredyCreated) return; - AlredyCreated = true; - - // Create all ID Label - GuessManager.CreateIDLabels(__instance); - } -} [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] class MeetingHudUpdatePatch { diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 127b41d74b..5eb6b409b6 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -8,9 +8,8 @@ internal class Jester : RoleBase { //===========================SETUP================================\\ private const int Id = 14400; - private static readonly HashSet PlayerIds = []; - public static bool HasEnabled => PlayerIds.Any(); - + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jester); + public override CustomRoles ThisRoleBase => CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -32,7 +31,7 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); CanVent = BooleanOptionItem.Create(Id + 3, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - CantMoveInVents = BooleanOptionItem.Create(Id + 10, GeneralOption.CantMoveOnVents, true, TabGroup.NeutralRoles, false) + CantMoveInVents = BooleanOptionItem.Create(Id + 10, GeneralOption.CantMoveOnVents, false, TabGroup.NeutralRoles, false) .SetParent(CanVent); HasImpostorVision = BooleanOptionItem.Create(Id + 4, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); @@ -47,13 +46,8 @@ public override void SetupCustomOption() } public override void Init() { - PlayerIds.Clear(); RememberBlockedVents.Clear(); } - public override void Add(byte playerId) - { - PlayerIds.Add(playerId); - } public override void ApplyGameOptions(IGameOptions opt, byte playerId) { AURoleOptions.EngineerCooldown = 1f; @@ -127,7 +121,7 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn DecidedWinner = true; } } - else if (CEMode.GetInt() == 2 && isMeetingHud) + else if (isMeetingHud) name += string.Format(Translator.GetString("JesterMeetingLoose"), MeetingsNeededForWin.GetInt() + 1); } } From 93638773552a9017a714a6d98689a2105546fd25 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 20:58:48 +0800 Subject: [PATCH 524/778] TryCast & Fix bug --- Modules/Utils.cs | 14 ++++----- Patches/PlayerControlPatch.cs | 2 +- Roles/(Ghosts)/Impostor/Bloodmoon.cs | 43 +++++++++++++++++++++++----- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 04014b3af3..0219e7d39e 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -149,7 +149,7 @@ public static bool IsActive(SystemTypes type) case SystemTypes.Electrical: { if (mapId == 5) return false; // if The Fungle return false - var SwitchSystem = ShipStatus.Instance.Systems[type].Cast(); + var SwitchSystem = ShipStatus.Instance.Systems[type].TryCast(); return SwitchSystem != null && SwitchSystem.IsActive; } case SystemTypes.Reactor: @@ -157,38 +157,38 @@ public static bool IsActive(SystemTypes type) if (mapId == 2) return false; // if Polus return false else { - var ReactorSystemType = ShipStatus.Instance.Systems[type].Cast(); + var ReactorSystemType = ShipStatus.Instance.Systems[type].TryCast(); return ReactorSystemType != null && ReactorSystemType.IsActive; } } case SystemTypes.Laboratory: { if (mapId != 2) return false; // Only Polus - var ReactorSystemType = ShipStatus.Instance.Systems[type].Cast(); + var ReactorSystemType = ShipStatus.Instance.Systems[type].TryCast(); return ReactorSystemType != null && ReactorSystemType.IsActive; } case SystemTypes.LifeSupp: { if (mapId is 2 or 4 or 5) return false; // Only Skeld & Dleks & Mira HQ - var LifeSuppSystemType = ShipStatus.Instance.Systems[type].Cast(); + var LifeSuppSystemType = ShipStatus.Instance.Systems[type].TryCast(); return LifeSuppSystemType != null && LifeSuppSystemType.IsActive; } case SystemTypes.HeliSabotage: { if (mapId != 4) return false; // Only Airhip - var HeliSabotageSystem = ShipStatus.Instance.Systems[type].Cast(); + var HeliSabotageSystem = ShipStatus.Instance.Systems[type].TryCast(); return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; } case SystemTypes.Comms: { if (mapId is 1 or 5) // Only Mira HQ & The Fungle { - var HqHudSystemType = ShipStatus.Instance.Systems[type].Cast(); + var HqHudSystemType = ShipStatus.Instance.Systems[type].TryCast(); return HqHudSystemType != null && HqHudSystemType.IsActive; } else { - var HudOverrideSystemType = ShipStatus.Instance.Systems[type].Cast(); + var HudOverrideSystemType = ShipStatus.Instance.Systems[type].TryCast(); return HudOverrideSystemType != null && HudOverrideSystemType.IsActive; } } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 1801c8e27d..40fe6b7bb4 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1359,7 +1359,7 @@ public static Task DoPostfix(PlayerControl __instance) } // Camouflage - if ((Utils.IsActive(SystemTypes.Comms) && Camouflage.IsActive) || Camouflager.AbilityActivated) + if ((Camouflage.IsActive && Utils.IsActive(SystemTypes.Comms)) || Camouflager.AbilityActivated) RealName = $"{RealName} "; string DeathReason = seer.Data.IsDead && seer.KnowDeathReason(target) diff --git a/Roles/(Ghosts)/Impostor/Bloodmoon.cs b/Roles/(Ghosts)/Impostor/Bloodmoon.cs index 12cb73fa78..fa21142901 100644 --- a/Roles/(Ghosts)/Impostor/Bloodmoon.cs +++ b/Roles/(Ghosts)/Impostor/Bloodmoon.cs @@ -1,4 +1,6 @@ using AmongUs.GameOptions; +using Hazel; +using InnerNet; using TOHE.Roles.Core; using TOHE.Roles.Double; using UnityEngine; @@ -51,6 +53,29 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.GuardianAngelCooldown = KillCooldown.GetFloat(); AURoleOptions.ProtectionDurationSeconds = 0f; } + public void SendRPC(byte targetId, bool add) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(AbilityLimit); + writer.Write(add); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + float Limit = reader.ReadSingle(); + bool add = reader.ReadBoolean(); + byte targetId = reader.ReadByte(); + + + AbilityLimit = Limit; + + if (add) + PlayerDie.Add(targetId, TimeTilDeath.GetInt()); + else + PlayerDie.Remove(targetId); + } public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) { if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) @@ -70,7 +95,7 @@ public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) LastTime.Add(target.PlayerId, GetTimeStamp()); killer.RpcResetAbilityCooldown(); AbilityLimit--; - SendSkillRPC(); + SendRPC(target.PlayerId, true); } return false; } @@ -95,11 +120,16 @@ private void OnFixedUpdateOther(PlayerControl player) } public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) { - if (LastTime.ContainsKey(DeadPlayer.PlayerId)) - LastTime.Remove(DeadPlayer.PlayerId); + var DeadPlayerId = DeadPlayer.PlayerId; + + if (LastTime.ContainsKey(DeadPlayerId)) + LastTime.Remove(DeadPlayerId); - if (PlayerDie.ContainsKey(DeadPlayer.PlayerId)) - PlayerDie.Remove(DeadPlayer.PlayerId); + if (PlayerDie.ContainsKey(DeadPlayerId)) + { + PlayerDie.Remove(DeadPlayerId); + SendRPC(DeadPlayerId, false); + } } public override string GetLowerTextOthers(PlayerControl seer, PlayerControl player = null, bool isForMeeting = false, bool isForHud = false) @@ -107,7 +137,6 @@ public override string GetLowerTextOthers(PlayerControl seer, PlayerControl play if (GameStates.IsMeeting || isForMeeting) return string.Empty; var playerid = player.PlayerId; - return PlayerDie.TryGetValue(playerid, out var DeathTimer) ? ColorString(GetRoleColor(CustomRoles.Bloodmoon), GetString("DeathTimer").Replace("{DeathTimer}", DeathTimer.ToString())) : ""; - + return PlayerDie.TryGetValue(playerid, out var DeathTimer) ? ColorString(GetRoleColor(CustomRoles.Bloodmoon), GetString("DeathTimer").Replace("{DeathTimer}", DeathTimer.ToString())) : string.Empty; } } From 51680ff7dc5a2e33c37ffd7fb27c5c2efa7cc62d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 21:08:23 +0800 Subject: [PATCH 525/778] Some changes --- Modules/Utils.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 0219e7d39e..fd3bd4a2a5 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -132,7 +132,7 @@ public static bool IsActive(SystemTypes type) return false; } - int mapId = GetActiveMapId(); + var mapName = (MapNames)GetActiveMapId(); /* The Skeld = 0 MIRA HQ = 1 @@ -148,13 +148,13 @@ public static bool IsActive(SystemTypes type) { case SystemTypes.Electrical: { - if (mapId == 5) return false; // if The Fungle return false + if (mapName is MapNames.Fungle) return false; // if The Fungle return false var SwitchSystem = ShipStatus.Instance.Systems[type].TryCast(); return SwitchSystem != null && SwitchSystem.IsActive; } case SystemTypes.Reactor: { - if (mapId == 2) return false; // if Polus return false + if (mapName is MapNames.Polus) return false; // if Polus return false else { var ReactorSystemType = ShipStatus.Instance.Systems[type].TryCast(); @@ -163,25 +163,25 @@ public static bool IsActive(SystemTypes type) } case SystemTypes.Laboratory: { - if (mapId != 2) return false; // Only Polus + if (mapName is not MapNames.Polus) return false; // Only Polus var ReactorSystemType = ShipStatus.Instance.Systems[type].TryCast(); return ReactorSystemType != null && ReactorSystemType.IsActive; } - case SystemTypes.LifeSupp: - { - if (mapId is 2 or 4 or 5) return false; // Only Skeld & Dleks & Mira HQ - var LifeSuppSystemType = ShipStatus.Instance.Systems[type].TryCast(); - return LifeSuppSystemType != null && LifeSuppSystemType.IsActive; - } case SystemTypes.HeliSabotage: { - if (mapId != 4) return false; // Only Airhip + if (mapName is not MapNames.Airship) return false; // Only Airhip var HeliSabotageSystem = ShipStatus.Instance.Systems[type].TryCast(); return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; } + case SystemTypes.LifeSupp: + { + if (mapName is MapNames.Polus or MapNames.Airship or MapNames.Fungle) return false; // Only Skeld & Dleks & Mira HQ + var LifeSuppSystemType = ShipStatus.Instance.Systems[type].TryCast(); + return LifeSuppSystemType != null && LifeSuppSystemType.IsActive; + } case SystemTypes.Comms: { - if (mapId is 1 or 5) // Only Mira HQ & The Fungle + if (mapName is MapNames.Mira or MapNames.Fungle) // Only Mira HQ & The Fungle { var HqHudSystemType = ShipStatus.Instance.Systems[type].TryCast(); return HqHudSystemType != null && HqHudSystemType.IsActive; @@ -194,7 +194,7 @@ public static bool IsActive(SystemTypes type) } case SystemTypes.MushroomMixupSabotage: { - if (mapId != 5) return false; // Only The Fungle + if (mapName is not MapNames.Fungle) return false; // Only The Fungle var MushroomMixupSabotageSystem = ShipStatus.Instance.Systems[type].TryCast(); return MushroomMixupSabotageSystem != null && MushroomMixupSabotageSystem.IsActive; } From 8274cf5a2310fb02c94384dd8d25d84617308157 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 21:12:34 +0800 Subject: [PATCH 526/778] Change --- Modules/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index fd3bd4a2a5..c19365977f 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -132,7 +132,7 @@ public static bool IsActive(SystemTypes type) return false; } - var mapName = (MapNames)GetActiveMapId(); + var mapName = GetActiveMapName(); /* The Skeld = 0 MIRA HQ = 1 From 40e2371931a1c5872b61f2df914ccfcb0578252f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 21:22:12 +0800 Subject: [PATCH 527/778] Target Is Already Dead --- Resources/Lang/en_US.json | 1 + Roles/Impostor/Blackmailer.cs | 2 +- Roles/Impostor/Ninja.cs | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 17b32a0ab7..e38b1a211d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2106,6 +2106,7 @@ "EnableSpurtCharge": "Display The Charge", "SpurtSuffix": "\n« Spurt: {0}% »", + "TargetIsAlreadyDead": "Target Is Already Dead", "ByBard": "by Bard", "ByBardGetFailed": "Oops, I seem to be out of inspiration.", "GangsterSuccessfullyRecruited": "You successfully recruited a player", diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 465f59db69..7132416b5f 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -55,7 +55,7 @@ private static void DoBlackmaile(PlayerControl blackmailer, PlayerControl target { if (!target.IsAlive()) { - blackmailer.Notify(Utils.ColorString(Utils.GetRoleColor(blackmailer.GetCustomRole()), GetString("NotAssassin"))); + blackmailer.Notify(Utils.ColorString(Utils.GetRoleColor(blackmailer.GetCustomRole()), GetString("TargetIsAlreadyDead"))); return; } diff --git a/Roles/Impostor/Ninja.cs b/Roles/Impostor/Ninja.cs index cd1ed2fe39..56e09ef659 100644 --- a/Roles/Impostor/Ninja.cs +++ b/Roles/Impostor/Ninja.cs @@ -128,10 +128,13 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl MarkedPlayer.Remove(shapeshifter.PlayerId); SendRPC(shapeshifter.PlayerId); - if (!(marketTarget == null || !marketTarget.IsAlive() || marketTarget.inVent || GameStates.IsMeeting)) + if (!(marketTarget == null || !marketTarget.IsAlive())) { if (shapeshifter.RpcCheckAndMurder(marketTarget, check: true)) { + if (marketTarget.inVent) + marketTarget.MyPhysics.RpcBootFromVent(Main.LastEnteredVent[marketTarget.PlayerId].Id); + shapeshifter.RpcTeleport(marketTarget.GetCustomPosition()); shapeshifter.ResetKillCooldown(); shapeshifter.RpcMurderPlayer(marketTarget); @@ -142,6 +145,8 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl return true; } } + else + shapeshifter.Notify(Utils.ColorString(Utils.GetRoleColor(shapeshifter.GetCustomRole()), GetString("TargetIsAlreadyDead"))); } return false; From d422dda57df258c4f7a3571e9d5ad1c211c3ebef Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 21:25:06 +0800 Subject: [PATCH 528/778] Move translations in DevBuild --- Resources/Lang/en_US.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7d6b74772a..e38b1a211d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -225,6 +225,7 @@ "Admirer": "Admirer", "TimeMaster": "Time Master", "Crusader": "Crusader", + "Altruist": "Altruist", "Reverie": "Reverie", "Lookout": "Lookout", "Telecommunication": "Telecommunication", @@ -538,6 +539,7 @@ "AdmirerInfo": "Choose a player to side with you", "TimeMasterInfo": "Rewind time!", "CrusaderInfo": "Kill a player's attacker", + "AltruistInfo": "Revive a player", "ReverieInfo": "With each kill, your cooldown decreases", "LookoutInfo": "See through disguises", "TelecommunicationInfo": "Track device usage", @@ -843,6 +845,7 @@ "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", + "AltruistInfoLong": "(Crewmates):\nAs the Altruist, you can sacrifice yourself to revive a dead body using the «Report» button.\nNote: If a dead player has left the game, you report that body normally.\nAlso revived player cannot report self dead body", "ReverieInfoLong": "(Crewmates):\nAs the Reverie, you can kill, but your cooldown starts high.\n\nIt increases if you kill a crewmate and reduces otherwise.\nDepending on the Host's setting, you may misfire on reaching the max kill cooldown, and your target dies with you. \n\nYou win with other crewmates.", "LookoutInfoLong": "(Crewmates):\nAs the Lookout, you can see the IDs of every player at all times.\nThis allows you to see through shapeshifts and camouflages.", "TelecommunicationInfoLong": "(Crewmates):\nAs the Telecommunication, you are notified when anyone uses cameras, vitals, door logs, or admin.", @@ -1160,6 +1163,7 @@ "GhostCanSeeDeathReason": "Ghost Can See Cause Of Death", "GhostIgnoreTasks": "Ghosts Exempt From Tasks", "ConvertedCanBeGhostRole": "Converted Players Can Be Any Ghost-Roles", + "NeutralCanBeGhostRole": "Neutral Players Can Be Any Ghost-Roles (Will change team respectively)", "MaxImpGhostRole": "Max Impostor Ghost-Roles", "MaxCrewGhostRole": "Max Crewmate Ghost-Roles", "DefaultAngelCooldown": "Default Ability Cooldown", @@ -1451,9 +1455,10 @@ "CanKill": "Can Kill", "KillCooldown": "Kill Cooldown", "CanVent": "Can Vent", - "CantMoveOnVents": "Can't Move On Vents", + "CantMoveOnVents": "Can't Move On Vents (May work unstable in some maps)", "ImpostorVision": "Has Impostor Vision", "CanUseSabotage": "Can Sabotage", + "CanHaveAccessToVitals": "Can Have Access To Vitals", "CanKillImpostors": "Can Kill Impostors", "CanGuess": "Can Guess in Guesser Mode or as Guesser", "HideVote": "Hide Vote", @@ -1701,6 +1706,10 @@ "MadmateCountMode.Imp": "Impostors", "MadmateCountMode.Original": "Original Team", + "Altruist_RevivedDeadBodyCannotBeReported_Option": "Revived Dead Body Cannot Be Reported", + "Altruist_YouTriedReportRevivedDeadBody": "You Tried Report Revived Dead Body", + "Altruist_DeadPlayerHasBeenRevived": "A Dead Player Has Been Revived!", + "SnatchesWin": "Snatches victory", "DemonKillCooldown": "Attack Cooldown", "DemonHealthMax": "Player max health", @@ -1899,7 +1908,7 @@ "WarnExample": "Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", "SayCommandDisabled": "The say command is currently disabled.", "MessageFromModerator": "MODERATOR", - "DeathReason.Kill": "Kill", + "DeathReason.Kill": "Killed", "DeathReason.Vote": "Ejected", "DeathReason.Suicide": "Suicide", "DeathReason.Spell": "Spelled", @@ -1943,6 +1952,7 @@ "DeathReason.Armageddon": "Armageddon", "DeathReason.Starved": "Starved", "DeathReason.Equilibrium": "Equilibrium", + "DeathReason.Sacrificed": "Sacrificed", "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", "Alive": "Alive", "Disconnected": "Disconnected", @@ -2096,6 +2106,7 @@ "EnableSpurtCharge": "Display The Charge", "SpurtSuffix": "\n« Spurt: {0}% »", + "TargetIsAlreadyDead": "Target Is Already Dead", "ByBard": "by Bard", "ByBardGetFailed": "Oops, I seem to be out of inspiration.", "GangsterSuccessfullyRecruited": "You successfully recruited a player", @@ -2304,6 +2315,8 @@ "Warning.BrokenVentsInDleksSendInGame": "Warning! The vents on this map are broken", "Warning.BrokenVentsInDleksMessage": "On the «dlekS ehT» map, the vents are broken, they cannot be fixed in host-only mods, this is a vanilla bug, so any roles using vent as an ability will not spawns on this map", + "Warning.NoGameEndIsEnabled": "Warning: {0} is enabled!", + "AntiBlackoutProtectionTitle": "Anti Blackout", "Warning.AntiBlackoutProtectionMsg": "Warning:\n\rBlack screen protection has been activated, due to the low number of alive Impostors, Crewmates and Neutral Killers\nThe voting screen will show as a tied vote (only affects the visual, not the results voting)\nModded players will see voting screen normally", "Warning.ShowAntiBlackExiledPlayer": "Last meeting triggered Black Screen Prevention!\nFollowing is the information of the player exiled in the last meeting.\n", From 64cf820ecddb760d537ae3465ee0ce62d3300220 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 23:52:43 +0800 Subject: [PATCH 529/778] Fix Overseer RpcSetSpecificScanner when Report Dead Body --- Roles/Crewmate/Overseer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 48aea22301..3d8a538edd 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -251,6 +251,10 @@ public override void OnFixedUpdate(PlayerControl player) public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { + if (_Player == null) return; + var farTarget = OverseerTimer[_Player.PlayerId].Item1; + farTarget?.RpcSetSpecificScanner(_Player, false); + OverseerTimer.Clear(); SendTimerRPC(0, byte.MaxValue); } From 0629a98dc73645f6601a69f72e55ea2a721bb030 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 17 Sep 2024 23:53:32 +0800 Subject: [PATCH 530/778] minuttes => minutes --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e38b1a211d..cf791acc05 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2298,7 +2298,7 @@ "Poll.MissingPlayers": "You can't start a poll with yourself dummy ;3", "Poll.Begin": "You may vote using /pv {answer}, ps: a number also works.", - "Poll.TimeInfo": "The results will be final in 2 minuttes", + "Poll.TimeInfo": "The results will be final in 2 minutes", "Poll.OnlyInLobby": "<#ab4f75>Sorry, this command may only be used in lobby", "Poll.Inactive": "There isn't any active poll currently.", From 1ab6d386da92debe3bfda0ea837d920574052e08 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:58:04 +0800 Subject: [PATCH 531/778] Lift NearBy vents to the top if a player is already in a vent --- Modules/ExtendedPlayerControl.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 64e5b38aec..f490927ac9 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -558,16 +558,35 @@ public static void RpcSpecificRejectShapeshift(this PlayerControl player, Player } } } + public static Vent GetClosestVent(this PlayerControl player) { var pos = player.GetCustomPosition(); return ShipStatus.Instance.AllVents.Where(x => x != null).MinBy(x => Vector2.Distance(pos, x.transform.position)); } + public static List GetVentsFromClosest(this PlayerControl player) { Vector2 playerpos = player.transform.position; - List vents = [.. ShipStatus.Instance.AllVents]; + List vents = new List(ShipStatus.Instance.AllVents); vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); + + // If player is inside a vent, we sort the nearby vents that the player can snapto and lift them to the top of the list + // Idk how to directly get the vent a player is in, so just assume the closet vent from the player is the vent that player is in + // Not sure about whether inVent flags works 100% correct here. Maybe player is being kicked from a vent and inVent flags can return true there + if (player.inVent && vents[0] != null) + { + var nextvents = vents[0].NearbyVents.ToList(); + nextvents.RemoveAll(v => v == null); + + foreach (var vent in nextvents) + { + vents.Remove(vent); + } + + vents.InsertRange(0, nextvents.FindAll(v => v != null)); + } + return vents; } From d5d89342d691ebcfe5e933c9f632239e90d8d084 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:59:15 +0800 Subject: [PATCH 532/778] Apply code suggestions --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f490927ac9..5b8221dbc8 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -568,7 +568,7 @@ public static Vent GetClosestVent(this PlayerControl player) public static List GetVentsFromClosest(this PlayerControl player) { Vector2 playerpos = player.transform.position; - List vents = new List(ShipStatus.Instance.AllVents); + List vents = new(ShipStatus.Instance.AllVents); vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); // If player is inside a vent, we sort the nearby vents that the player can snapto and lift them to the top of the list From d8788d256ef8a36133b46399463fa01dbe3799e1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 00:00:07 +0800 Subject: [PATCH 533/778] Change note --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index cf791acc05..8bcffe6cbe 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1455,7 +1455,7 @@ "CanKill": "Can Kill", "KillCooldown": "Kill Cooldown", "CanVent": "Can Vent", - "CantMoveOnVents": "Can't Move On Vents (May work unstable in some maps)", + "CantMoveOnVents": "Can't Move On Vents (Unstable in Dleks map)", "ImpostorVision": "Has Impostor Vision", "CanUseSabotage": "Can Sabotage", "CanHaveAccessToVitals": "Can Have Access To Vitals", From 263fcdc9767528997296b9136d2e970296d0b0a7 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 18 Sep 2024 00:01:15 +0800 Subject: [PATCH 534/778] Change comments --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5b8221dbc8..7d8d10b26b 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -571,7 +571,7 @@ public static List GetVentsFromClosest(this PlayerControl player) List vents = new(ShipStatus.Instance.AllVents); vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); - // If player is inside a vent, we sort the nearby vents that the player can snapto and lift them to the top of the list + // If player is inside a vent, we get the nearby vents that the player can snapto and insert them to the top of the list // Idk how to directly get the vent a player is in, so just assume the closet vent from the player is the vent that player is in // Not sure about whether inVent flags works 100% correct here. Maybe player is being kicked from a vent and inVent flags can return true there if (player.inVent && vents[0] != null) From f2e07d081959f957f265cd22fc5490525cd7062a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 15:15:27 +0800 Subject: [PATCH 535/778] Improve Altruist --- Modules/ExtendedPlayerControl.cs | 159 +++++----------------------- Modules/LocateArrow.cs | 2 +- Modules/NameColorManager.cs | 9 +- Modules/TargetArrow.cs | 2 +- Modules/Utils.cs | 5 +- Patches/PlayerControlPatch.cs | 5 +- Patches/onGameStartedPatch.cs | 1 + Resources/Lang/en_US.json | 9 ++ Roles/AddOns/Common/Radar.cs | 4 +- Roles/Crewmate/Altruist.cs | 174 +++++++++++++++++++------------ main.cs | 1 + 11 files changed, 165 insertions(+), 206 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 7d8d10b26b..a0f17a3de3 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -2,6 +2,7 @@ using Hazel; using InnerNet; using System; +using System.Linq; using System.Text; using TOHE.Modules; using TOHE.Patches; @@ -84,9 +85,6 @@ public static void RpcRevive(this PlayerControl player) Main.PlayerStates[player.PlayerId].IsDead = false; Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; - if (player.HasGhostRole()) - player.RpcSetCustomRole(customRole); - player.RpcChangeRoleBasis(customRole, true); player.ResetKillCooldown(); player.SyncSettings(); @@ -1225,7 +1223,10 @@ public static List GetPlayersInAbilityRangeSorted(this PlayerCont public static bool IsNeutralApocalypse(this PlayerControl player) => player.GetCustomRole().IsNA(); public static bool IsTransformedNeutralApocalypse(this PlayerControl player) => player.GetCustomRole().IsTNA(); public static bool IsNonNeutralKiller(this PlayerControl player) => player.GetCustomRole().IsNonNK(); - + + public static bool IsMurderedThisRound(this PlayerControl player) => player.PlayerId.IsMurderedThisRound(); + public static bool IsMurderedThisRound(this byte playerId) => Main.MurderedThisRound.Contains(playerId); + public static bool KnowDeathReason(this PlayerControl seer, PlayerControl target) => (Options.EveryoneCanSeeDeathReason.GetBool() || seer.Is(CustomRoles.Doctor) || seer.Is(CustomRoles.Autopsy) @@ -1241,134 +1242,30 @@ public static bool KnowLivingTeam(this PlayerControl seer, PlayerControl target) && !target.Data.IsDead; private readonly static LogHandler logger = Logger.Handler("KnowRoleTarget"); - public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target, bool isVanilla = false) + public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target) { - if (Options.CurrentGameMode == CustomGameMode.FFA || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) - { - if (isVanilla) - logger.Info($"IsFFA {Options.CurrentGameMode == CustomGameMode.FFA} or Game End {GameEndCheckerForNormal.ShowAllRolesWhenGameEnd}"); - return true; - } - else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || (PlayerControl.LocalPlayer.PlayerId == seer.PlayerId && Main.GodMode.Value)) - { - if (isVanilla) - logger.Info($"Is GM {seer.Is(CustomRoles.GM)} or {target.Is(CustomRoles.GM)} or GodMode {(PlayerControl.LocalPlayer.PlayerId == seer.PlayerId && Main.GodMode.Value)}"); - return true; - } - else if (Main.VisibleTasksCount && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) - { - if (isVanilla) - logger.Info($"Is dead and can see other roles"); - return true; - } - else if (Options.SeeEjectedRolesInMeeting.GetBool() && Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - if (isVanilla) - logger.Info($"See Ejected Roles In Meeting"); - return true; - } - else if (seer.GetCustomRole() == target.GetCustomRole() && seer.GetCustomRole().IsNK()) - { - if (isVanilla) - logger.Info("Roles == and IsNK"); - return true; - } - else if (Options.LoverKnowRoles.GetBool() && seer.Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) - { - if (isVanilla) - logger.Info($"Lover Know Roles"); - return true; - } - else if (Options.ImpsCanSeeEachOthersRoles.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor)) - { - if (isVanilla) - logger.Info($"Imps Can See Each Others Roles"); - return true; - } - else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) - { - if (isVanilla) - logger.Info($"Madmate Know Whos Imp"); - return true; - } - else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor)) - { - if (isVanilla) - logger.Info($"Imp Know Whos Madmate"); - return true; - } - else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) - { - if (isVanilla) - logger.Info("Impostor see Imp Ghost Role"); - return true; - } - else if (target.GetRoleClass().KnowRoleTarget(seer, target)) - { - if (isVanilla) - logger.Info($"target {target.GetCustomRole()} GetRoleClass().KnowRoleTarget"); - return true; - } - else if (seer.GetRoleClass().KnowRoleTarget(seer, target)) - { - if (isVanilla) - logger.Info($"seer {seer.GetCustomRole()} GetRoleClass().KnowRoleTarget"); - return true; - } - else if (Solsticer.OtherKnowSolsticer(target)) - { - if (isVanilla) - logger.Info("Solsticer Other Know Solsticer"); - return true; - } - else if (Overseer.IsRevealedPlayer(seer, target) && !target.Is(CustomRoles.Trickster)) - { - if (isVanilla) - logger.Info($"Overseer.IsRevealedPlayer"); - return true; - } - else if (Gravestone.EveryoneKnowRole(target)) - { - if (isVanilla) - logger.Info("Gravestone.EveryoneKnowRole"); - return true; - } - else if (Mimic.CanSeeDeadRoles(seer, target)) - { - if (isVanilla) - logger.Info("Mimic.CanSeeDeadRoles"); - return true; - } - else if (Workaholic.OthersKnowWorka(target)) - { - if (isVanilla) - logger.Info("Workaholic Others Know Worka"); - return true; - } - else if (Jackal.JackalKnowRole(seer, target)) - { - if (isVanilla) - logger.Info("Jackal Know Role"); - return true; - } - else if (Cultist.KnowRole(seer, target)) - { - if (isVanilla) - logger.Info("Cultist Know Role"); - return true; - } - else if (Infectious.KnowRole(seer, target)) - { - if (isVanilla) - logger.Info("Infectious Know Role"); - return true; - } - else if (Virus.KnowRole(seer, target)) - { - if (isVanilla) - logger.Info($"Virus Know Role"); - return true; - } + if (Options.CurrentGameMode == CustomGameMode.FFA || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) return true; + else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || (PlayerControl.LocalPlayer.PlayerId == seer.PlayerId && Main.GodMode.Value)) return true; + else if (Options.SeeEjectedRolesInMeeting.GetBool() && Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote) return true; + else if (Altruist.HasEnabled && seer.IsMurderedThisRound()) return false; + else if (Main.VisibleTasksCount && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) return true; + else if (seer.GetCustomRole() == target.GetCustomRole() && seer.GetCustomRole().IsNK()) return true; + else if (Options.LoverKnowRoles.GetBool() && seer.Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) return true; + else if (Options.ImpsCanSeeEachOthersRoles.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor)) return true; + else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) return true; + else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor)) return true; + else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) return true; + else if (target.GetRoleClass().KnowRoleTarget(seer, target)) return true; + else if (seer.GetRoleClass().KnowRoleTarget(seer, target)) return true; + else if (Solsticer.OtherKnowSolsticer(target)) return true; + else if (Overseer.IsRevealedPlayer(seer, target) && !target.Is(CustomRoles.Trickster)) return true; + else if (Gravestone.EveryoneKnowRole(target)) return true; + else if (Mimic.CanSeeDeadRoles(seer, target)) return true; + else if (Workaholic.OthersKnowWorka(target)) return true; + else if (Jackal.JackalKnowRole(seer, target)) return true; + else if (Cultist.KnowRole(seer, target)) return true; + else if (Infectious.KnowRole(seer, target)) return true; + else if (Virus.KnowRole(seer, target)) return true; else return false; diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index d4ccf319ec..94972b6a1f 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -29,7 +29,7 @@ public static void Init() public static void SendRPC(int index, byte seerId, Vector3 vector3) { var seer = Utils.GetPlayerById(seerId); - if (!AmongUsClient.Instance.AmHost || seer == null) return; + if (!AmongUsClient.Instance.AmHost || seer == null || seer.AmOwner) return; var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable, seer.GetClientId()); writer.Write(false); writer.WritePacked(index); diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 93df28dabb..386558c353 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -2,6 +2,7 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; +using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; @@ -30,6 +31,12 @@ public static string ApplyNameColorData(this string name, PlayerControl seer, Pl } private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target, bool isMeeting, out string color) { + if (Altruist.HasEnabled && seer.IsMurderedThisRound()) + { + color = ""; + return false; + } + if (seer != target) target = DollMaster.SwapPlayerInfo(target); // If a player is possessed by the Dollmaster swap each other's controllers. @@ -86,8 +93,8 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target else return seer == target || (Main.GodMode.Value && seer.IsHost()) || (Options.CurrentGameMode == CustomGameMode.FFA) - || (Main.VisibleTasksCount && Main.PlayerStates[seer.Data.PlayerId].IsDead && seer.Data.IsDead && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) || seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) + || (Main.VisibleTasksCount && Main.PlayerStates[seer.Data.PlayerId].IsDead && seer.Data.IsDead && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) || target.GetRoleClass().OthersKnowTargetRoleColor(seer, target) || Mimic.CanSeeDeadRoles(seer, target) || (seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor)) diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index 9bd2dde139..96536733d4 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -29,7 +29,7 @@ public static void Init() public static void SendRPC(int index, byte seerId, byte targetId = byte.MaxValue) { var seer = Utils.GetPlayerById(seerId); - if (!AmongUsClient.Instance.AmHost || seer == null) return; + if (!AmongUsClient.Instance.AmHost || seer == null || seer.AmOwner) return; var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable, seer.GetClientId()); writer.Write(true); writer.WritePacked(index); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index c19365977f..ce55905e08 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -23,6 +23,7 @@ using TOHE.Roles.Core; using static TOHE.Translator; using TOHE.Patches; +using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -1932,7 +1933,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SelfSuffix.Append(seerRoleClass?.GetSuffix(seer, seer, isForMeeting: isForMeeting)); SelfSuffix.Append(CustomRoleManager.GetSuffixOthers(seer, seer, isForMeeting: isForMeeting)); - SelfSuffix.Append(Radar.GetPlayerArrow(seer, isForMeeting: isForMeeting)); + SelfSuffix.Append(Radar.GetPlayerArrow(seer, seer, isForMeeting: isForMeeting)); SelfSuffix.Append(Spurt.GetSuffix(seer, isformeeting: isForMeeting)); @@ -2081,7 +2082,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl // ====== Seer know target role ====== - bool KnowRoleTarget = ExtendedPlayerControl.KnowRoleTarget(seer, target, true); + bool KnowRoleTarget = ExtendedPlayerControl.KnowRoleTarget(seer, target); string TargetRoleText = KnowRoleTarget ? $"{seer.GetDisplayRoleAndSubName(target, false)}{GetProgressText(target)}\r\n" : ""; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 40fe6b7bb4..e308de736a 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -449,6 +449,8 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player target.SetDeathReason(PlayerState.DeathReason.Kill); } + Main.MurderedThisRound.Add(target.PlayerId); + // Check Youtuber first died if (Main.FirstDied == "" && target.Is(CustomRoles.Youtuber) && !killer.Is(CustomRoles.KillingMachine)) { @@ -857,6 +859,7 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta Main.LastVotedPlayerInfo = null; Main.AllKillers.Clear(); GuessManager.GuesserGuessed.Clear(); + Main.MurderedThisRound.Clear(); Logger.Info($"target is null? - {target == null}", "AfterReportTasks"); Logger.Info($"target.Object is null? - {target?.Object == null}", "AfterReportTasks"); @@ -1316,7 +1319,7 @@ public static Task DoPostfix(PlayerControl __instance) Suffix.Append(seerRoleClass?.GetSuffix(seer, target, false)); Suffix.Append(CustomRoleManager.GetSuffixOthers(seer, target, false)); - Suffix.Append(Radar.GetPlayerArrow(seer, isForMeeting: false)); + Suffix.Append(Radar.GetPlayerArrow(seer, target, isForMeeting: false)); if (seerRole.IsImpostor() && target.GetPlayerTaskState().IsTaskFinished) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index fcadf68fa7..f57042a2d5 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -62,6 +62,7 @@ public static void Postfix(AmongUsClient __instance) Main.LastEnteredVent.Clear(); Main.LastEnteredVentLocation.Clear(); + Main.MurderedThisRound.Clear(); Main.DesyncPlayerList.Clear(); Main.PlayersDiedInMeeting.Clear(); GuessManager.GuesserGuessed.Clear(); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 8bcffe6cbe..aaf6fb9655 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1707,8 +1707,17 @@ "MadmateCountMode.Original": "Original Team", "Altruist_RevivedDeadBodyCannotBeReported_Option": "Revived Dead Body Cannot Be Reported", + "Altruist_ImpostorsCanGetsAlert": "Impostors Can Get Alert", + "Altruist_ImpostorsCanGetsArrow": "Impostors Can Get Arrow", + "Altruist_NeutralKillersCanGetsAlert": "Neutral Killers Can Get Alert", + "Altruist_NeutralKillersCanGetsArrow": "Neutral Killers Can Get Arrow", + "AltruistSuffix": "<#00ffa5>Mode: {0}", + "AltruistReviveMode": "Revive", + "AltruistReportMode": "Report", "Altruist_YouTriedReportRevivedDeadBody": "You Tried Report Revived Dead Body", "Altruist_DeadPlayerHasBeenRevived": "A Dead Player Has Been Revived!", + "AltruistReportButton": "Revive", + "AltruistAbilityButton": "Change Mode", "SnatchesWin": "Snatches victory", "DemonKillCooldown": "Attack Cooldown", diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index df5811792d..dbab822cf9 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -39,9 +39,9 @@ public void OnFixedUpdateLowLoad(PlayerControl seer) TargetArrow.Add(seer.PlayerId, closest.PlayerId); } } - public static string GetPlayerArrow(PlayerControl seer, bool isForMeeting = false) + public static string GetPlayerArrow(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (isForMeeting || !seer.Is(CustomRoles.Radar)) return string.Empty; + if (isForMeeting || !seer.Is(CustomRoles.Radar) || seer.PlayerId != target.PlayerId) return string.Empty; return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Radar), TargetArrow.GetArrows(seer)); } } diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index e030b58343..692f488f34 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -8,126 +8,166 @@ internal class Altruist : RoleBase //===========================SETUP================================\\ private const int Id = 29800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Altruist); - public override bool IsExperimental => true; - public override CustomRoles ThisRoleBase => CanHaveAccessToVitals.GetBool() ? CustomRoles.Scientist : CustomRoles.Crewmate; + public override CustomRoles ThisRoleBase => CustomRoles.Engineer; public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ private static OptionItem RevivedDeadBodyCannotBeReported; - private static OptionItem CanHaveAccessToVitals; - private static OptionItem BatteryCooldown; - private static OptionItem BatteryDuration; + //private static OptionItem KillerAlwaysCanGetAlertAndArrow; + private static OptionItem ImpostorsCanGetsAlert; + private static OptionItem ImpostorsCanGetsArrow; + private static OptionItem NeutralKillersCanGetsAlert; + private static OptionItem NeutralKillersCanGetsArrow; + private bool IsRevivingMode = true; private byte RevivedPlayerId = byte.MaxValue; - private readonly static HashSet AllRevivedPlayerId = []; + //private readonly static HashSet AllRevivedPlayerId = []; public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Altruist); RevivedDeadBodyCannotBeReported = BooleanOptionItem.Create(Id + 10, "Altruist_RevivedDeadBodyCannotBeReported_Option", true, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); - CanHaveAccessToVitals = BooleanOptionItem.Create(Id + 11, GeneralOption.CanHaveAccessToVitals, true, TabGroup.CrewmateRoles, false) + ImpostorsCanGetsAlert = BooleanOptionItem.Create(Id + 11, "Altruist_ImpostorsCanGetsAlert", true, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); - BatteryCooldown = IntegerOptionItem.Create(Id + 12, GeneralOption.ScientistBase_BatteryCooldown, new(1, 250, 1), 15, TabGroup.CrewmateRoles, false) - .SetParent(CanHaveAccessToVitals) - .SetValueFormat(OptionFormat.Seconds); - BatteryDuration = IntegerOptionItem.Create(Id + 13, GeneralOption.ScientistBase_BatteryDuration, new(1, 250, 1), 5, TabGroup.CrewmateRoles, false) - .SetParent(CanHaveAccessToVitals) - .SetValueFormat(OptionFormat.Seconds); + ImpostorsCanGetsArrow = BooleanOptionItem.Create(Id + 12, "Altruist_ImpostorsCanGetsArrow", true, TabGroup.CrewmateRoles, false) + .SetParent(ImpostorsCanGetsAlert); + NeutralKillersCanGetsAlert = BooleanOptionItem.Create(Id + 13, "Altruist_NeutralKillersCanGetsAlert", true, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); + NeutralKillersCanGetsArrow = BooleanOptionItem.Create(Id + 14, "Altruist_NeutralKillersCanGetsArrow", true, TabGroup.CrewmateRoles, false) + .SetParent(NeutralKillersCanGetsAlert); } public override void Init() { RevivedPlayerId = byte.MaxValue; - AllRevivedPlayerId.Clear(); - } - public override void Add(byte playerId) - { - CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); + //AllRevivedPlayerId.Clear(); + IsRevivingMode = true; } public override void ApplyGameOptions(IGameOptions opt, byte playerId) { - AURoleOptions.ScientistCooldown = BatteryCooldown.GetInt(); - AURoleOptions.ScientistBatteryCharge = BatteryDuration.GetInt(); + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + IsRevivingMode = !IsRevivingMode; + Utils.NotifyRoles(SpecifySeer: physics.myPlayer, ForceLoop: false); } - public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { - if (deadBody != null && deadBody.Object != null) + if (deadBody == null || deadBody.Object == null) return true; + + if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) { - if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) + var deadPlayer = deadBody.Object; + var deadPlayerId = deadPlayer.PlayerId; + var deadBodyObject = deadBody.GetDeadBody(); + + RevivedPlayerId = deadPlayerId; + //AllRevivedPlayerId.Add(deadPlayerId); + + if (deadPlayer.HasGhostRole()) { - var deadPlayer = deadBody.Object; - var deadPlayerId = deadPlayer.PlayerId; - var deadBodyObject = deadBody.GetDeadBody(); + deadPlayer.GetRoleClass().Remove(deadPlayerId); + deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).CustomRole); + deadPlayer.GetRoleClass().Add(deadPlayerId); + } - RevivedPlayerId = deadPlayerId; - AllRevivedPlayerId.Add(deadPlayerId); + deadPlayer.RpcTeleport(deadBodyObject.transform.position); + deadPlayer.RpcRevive(); - deadPlayer.RpcTeleport(deadBodyObject.transform.position); - deadPlayer.RpcRevive(); + _Player.SetDeathReason(PlayerState.DeathReason.Sacrificed); + _Player.Data.IsDead = true; + _Player.RpcExileV2(); + Main.PlayerStates[_Player.PlayerId].SetDead(); - if (deadPlayer.HasGhostRole()) + if (ImpostorsCanGetsAlert.GetBool() || NeutralKillersCanGetsAlert.GetBool()) + { + foreach (var pc in Main.AllAlivePlayerControls) { - deadPlayer.GetRoleClass().Remove(deadPlayerId); - deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).CustomRole); - deadPlayer.GetRoleClass().Add(deadPlayerId); - } + if (pc.GetCustomRole().IsCrewmate()) continue; - _Player.SetDeathReason(PlayerState.DeathReason.Sacrificed); - _Player.Data.IsDead = true; - _Player.RpcExileV2(); - Main.PlayerStates[_Player.PlayerId].SetDead(); + var getAlert = false; + var getArrow = false; - foreach (var pc in Main.AllPlayerControls) - { - if (pc.Is(Custom_Team.Impostor) && pc.PlayerId != RevivedPlayerId) + if (ImpostorsCanGetsAlert.GetBool() && pc.Is(Custom_Team.Impostor) && pc.PlayerId != RevivedPlayerId) + { + getAlert = true; + + if (ImpostorsCanGetsArrow.GetBool()) + getArrow = true; + } + else if (NeutralKillersCanGetsAlert.GetBool() && (pc.IsNeutralKiller() || pc.IsNeutralApocalypse()) && pc.PlayerId != RevivedPlayerId) + { + getAlert = true; + + if (NeutralKillersCanGetsArrow.GetBool()) + getArrow = true; + } + + if (getAlert) { - TargetArrow.Add(pc.PlayerId, deadPlayerId); pc.KillFlash(playKillSound: false); pc.Notify(Translator.GetString("Altruist_DeadPlayerHasBeenRevived")); } + if (getArrow) + TargetArrow.Add(pc.PlayerId, deadPlayerId); } - Utils.NotifyRoles(); - return false; - } - else if ((RevivedDeadBodyCannotBeReported.GetBool() || reporter.PlayerId == RevivedPlayerId) && deadBody.PlayerId == RevivedPlayerId) - { - reporter.Notify(Translator.GetString("Altruist_YouTriedReportRevivedDeadBody")); - return false; } + Utils.NotifyRoles(); + return false; + } + else if ((RevivedDeadBodyCannotBeReported.GetBool() || reporter.PlayerId == RevivedPlayerId) && deadBody.PlayerId == RevivedPlayerId) + { + var countDeadBody = UnityEngine.Object.FindObjectsOfType().Count(bead => bead.ParentId == deadBody.PlayerId); + if (countDeadBody >= 2) return true; + + reporter.Notify(Translator.GetString("Altruist_YouTriedReportRevivedDeadBody")); + return false; } return true; } - private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) + public override string GetLowerText(PlayerControl seer, PlayerControl target, bool isForMeeting = false, bool isForHud = false) { - if (inMeeting) return; - // if Revived Player is dead again, clear RevivedPlayerId - if (RevivedPlayerId == target.PlayerId) - { - RevivedPlayerId = byte.MaxValue; - } + if (seer.PlayerId != target.PlayerId || isForMeeting) return string.Empty; + return string.Format(Translator.GetString("AltruistSuffix"), Translator.GetString(IsRevivingMode ? "AltruistReviveMode" : "AltruistReportMode")); } public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (RevivedPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId || !seer.Is(Custom_Team.Impostor)) return string.Empty; - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Altruist), TargetArrow.GetArrows(seer));; + if (RevivedPlayerId == byte.MaxValue || isForMeeting || seer.PlayerId != target.PlayerId) return string.Empty; + if (seer.Is(Custom_Team.Impostor) || seer.IsNeutralKiller() || seer.IsNeutralApocalypse()) + { + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Altruist), TargetArrow.GetArrows(seer)); + } + return string.Empty; } - public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - if (RevivedPlayerId != byte.MaxValue) + if (!(ImpostorsCanGetsArrow.GetBool() || NeutralKillersCanGetsArrow.GetBool()) || RevivedPlayerId == byte.MaxValue) return; + + foreach (var pc in Main.AllAlivePlayerControls) { - foreach (var pc in Main.AllAlivePlayerControls) + if (ImpostorsCanGetsArrow.GetBool() && pc.Is(Custom_Team.Impostor)) { - if (pc.Is(Custom_Team.Impostor)) - { - TargetArrow.Remove(pc.PlayerId, RevivedPlayerId); - continue; - } + TargetArrow.Remove(pc.PlayerId, RevivedPlayerId); + continue; + } + if (NeutralKillersCanGetsArrow.GetBool() && (pc.IsNeutralKiller() || pc.IsNeutralApocalypse())) + { + TargetArrow.Remove(pc.PlayerId, RevivedPlayerId); + continue; } } } + + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + hud?.AbilityButton?.OverrideText(Translator.GetString("AltruistAbilityButton")); + + if (IsRevivingMode) + hud?.ReportButton?.OverrideText(Translator.GetString("AltruistReportButton")); + } } diff --git a/main.cs b/main.cs index b2302811fb..60edc6c57d 100644 --- a/main.cs +++ b/main.cs @@ -144,6 +144,7 @@ public class Main : BasePlugin public static bool MeetingIsStarted = false; public static readonly HashSet DesyncPlayerList = []; + public static readonly HashSet MurderedThisRound = []; public static readonly HashSet TasklessCrewmate = []; public static readonly HashSet OverDeadPlayerList = []; public static readonly HashSet UnreportableBodies = []; From 933eae4313d3024725da27194dd6507ad4c8edfe Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 16:14:14 +0800 Subject: [PATCH 536/778] New add-on: Prohibited --- Modules/CustomRolesHelper.cs | 7 +++ Modules/ExtendedPlayerControl.cs | 4 ++ Modules/OptionHolder.cs | 2 +- Patches/IntroPatch.cs | 2 +- Resources/roleColor.json | 1 + Roles/AddOns/Common/Prohibited.cs | 77 +++++++++++++++++++++++++++++++ Roles/Neutral/Jester.cs | 2 +- main.cs | 1 + 8 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 Roles/AddOns/Common/Prohibited.cs diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 0b93af3ae6..a73e369ba3 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -962,6 +962,13 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; break; + case CustomRoles.Prohibited: + if (!pc.CanUseVents()) + return false; + if (pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool()) + return false; + break; + case CustomRoles.Flash: if (pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Solsticer) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index a0f17a3de3..828a645385 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1164,6 +1164,10 @@ public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, Rebirth.Remove(Killed.PlayerId); Rebirth.Add(target.PlayerId); break; + case CustomRoles.Prohibited: + Prohibited.Remove(Killed.PlayerId); + Prohibited.Add(target.PlayerId); + break; } } public static bool RpcCheckAndMurder(this PlayerControl killer, PlayerControl target, bool check = false) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c38c41c7df..3ecb5a357d 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -622,7 +622,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29800 last id for roles/add-ons (Next use 29900) + // 29900 last id for roles/add-ons (Next use 30000) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 9b3f7fddf3..88150a6722 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -589,7 +589,7 @@ public static void Prefix() if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { var mapId = Utils.GetActiveMapId(); - Logger.Msg($"Check map {mapId}", "Map"); + Logger.Msg($"Check map {mapId} - all vents count: {ShipStatus.Instance.AllVents.Count}", "Map"); RandomSpawn.SpawnMap map = mapId switch { 0 => new RandomSpawn.SkeldSpawnMap(), diff --git a/Resources/roleColor.json b/Resources/roleColor.json index f786d35e3e..d49358bfb3 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -106,6 +106,7 @@ "Pelican": "#34C849", "Revolutionist": "#ba4d06", "Hater": "#414b66", + "Prohibited": "#000295", "Demon": "#68bc71", "Stalker": "#483d8b", "Workaholic": "#008b8b", diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs new file mode 100644 index 0000000000..633ef4ea16 --- /dev/null +++ b/Roles/AddOns/Common/Prohibited.cs @@ -0,0 +1,77 @@ +using TOHE.Roles.Core; +using static TOHE.Options; + +namespace TOHE.Roles.AddOns.Common; + +public class Prohibited : IAddon +{ + private const int Id = 29900; + public AddonTypes Type => AddonTypes.Harmful; + + private static OptionItem CountBlockedVentsInSkeld; + private static OptionItem CountBlockedVentsInMira; + private static OptionItem CountBlockedVentsInPolus; + private static OptionItem CountBlockedVentsInDleks; + private static OptionItem CountBlockedVentsInAirship; + private static OptionItem CountBlockedVentsInFungle; + + private static readonly Dictionary RememberBlokcedVents = []; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(Id, CustomRoles.Prohibited, canSetNum: true, teamSpawnOptions: true); + CountBlockedVentsInSkeld = IntegerOptionItem.Create(Id + 10, "Prohibited_CountBlockedVentsInSkeld", new(0, 14, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + CountBlockedVentsInMira = IntegerOptionItem.Create(Id + 11, "Prohibited_CountBlockedVentsInMira", new(0, 11, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + CountBlockedVentsInPolus = IntegerOptionItem.Create(Id + 12, "Prohibited_CountBlockedVentsInPolus", new(0, 12, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + CountBlockedVentsInDleks = IntegerOptionItem.Create(Id + 13, "Prohibited_CountBlockedVentsInDleks", new(0, 14, 1), 0, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + CountBlockedVentsInAirship = IntegerOptionItem.Create(Id + 14, "Prohibited_CountBlockedVentsInAirship", new(0, 12, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + CountBlockedVentsInFungle = IntegerOptionItem.Create(Id + 15, "Prohibited_CountBlockedVentsInFungle", new(0, 10, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + } + + public static void Init() + { + RememberBlokcedVents.Clear(); + } + public static void Add(byte playerId) + { + var coutBlokedVents = Utils.GetActiveMapName() switch + { + MapNames.Skeld => CountBlockedVentsInSkeld.GetInt(), + MapNames.Mira => CountBlockedVentsInMira.GetInt(), + MapNames.Polus => CountBlockedVentsInPolus.GetInt(), + MapNames.Dleks => CountBlockedVentsInDleks.GetInt(), + MapNames.Airship => CountBlockedVentsInAirship.GetInt(), + MapNames.Fungle => CountBlockedVentsInFungle.GetInt(), + _ => 0 + }; + + if (coutBlokedVents <= 0) return; + var allVents = ShipStatus.Instance.AllVents.ToList(); + var allVentsCount = allVents.Count; + + if (coutBlokedVents > allVentsCount) + { + coutBlokedVents = allVentsCount; + } + + for (int i = 0; i < coutBlokedVents; i++) + { + var vent = allVents.RandomElement(); + RememberBlokcedVents[playerId] = vent.Id; + CustomRoleManager.BlockedVentsList[playerId].Add(vent.Id); + allVents.Remove(vent); + } + } + public static void Remove(byte playerId) + { + if (!RememberBlokcedVents.ContainsKey(playerId)) return; + + foreach (var (pcId, ventId) in RememberBlokcedVents) + { + if (pcId != playerId) continue; + + CustomRoleManager.BlockedVentsList[playerId].Remove(ventId); + } + RememberBlokcedVents.Remove(playerId); + } +} diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 5eb6b409b6..1cb1413c08 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -17,7 +17,7 @@ internal class Jester : RoleBase private static OptionItem CanUseMeetingButton; private static OptionItem HasImpostorVision; private static OptionItem CanVent; - private static OptionItem CantMoveInVents; + public static OptionItem CantMoveInVents; private static OptionItem MeetingsNeededForWin; private static OptionItem HideJesterVote; public static OptionItem SunnyboyChance; diff --git a/main.cs b/main.cs index 60edc6c57d..ea80315608 100644 --- a/main.cs +++ b/main.cs @@ -918,6 +918,7 @@ public enum CustomRoles Onbound, Overclocked, Paranoia, + Prohibited, Radar, Rainbow, Rascal, From 43130b3d12cbc646aa4f92a636b01d88535244d0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 16:15:51 +0800 Subject: [PATCH 537/778] Remove --- Patches/IntroPatch.cs | 2 +- Roles/AddOns/Common/Prohibited.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 88150a6722..9b3f7fddf3 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -589,7 +589,7 @@ public static void Prefix() if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) { var mapId = Utils.GetActiveMapId(); - Logger.Msg($"Check map {mapId} - all vents count: {ShipStatus.Instance.AllVents.Count}", "Map"); + Logger.Msg($"Check map {mapId}", "Map"); RandomSpawn.SpawnMap map = mapId switch { 0 => new RandomSpawn.SkeldSpawnMap(), diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index 633ef4ea16..d6a4175b8d 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -49,11 +49,6 @@ public static void Add(byte playerId) var allVents = ShipStatus.Instance.AllVents.ToList(); var allVentsCount = allVents.Count; - if (coutBlokedVents > allVentsCount) - { - coutBlokedVents = allVentsCount; - } - for (int i = 0; i < coutBlokedVents; i++) { var vent = allVents.RandomElement(); From 67449281fd071516c89ee5b2cbb9ef0294803b82 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 16:18:51 +0800 Subject: [PATCH 538/778] Fix bug --- Roles/AddOns/Common/Prohibited.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index d6a4175b8d..1ba8119d62 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -15,7 +15,7 @@ public class Prohibited : IAddon private static OptionItem CountBlockedVentsInAirship; private static OptionItem CountBlockedVentsInFungle; - private static readonly Dictionary RememberBlokcedVents = []; + private static readonly Dictionary> RememberBlokcedVents = []; public void SetupCustomOption() { @@ -52,19 +52,17 @@ public static void Add(byte playerId) for (int i = 0; i < coutBlokedVents; i++) { var vent = allVents.RandomElement(); - RememberBlokcedVents[playerId] = vent.Id; + RememberBlokcedVents[playerId].Add(vent.Id); CustomRoleManager.BlockedVentsList[playerId].Add(vent.Id); allVents.Remove(vent); } } public static void Remove(byte playerId) { - if (!RememberBlokcedVents.ContainsKey(playerId)) return; + if (!RememberBlokcedVents.TryGetValue(playerId, out var ventListId)) return; - foreach (var (pcId, ventId) in RememberBlokcedVents) + foreach(var ventId in ventListId) { - if (pcId != playerId) continue; - CustomRoleManager.BlockedVentsList[playerId].Remove(ventId); } RememberBlokcedVents.Remove(playerId); From 495042a0e586f2ce65d15226148f1c17ac237045 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 16:39:41 +0800 Subject: [PATCH 539/778] Fix error for Psychic & some changes --- Resources/Lang/en_US.json | 11 ++++++----- Roles/Crewmate/Psychic.cs | 23 +++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index aaf6fb9655..0bc66e7b6e 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1613,11 +1613,12 @@ "EvilHackerDeadbody": "DEAD", "TraitorKnowMadmate": "Traitor Knows Madmates", - "NBareRed": "Neutral Benign can be red", - "NEareRed": "Neutral Evil can be red", - "NCareRed": "Neutral Chaos can be red", - "NAareRed": "Neutral Apocalypse can be red", - "CrewKillingRed": "Crewmate Killings can be red", + "Psychic_NBareRed": "Neutral Benign can be red", + "Psychic_NEareRed": "Neutral Evil can be red", + "Psychic_NCareRed": "Neutral Chaos can be red", + "Psychic_NAareRed": "Neutral Apocalypse can be red", + "Psychic_NKareRed": "Neutral Killers can be red", + "Psychic_CrewKillingRed": "Crewmate Killing can be red", "PsychicCanSeeNum": "Max number of red names", "PsychicFresh": "New red names every meeting", "DetectiveCanknowKiller": "Can find the killer's role", diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index 89dda19580..be3f25e468 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -23,6 +23,7 @@ internal class Psychic : RoleBase private static OptionItem NEshowEvil; private static OptionItem NCshowEvil; private static OptionItem NAshowEvil; + private static OptionItem NKshowEvil; private readonly HashSet RedPlayer = []; @@ -32,11 +33,12 @@ public override void SetupCustomOption() CanSeeNum = IntegerOptionItem.Create(Id + 2, "PsychicCanSeeNum", new(1, 15, 1), 3, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]) .SetValueFormat(OptionFormat.Pieces); Fresh = BooleanOptionItem.Create(Id + 6, "PsychicFresh", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - CkshowEvil = BooleanOptionItem.Create(Id + 3, "CrewKillingRed", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - NBshowEvil = BooleanOptionItem.Create(Id + 4, "NBareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - NEshowEvil = BooleanOptionItem.Create(Id + 5, "NEareRed", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - NCshowEvil = BooleanOptionItem.Create(Id + 7, "NCareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - NAshowEvil = BooleanOptionItem.Create(Id + 8, "NAareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + CkshowEvil = BooleanOptionItem.Create(Id + 3, "Psychic_CrewKillingRed", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + NBshowEvil = BooleanOptionItem.Create(Id + 4, "Psychic_NBareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + NEshowEvil = BooleanOptionItem.Create(Id + 5, "Psychic_NEareRed", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + NCshowEvil = BooleanOptionItem.Create(Id + 7, "Psychic_NCareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + NAshowEvil = BooleanOptionItem.Create(Id + 8, "Psychic_NAareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + NKshowEvil = BooleanOptionItem.Create(Id + 9, "Psychic_NKareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); } public override void Init() { @@ -44,11 +46,10 @@ public override void Init() } public override void Add(byte playerId) { - _ = new LateTask(() => + if (!Fresh.GetBool()) { - if (!Fresh.GetBool()) - GetRedName(); - }, 2f, $"Get Red Name for {_state.PlayerId}"); + _ = new LateTask(GetRedName, 10f, $"Get Red Name For {_state.PlayerId}"); + } } private void SendRPC() { @@ -84,11 +85,13 @@ private void GetRedName() if (!_Player.IsAlive() || !AmongUsClient.Instance.AmHost) return; List BadListPc = Main.AllAlivePlayerControls.Where(x => - x.Is(Custom_Team.Impostor) && !x.Is(CustomRoles.Trickster) || !x.Is(CustomRoles.Admired) || x.IsAnySubRole(x => x.IsConverted()) || + (x.Is(Custom_Team.Impostor) && !x.Is(CustomRoles.Trickster) && !x.Is(CustomRoles.Admired)) || + x.IsAnySubRole(x => x.IsConverted()) || (x.GetCustomRole().IsCrewKiller() && CkshowEvil.GetBool()) || (x.GetCustomRole().IsNE() && NEshowEvil.GetBool()) || (x.GetCustomRole().IsNC() && NCshowEvil.GetBool()) || (x.GetCustomRole().IsNB() && NBshowEvil.GetBool()) || + (x.GetCustomRole().IsNK() && NKshowEvil.GetBool()) || (x.GetCustomRole().IsNA() && NAshowEvil.GetBool()) ).ToList(); From d2c624045bb40b2141e9f29f4c3c4f2ad42afe20 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 16:44:39 +0800 Subject: [PATCH 540/778] Change --- Resources/Lang/en_US.json | 1 - Roles/Crewmate/Altruist.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 0bc66e7b6e..7b0f000edf 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1717,7 +1717,6 @@ "AltruistReportMode": "Report", "Altruist_YouTriedReportRevivedDeadBody": "You Tried Report Revived Dead Body", "Altruist_DeadPlayerHasBeenRevived": "A Dead Player Has Been Revived!", - "AltruistReportButton": "Revive", "AltruistAbilityButton": "Change Mode", "SnatchesWin": "Snatches victory", diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 692f488f34..037a8790ac 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -168,6 +168,6 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) hud?.AbilityButton?.OverrideText(Translator.GetString("AltruistAbilityButton")); if (IsRevivingMode) - hud?.ReportButton?.OverrideText(Translator.GetString("AltruistReportButton")); + hud?.ReportButton?.OverrideText(Translator.GetString("AltruistReviveMode")); } } From 671c77f6000b85b88395b803e4887b88991da340 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 18:15:13 +0800 Subject: [PATCH 541/778] Change --- Modules/AntiBlackout.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index bb9823f467..a2673ddc16 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -45,7 +45,7 @@ public static bool CheckBlackOut() else Crewmates.Add(pc.PlayerId); } - var numAliveImpostors = Impostors.Count; + var numAliveImpostors = Impostors.Count; var numAliveCrewmates = Crewmates.Count; var numAliveNeutralKillers = NeutralKillers.Count; @@ -134,7 +134,7 @@ public static void SendGameData([CallerMemberName] string callerMethodName = "") logger.Info($"SendGameData is called from {callerMethodName}"); foreach (var playerinfo in GameData.Instance.AllPlayers) { - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + MessageWriter writer = MessageWriter.Get(); writer.StartMessage(5); //0x05 GameData writer.Write(AmongUsClient.Instance.GameId); { From 81aea334df55b44669da9b5c7145a43b7bbba0b0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 20:36:33 +0800 Subject: [PATCH 542/778] Alpha 12 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 60edc6c57d..f182aa9a4d 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0909.210.00110"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 11"; + public const string PluginVersion = "2024.0918.210.00120"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 12"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 11 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 12 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 80e5631d57a003bf35f9e3a24f39f3f8cf255e95 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 18 Sep 2024 22:08:04 +0800 Subject: [PATCH 543/778] Check player.walkingToVent in GetVentsFromClosest --- Modules/ExtendedPlayerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index a0f17a3de3..143758bd13 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -572,7 +572,7 @@ public static List GetVentsFromClosest(this PlayerControl player) // If player is inside a vent, we get the nearby vents that the player can snapto and insert them to the top of the list // Idk how to directly get the vent a player is in, so just assume the closet vent from the player is the vent that player is in // Not sure about whether inVent flags works 100% correct here. Maybe player is being kicked from a vent and inVent flags can return true there - if (player.inVent && vents[0] != null) + if ((player.walkingToVent || player.inVent) && vents[0] != null) { var nextvents = vents[0].NearbyVents.ToList(); nextvents.RemoveAll(v => v == null); From f950075492666072ca444b83b8be77169acc3c4c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:21:45 -0400 Subject: [PATCH 544/778] armageddon check --- Roles/Neutral/Terrorist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Terrorist.cs b/Roles/Neutral/Terrorist.cs index d807ab99ea..2e6081d8ea 100644 --- a/Roles/Neutral/Terrorist.cs +++ b/Roles/Neutral/Terrorist.cs @@ -66,7 +66,7 @@ private static void CheckTerroristWin(NetworkedPlayerInfo terrorist) { if (pc.Is(CustomRoles.Terrorist)) { - if (Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote) + if (Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote || Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Armageddon) { pc.SetDeathReason(PlayerState.DeathReason.etc); } From 1a0f5cbacb64f3d7eab287a07300af05b78babd1 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Wed, 18 Sep 2024 23:45:42 -0500 Subject: [PATCH 545/778] Better Among Us RPC support --- Modules/BAUPlayersData.cs | 79 +++++++++++++++++++++++++++++++++++ Modules/RPC.cs | 28 ++++++++++++- Patches/PlayerControlPatch.cs | 7 ++++ main.cs | 2 + 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 Modules/BAUPlayersData.cs diff --git a/Modules/BAUPlayersData.cs b/Modules/BAUPlayersData.cs new file mode 100644 index 0000000000..be1d894753 --- /dev/null +++ b/Modules/BAUPlayersData.cs @@ -0,0 +1,79 @@ + +namespace TOHE.Modules; + +public class BAUPlayersData +{ + private Dictionary _players = new Dictionary(); + + public string this[NetworkedPlayerInfo key] + { + get + { + CleanUpNullEntries(); + return _players.ContainsKey(key) ? _players[key] : null; + } + set + { + CleanUpNullEntries(); + if (key != null) + { + _players[key] = value; + } + } + } + + private void CleanUpNullEntries() + { + var keysToRemove = _players.Where(kvp => kvp.Key == null).Select(kvp => kvp.Key).ToList(); + foreach (var key in keysToRemove) + { + _players.Remove(key); + } + } + + public bool TryGetValue(NetworkedPlayerInfo key, out string value) + { + CleanUpNullEntries(); + return _players.TryGetValue(key, out value); + } + + public void Add(NetworkedPlayerInfo key, string value) + { + CleanUpNullEntries(); + if (key != null) + { + _players[key] = value; + } + } + + public bool Remove(NetworkedPlayerInfo key) + { + CleanUpNullEntries(); + return _players.Remove(key); + } + + public bool ContainsKey(NetworkedPlayerInfo key) + { + CleanUpNullEntries(); + return _players.ContainsKey(key); + } + + public Dictionary.KeyCollection Keys + { + get + { + CleanUpNullEntries(); + return _players.Keys; + } + } + + public Dictionary.ValueCollection Values + { + get + { + CleanUpNullEntries(); + return _players.Values; + } + } +} + diff --git a/Modules/RPC.cs b/Modules/RPC.cs index abbd44f770..ab55c6d489 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -74,6 +74,7 @@ enum CustomRPC : byte // 193/255 USED SetLoversPlayers, SetExecutionerTarget, RemoveExecutionerTarget, + BetterCheck, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! SendFireworkerState, SetCurrentDousingTarget, SetEvilTrackerTarget, @@ -144,7 +145,8 @@ or CustomRPC.Guess or CustomRPC.PresidentEnd or CustomRPC.SetSwapperVotes or CustomRPC.DumpLog - or CustomRPC.SetFriendCode; + or CustomRPC.SetFriendCode + or CustomRPC.BetterCheck; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) { var rpcType = (RpcCalls)callId; @@ -501,6 +503,30 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.RemoveExecutionerTarget: Executioner.ReceiveRPC(reader, SetTarget: false); break; + case CustomRPC.BetterCheck: // Better Among Us RPC + { + var SetBetterUser = reader.ReadBoolean(); // Used to set player as better user, boolean is used for a future for BAU later on. + var IsBetterHost = reader.ReadBoolean(); // Used to set the player as better host, this should never be flagged for a TOHE lobby, if it is it's a spoofed RPC + var Signature = reader.ReadString(); // Used to verify that the RPC isn't spoofed, only possible in BAU mod due to a special signature that can't really be replicated easily + var Version = reader.ReadString(); // Used to read players BAU version + + if (IsBetterHost) + { + EAC.Report(__instance, "BetterCheck set as BetterHost"); + EAC.HandleCheat(__instance, "BetterCheck set as BetterHost"); + break; + } + + if (string.IsNullOrEmpty(Signature) || string.IsNullOrEmpty(Version)) + { + EAC.Report(__instance, "BetterCheck invalid info"); + EAC.HandleCheat(__instance, "BetterCheck invalid info"); + break; + } + + Main.BAUPlayers[__instance.Data] = __instance.Data.Puid; + } + break; case CustomRPC.SendFireworkerState: Fireworker.ReceiveRPC(reader); break; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e308de736a..80afeafa1b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1236,6 +1236,13 @@ public static Task DoPostfix(PlayerControl __instance) __instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"{__instance.name}" : $"{ver.tag}\n{__instance?.name}"; else __instance.cosmetics.nameText.text = $"v{ver.version}\n{__instance?.name}"; } + if (Main.BAUPlayers.TryGetValue(__instance.Data, out var puid)) // Set name color for BAU users + { + if (puid == __instance.Data.Puid) + { + __instance.cosmetics.nameText.text = $"{__instance.name}"; + } + } else __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName; } if (GameStates.IsInGame) diff --git a/main.cs b/main.cs index f182aa9a4d..cee0de1e8a 100644 --- a/main.cs +++ b/main.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Text; using System.Text.Json; +using TOHE.Modules; using TOHE.Roles.AddOns; using TOHE.Roles.Core; using TOHE.Roles.Double; @@ -104,6 +105,7 @@ public class Main : BasePlugin public static ConfigEntry AutoRehost { get; private set; } public static Dictionary playerVersion = []; + public static BAUPlayersData BAUPlayers = new(); //Preset Name Options public static ConfigEntry Preset1 { get; private set; } public static ConfigEntry Preset2 { get; private set; } From 822fa404937aa041cee85de0971b11412057b3e8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 16:38:24 +0800 Subject: [PATCH 546/778] Fix Multiple Player ID & FFA kill button now work & modded client can't sabotage --- GameModes/FFAManager.cs | 10 +++++----- Modules/GuessManager.cs | 6 +++--- Patches/HudPatch.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index 3e73662587..c9243b7e9b 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -99,16 +99,16 @@ public static void Init() FFAEnterVentTime = []; } - _ = new LateTask( ()=> + _ = new LateTask(()=> { RoundTime = FFA_GameTime.GetInt() + 8; var now = Utils.GetTimeStamp() + 8; foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - KBScore.TryAdd(pc.PlayerId, 0); - if (FFA_DisableVentingWhenKCDIsUp.GetBool()) FFALastKill.TryAdd(pc.PlayerId, now); + KBScore[pc.PlayerId] = 0; + if (FFA_DisableVentingWhenKCDIsUp.GetBool()) FFALastKill[pc.PlayerId] = now; } - }, 15f, "Set Chat Visible for Everyone"); + }, 25f, "Set Chat Visible for Everyone"); } private static void SendRPCSyncFFAPlayer(byte playerId) { @@ -151,7 +151,7 @@ public static void GetNameNotify(PlayerControl player, ref string name) public static string GetDisplayScore(byte playerId) { int rank = GetRankOfScore(playerId); - string score = KBScore.TryGetValue(playerId, out var s) ? $"{s}" : "Invalid"; + string score = KBScore.TryGetValue(playerId, out var s) ? $"{s}" : "0"; string text = string.Format(GetString("FFADisplayScore"), rank.ToString(), score); Color color = Utils.GetRoleColor(CustomRoles.Killer); return Utils.ColorString(color, text); diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index c56a7676e4..1cbdb429cf 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -1083,14 +1083,14 @@ public static void CreateIDLabels(MeetingHud __instance) { if (pva == null) continue; var levelDisplay = pva.transform.FindChild("PlayerLevel").gameObject; - var panel = UnityEngine.Object.Instantiate(levelDisplay); + var panel = UnityEngine.Object.Instantiate(levelDisplay, pva.transform, true); panel.gameObject.name = "PlayerIDLabel"; var panelTransform = panel.transform; - panelTransform.transform.SetParent(levelDisplay.transform); - panelTransform.localPosition = new(0f, -0.90f, levelDisplay.transform.localPosition.z); var background = panel.GetComponent(); background.color = Palette.Purple; background.sortingOrder = max - 1; + panelTransform.SetAsFirstSibling(); + panelTransform.localPosition = new(-1.21f, -0.15f, 0f); var levelLabel = panelTransform.FindChild("LevelLabel").GetComponents()[0]; levelLabel.DestroyTranslator(); levelLabel.text = "ID"; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 65017e8fbf..8539314dba 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -123,7 +123,7 @@ public static void Postfix(HudManager __instance) player.Data.Role.CanVent = player.CanUseVents(); // Sometimes sabotage button was visible for non-host modded clients - if (!AmongUsClient.Instance.AmHost && player.CanUseSabotage()) + if (!AmongUsClient.Instance.AmHost && !player.CanUseSabotage()) __instance.SabotageButton.Hide(); } else From abb9d8a557b410c0fec3e89600a3a87a1dbf1c49 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 16:42:23 +0800 Subject: [PATCH 547/778] Hotfix 1 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index f182aa9a4d..e83d2d19f4 100644 --- a/main.cs +++ b/main.cs @@ -41,12 +41,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0918.210.00120"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 12"; + public const string PluginVersion = "2024.0919.210.00121"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 12 Hotfix 1"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 12 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 12 Hotfix 1 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 8627bb206d2d2e5d77c52b080006c65d3c1b4610 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 16:50:35 +0800 Subject: [PATCH 548/778] Some changes --- GameModes/FFAManager.cs | 14 +++++++++++++- Patches/IntroPatch.cs | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index c9243b7e9b..2decb6f8d5 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -108,7 +108,19 @@ public static void Init() KBScore[pc.PlayerId] = 0; if (FFA_DisableVentingWhenKCDIsUp.GetBool()) FFALastKill[pc.PlayerId] = now; } - }, 25f, "Set Chat Visible for Everyone"); + }, 18f, "Add data after game start"); + } + public static void SetData() + { + if (Options.CurrentGameMode != CustomGameMode.FFA) return; + + RoundTime = FFA_GameTime.GetInt() + 8; + var now = Utils.GetTimeStamp() + 8; + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + KBScore[pc.PlayerId] = 0; + if (FFA_DisableVentingWhenKCDIsUp.GetBool()) FFALastKill[pc.PlayerId] = now; + } } private static void SendRPCSyncFFAPlayer(byte playerId) { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 9b3f7fddf3..6d8b9e5915 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -265,6 +265,8 @@ public static void Prefix() GameStates.InGame = true; RPC.RpcVersionCheck(); + FFAManager.SetData(); + if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) { RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch From 4af9d199757fd0063c4900807077b60abe83c907 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 17:43:33 +0800 Subject: [PATCH 549/778] Task After Remove Add-ons & Add strings --- Modules/CustomRolesHelper.cs | 2 +- Modules/ExtendedPlayerControl.cs | 41 +++++++++++++++++++++++++++++++ Modules/GameState.cs | 25 +++++-------------- Patches/onGameStartedPatch.cs | 4 +++ Resources/Lang/en_US.json | 11 +++++++++ Resources/roleColor.json | 2 +- Roles/AddOns/Common/Prohibited.cs | 29 +++++++++++++--------- 7 files changed, 81 insertions(+), 33 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index a73e369ba3..93f4ee6dc6 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -963,7 +963,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Prohibited: - if (!pc.CanUseVents()) + if (Prohibited.GetCountBlokedVents() <= 0 || !pc.CanUseVents()) return false; if (pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool()) return false; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 2f5cc36e57..f7194edac9 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1166,10 +1166,51 @@ public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, break; case CustomRoles.Prohibited: Prohibited.Remove(Killed.PlayerId); + Killed?.RpcSetVentInteraction(); Prohibited.Add(target.PlayerId); + target?.RpcSetVentInteraction(); break; } } + public static void TaskAfterRemoveAddons(this PlayerControl target, CustomRoles Addon, bool syncSettings = false) + { + var sync = syncSettings; + + switch (Addon) + { + case CustomRoles.Tired: + Tired.Remove(target.PlayerId); + break; + case CustomRoles.Flash: + Flash.SetSpeed(target.PlayerId, true); + sync = true; + break; + case CustomRoles.Sloth: + Sloth.SetSpeed(target.PlayerId, true); + sync = true; + break; + case CustomRoles.Lucky: + Lucky.Remove(target.PlayerId); + break; + case CustomRoles.Clumsy: + Clumsy.Remove(target.PlayerId); + break; + case CustomRoles.Statue: + Statue.Remove(target.PlayerId); + break; + case CustomRoles.Glow: + Glow.Remove(target.PlayerId); + break; + case CustomRoles.Rebirth: + Rebirth.Remove(target.PlayerId); + break; + case CustomRoles.Prohibited: + Prohibited.Remove(target.PlayerId); + target?.RpcSetVentInteraction(); + break; + } + if (sync) Utils.MarkEveryoneDirtySettings(); + } public static bool RpcCheckAndMurder(this PlayerControl killer, PlayerControl target, bool check = false) { var caller = new System.Diagnostics.StackFrame(1, false); diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 17571c2d72..34750f7d22 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -114,36 +114,21 @@ public void SetMainRole(CustomRoles role) } } - public void SetSubRole(CustomRoles role, bool AllReplace = false, PlayerControl pc = null) + public void SetSubRole(CustomRoles role, PlayerControl pc = null) { if (role == CustomRoles.Cleansed) { if (pc != null) countTypes = pc.GetCustomRole().GetCountTypes(); - AllReplace = true; - } - if (AllReplace) - { - var sync = false; + foreach (var subRole in SubRoles.ToArray()) { - if (pc.Is(CustomRoles.Flash)) - { - Flash.SetSpeed(pc.PlayerId, true); - sync = true; - } - if (pc.Is(CustomRoles.Sloth)) - { - Sloth.SetSpeed(pc.PlayerId, true); - sync = true; - } - SubRoles.Remove(subRole); - - if (sync) MarkEveryoneDirtySettings(); + RemoveSubRole(subRole); } } if (!SubRoles.Contains(role)) SubRoles.Add(role); + if (role.IsConverted()) { SubRoles.RemoveAll(AddON => AddON != role && AddON.IsConverted()); @@ -225,6 +210,8 @@ public void RemoveSubRole(CustomRoles role) if (SubRoles.Contains(role)) SubRoles.Remove(role); + PlayerId.GetPlayer()?.TaskAfterRemoveAddons(role); + if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveSubRole, SendOption.Reliable); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index f57042a2d5..16f59c02ee 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -235,6 +235,7 @@ public static void Postfix(AmongUsClient __instance) Rebirth.Init(); Evader.Init(); Radar.Init(); + Prohibited.Init(); //FFA FFAManager.Init(); @@ -548,6 +549,9 @@ public static System.Collections.IEnumerator AssignRoles() case CustomRoles.Spurt: Spurt.Add(); break; + case CustomRoles.Prohibited: + Prohibited.Add(pc.PlayerId); + break; default: break; } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7b0f000edf..18367217d3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -397,6 +397,7 @@ "DollMaster": "Dollmaster", "DoubleAgent": "Double Agent", "Sloth": "Sloth", + "Prohibited": "Prohibited", "BracketAddons": "Add Brackets To Add-ons", "EngineerTOHEInfo": "Use the vents to catch the Impostors", "ScientistTOHEInfo": "Access portable vitals from anywhere", @@ -706,6 +707,7 @@ "DollMasterInfo": "Take control of players actions!", "DoubleAgentInfo": "Plant bombs on players in meetings", "SlothInfo": "You're slower", + "ProhibitedInfo": "Certain vents are blocked", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", @@ -1015,6 +1017,8 @@ "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", "DoubleAgentInfoLong": "(Impostor):\nAs the Double Agent, you cannot access the kill button. However, you can vote for someone in a meeting to pass a bomb onto them, which can only be done one player at a time. Once the meeting has finished, the bomb will activate and explode in a set amount of time.\nNote: when you pass the bomb onto someone in a meeting, you can vote afterward.\n\nAdditionally depending on settings the Double Agent can diffuse Bastion and Agitator bombs when venting.\n\nThe Double Agent can change roles when they are the Last Imposter, depending on the settings the role can be a Admired Impostor, Trickster, Traitor, or stay as the Double Agent.", "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", + "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have some vents blocked for use.", + "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", @@ -1575,6 +1579,13 @@ "PyroDouseCooldown": "Douse cooldown", "PyroBurnCooldown": "Kill cooldown after killing a doused player", + "Prohibited_CountBlockedVentsInSkeld": "Count Blocked Vents In The Skeld", + "Prohibited_CountBlockedVentsInMira": "Count Blocked Vents In MIRA HQ", + "Prohibited_CountBlockedVentsInPolus": "Count Blocked Vents In Polus", + "Prohibited_CountBlockedVentsInDleks": "Count Blocked Vents In Dleks", + "Prohibited_CountBlockedVentsInAirship": "Count Blocked Vents In Airship", + "Prohibited_CountBlockedVentsInFungle": "Count Blocked Vents In The Fungle", + "UndertakerFreezeDuration": "Freeze Duration", "NameDisplayAddons": "Display Add-Ons next to the role name", "YourAddon": "Your Add-ons:", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index d49358bfb3..ab4ad4a860 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -106,7 +106,7 @@ "Pelican": "#34C849", "Revolutionist": "#ba4d06", "Hater": "#414b66", - "Prohibited": "#000295", + "Prohibited": "#6a8f8f", "Demon": "#68bc71", "Stalker": "#483d8b", "Workaholic": "#008b8b", diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index 1ba8119d62..8d5afe7731 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -23,7 +23,7 @@ public void SetupCustomOption() CountBlockedVentsInSkeld = IntegerOptionItem.Create(Id + 10, "Prohibited_CountBlockedVentsInSkeld", new(0, 14, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInMira = IntegerOptionItem.Create(Id + 11, "Prohibited_CountBlockedVentsInMira", new(0, 11, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInPolus = IntegerOptionItem.Create(Id + 12, "Prohibited_CountBlockedVentsInPolus", new(0, 12, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); - CountBlockedVentsInDleks = IntegerOptionItem.Create(Id + 13, "Prohibited_CountBlockedVentsInDleks", new(0, 14, 1), 0, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); + CountBlockedVentsInDleks = IntegerOptionItem.Create(Id + 13, "Prohibited_CountBlockedVentsInDleks", new(0, 14, 1), 2, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInAirship = IntegerOptionItem.Create(Id + 14, "Prohibited_CountBlockedVentsInAirship", new(0, 12, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInFungle = IntegerOptionItem.Create(Id + 15, "Prohibited_CountBlockedVentsInFungle", new(0, 10, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); } @@ -34,20 +34,12 @@ public static void Init() } public static void Add(byte playerId) { - var coutBlokedVents = Utils.GetActiveMapName() switch - { - MapNames.Skeld => CountBlockedVentsInSkeld.GetInt(), - MapNames.Mira => CountBlockedVentsInMira.GetInt(), - MapNames.Polus => CountBlockedVentsInPolus.GetInt(), - MapNames.Dleks => CountBlockedVentsInDleks.GetInt(), - MapNames.Airship => CountBlockedVentsInAirship.GetInt(), - MapNames.Fungle => CountBlockedVentsInFungle.GetInt(), - _ => 0 - }; + var coutBlokedVents = GetCountBlokedVents(); if (coutBlokedVents <= 0) return; var allVents = ShipStatus.Instance.AllVents.ToList(); - var allVentsCount = allVents.Count; + + RememberBlokcedVents[playerId] = []; for (int i = 0; i < coutBlokedVents; i++) { @@ -67,4 +59,17 @@ public static void Remove(byte playerId) } RememberBlokcedVents.Remove(playerId); } + public static int GetCountBlokedVents() + { + return Utils.GetActiveMapName() switch + { + MapNames.Skeld => CountBlockedVentsInSkeld.GetInt(), + MapNames.Mira => CountBlockedVentsInMira.GetInt(), + MapNames.Polus => CountBlockedVentsInPolus.GetInt(), + MapNames.Dleks => CountBlockedVentsInDleks.GetInt(), + MapNames.Airship => CountBlockedVentsInAirship.GetInt(), + MapNames.Fungle => CountBlockedVentsInFungle.GetInt(), + _ => 0 + }; + } } From 45db085daa10e696baf0d13a0cac53074f1e0aed Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 17:57:18 +0800 Subject: [PATCH 550/778] Setting: Override Blocked Vents After Meeting --- Modules/Utils.cs | 5 ++++ Resources/Lang/en_US.json | 1 + Roles/AddOns/Common/Prohibited.cs | 39 ++++++++++++++++++++----------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ce55905e08..0e3f2ffa53 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2307,6 +2307,11 @@ public static void AfterMeetingTasks() foreach (var player in Main.AllAlivePlayerControls) { player.SetKillTimer(); + + if (player.Is(CustomRoles.Prohibited)) + { + Prohibited.AfterMeetingTasks(player.PlayerId); + } } if (LateExileTask.Any()) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 18367217d3..7969586038 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1579,6 +1579,7 @@ "PyroDouseCooldown": "Douse cooldown", "PyroBurnCooldown": "Kill cooldown after killing a doused player", + "Prohibited_OverrideBlockedVentsAfterMeeting": "Override Blocked Vents After Meeting", "Prohibited_CountBlockedVentsInSkeld": "Count Blocked Vents In The Skeld", "Prohibited_CountBlockedVentsInMira": "Count Blocked Vents In MIRA HQ", "Prohibited_CountBlockedVentsInPolus": "Count Blocked Vents In Polus", diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index 8d5afe7731..3d9c466189 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -14,12 +14,14 @@ public class Prohibited : IAddon private static OptionItem CountBlockedVentsInDleks; private static OptionItem CountBlockedVentsInAirship; private static OptionItem CountBlockedVentsInFungle; + private static OptionItem OverrideBlockedVentsAfterMeeting; private static readonly Dictionary> RememberBlokcedVents = []; public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Prohibited, canSetNum: true, teamSpawnOptions: true); + OverrideBlockedVentsAfterMeeting = BooleanOptionItem.Create(Id + 16, "Prohibited_OverrideBlockedVentsAfterMeeting", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInSkeld = IntegerOptionItem.Create(Id + 10, "Prohibited_CountBlockedVentsInSkeld", new(0, 14, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInMira = IntegerOptionItem.Create(Id + 11, "Prohibited_CountBlockedVentsInMira", new(0, 11, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); CountBlockedVentsInPolus = IntegerOptionItem.Create(Id + 12, "Prohibited_CountBlockedVentsInPolus", new(0, 12, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); @@ -34,20 +36,7 @@ public static void Init() } public static void Add(byte playerId) { - var coutBlokedVents = GetCountBlokedVents(); - - if (coutBlokedVents <= 0) return; - var allVents = ShipStatus.Instance.AllVents.ToList(); - - RememberBlokcedVents[playerId] = []; - - for (int i = 0; i < coutBlokedVents; i++) - { - var vent = allVents.RandomElement(); - RememberBlokcedVents[playerId].Add(vent.Id); - CustomRoleManager.BlockedVentsList[playerId].Add(vent.Id); - allVents.Remove(vent); - } + SetBlockedVents(playerId); } public static void Remove(byte playerId) { @@ -72,4 +61,26 @@ public static int GetCountBlokedVents() _ => 0 }; } + public static void SetBlockedVents(byte playerId) + { + var coutBlokedVents = GetCountBlokedVents(); + + if (coutBlokedVents <= 0) return; + var allVents = ShipStatus.Instance.AllVents.ToList(); + + RememberBlokcedVents[playerId] = []; + + for (int i = 0; i < coutBlokedVents; i++) + { + var vent = allVents.RandomElement(); + RememberBlokcedVents[playerId].Add(vent.Id); + CustomRoleManager.BlockedVentsList[playerId].Add(vent.Id); + allVents.Remove(vent); + } + } + public static void AfterMeetingTasks(byte playerId) + { + if (OverrideBlockedVentsAfterMeeting.GetBool()) + SetBlockedVents(playerId); + } } From de24540e8c2d0598d2d55882859e482677cd7b33 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 17:58:54 +0800 Subject: [PATCH 551/778] Remove --- Modules/ExtendedPlayerControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f7194edac9..3acc3c65a2 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1172,10 +1172,9 @@ public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, break; } } - public static void TaskAfterRemoveAddons(this PlayerControl target, CustomRoles Addon, bool syncSettings = false) + public static void TaskAfterRemoveAddons(this PlayerControl target, CustomRoles Addon) { - var sync = syncSettings; - + var sync = false; switch (Addon) { case CustomRoles.Tired: From f0fe8baea991ccb6261bb2b1be5801f2a5229f44 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 21:56:00 +0800 Subject: [PATCH 552/778] IAddon.Init() IAddon.Add() IAddon.Remove() --- Modules/ExtendedPlayerControl.cs | 81 +---------- Modules/GameState.cs | 18 ++- Modules/RPC.cs | 55 ++------ Patches/MeetingHudPatch.cs | 20 +-- Patches/PlayerControlPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 98 ++----------- Roles/AddOns/Common/Antidote.cs | 20 ++- Roles/AddOns/Common/Autopsy.cs | 6 + Roles/AddOns/Common/Avanger.cs | 6 + Roles/AddOns/Common/Aware.cs | 29 ++-- Roles/AddOns/Common/Bait.cs | 33 ++++- Roles/AddOns/Common/Beartrap.cs | 6 + Roles/AddOns/Common/Bewilder.cs | 16 ++- Roles/AddOns/Common/Burst.cs | 19 ++- Roles/AddOns/Common/Cyber.cs | 11 +- Roles/AddOns/Common/Diseased.cs | 20 ++- Roles/AddOns/Common/DoubleShot.cs | 10 +- Roles/AddOns/Common/Egoist.cs | 7 + Roles/AddOns/Common/Evader.cs | 19 +-- Roles/AddOns/Common/Flash.cs | 18 ++- Roles/AddOns/Common/Fool.cs | 15 +- Roles/AddOns/Common/Fragile.cs | 9 +- Roles/AddOns/Common/Glow.cs | 11 +- Roles/AddOns/Common/Gravestone.cs | 6 + Roles/AddOns/Common/Guesser.cs | 6 + Roles/AddOns/Common/Influenced.cs | 11 +- Roles/AddOns/Common/Loyal.cs | 6 + Roles/AddOns/Common/Lucky.cs | 12 +- Roles/AddOns/Common/Mundane.cs | 9 +- Roles/AddOns/Common/Necroview.cs | 6 + Roles/AddOns/Common/Oblivious.cs | 6 + Roles/AddOns/Common/Oiiai.cs | 23 ++- Roles/AddOns/Common/Onbound.cs | 6 + Roles/AddOns/Common/Overlocked.cs | 6 + Roles/AddOns/Common/Paranoia.cs | 6 + Roles/AddOns/Common/Prohibited.cs | 8 +- Roles/AddOns/Common/Radar.cs | 9 +- Roles/AddOns/Common/Rainbow.cs | 23 ++- Roles/AddOns/Common/Reach.cs | 6 + Roles/AddOns/Common/Rebirth.cs | 10 +- Roles/AddOns/Common/Rebound.cs | 6 + Roles/AddOns/Common/Seer.cs | 6 + Roles/AddOns/Common/Silent.cs | 6 + Roles/AddOns/Common/Sleuth.cs | 10 +- Roles/AddOns/Common/Sloth.cs | 18 ++- Roles/AddOns/Common/Spurt.cs | 196 +++++++++++++------------- Roles/AddOns/Common/Statue.cs | 40 +++--- Roles/AddOns/Common/Stubborn.cs | 6 + Roles/AddOns/Common/Susceptible.cs | 7 +- Roles/AddOns/Common/Tiebreaker.cs | 11 +- Roles/AddOns/Common/Tired.cs | 26 ++-- Roles/AddOns/Common/Unlucky.cs | 6 + Roles/AddOns/Common/Unreportable.cs | 6 + Roles/AddOns/Common/Voidballot.cs | 6 + Roles/AddOns/Common/Watcher.cs | 7 +- Roles/AddOns/Common/Youtuber.cs | 6 + Roles/AddOns/Crewmate/Bloodthirst.cs | 7 +- Roles/AddOns/Crewmate/Ghoul.cs | 17 ++- Roles/AddOns/Crewmate/Hurried.cs | 7 +- Roles/AddOns/Crewmate/Lazy.cs | 7 +- Roles/AddOns/Crewmate/Nimble.cs | 6 + Roles/AddOns/Crewmate/Rascal.cs | 7 +- Roles/AddOns/Crewmate/Torch.cs | 7 +- Roles/AddOns/Crewmate/Workhorse.cs | 17 ++- Roles/AddOns/IAddon.cs | 3 + Roles/AddOns/Impostor/Circumvent.cs | 7 +- Roles/AddOns/Impostor/Clumsy.cs | 14 +- Roles/AddOns/Impostor/LastImpostor.cs | 11 +- Roles/AddOns/Impostor/Mare.cs | 15 +- Roles/AddOns/Impostor/Mimic.cs | 7 +- Roles/AddOns/Impostor/Stealer.cs | 6 + Roles/AddOns/Impostor/Swift.cs | 6 + Roles/AddOns/Impostor/Tricky.cs | 12 +- Roles/Core/CustomRoleManager.cs | 4 +- Roles/Crewmate/Inspector.cs | 6 +- Roles/Crewmate/Merchant.cs | 4 +- Roles/Crewmate/Overseer.cs | 1 + Roles/Impostor/Dazzler.cs | 2 +- Roles/Neutral/Bandit.cs | 7 +- 79 files changed, 704 insertions(+), 527 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 3acc3c65a2..f0c3edb032 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1129,86 +1129,17 @@ CustomRoles.Infected and not CustomRoles.Contagious; } - public static void AddInSwitchAddons(PlayerControl Killed, PlayerControl target, CustomRoles Addon = CustomRoles.NotAssigned, CustomRoles? IsAddon = CustomRoles.NotAssigned) + public static void AddInSwitchAddons(this PlayerControl Killed, PlayerControl target, CustomRoles Addon = CustomRoles.NotAssigned, CustomRoles? IsAddon = CustomRoles.NotAssigned) { if (Addon == CustomRoles.NotAssigned) { Addon = IsAddon ?? CustomRoles.NotAssigned; } - switch (Addon) - { - case CustomRoles.Tired: - Tired.Remove(Killed.PlayerId); - Tired.Add(target.PlayerId); - break; - case CustomRoles.Bewilder: - Bewilder.Add(); - break; - case CustomRoles.Lucky: - Lucky.Remove(Killed.PlayerId); - Lucky.Add(target.PlayerId); - break; - case CustomRoles.Clumsy: - Clumsy.Remove(Killed.PlayerId); - Clumsy.Add(target.PlayerId); - break; - case CustomRoles.Statue: - Statue.Remove(Killed.PlayerId); - Statue.Add(target.PlayerId); - break; - case CustomRoles.Glow: - Glow.Remove(Killed.PlayerId); - Glow.Add(target.PlayerId); - break; - case CustomRoles.Rebirth: - Rebirth.Remove(Killed.PlayerId); - Rebirth.Add(target.PlayerId); - break; - case CustomRoles.Prohibited: - Prohibited.Remove(Killed.PlayerId); - Killed?.RpcSetVentInteraction(); - Prohibited.Add(target.PlayerId); - target?.RpcSetVentInteraction(); - break; - } - } - public static void TaskAfterRemoveAddons(this PlayerControl target, CustomRoles Addon) - { - var sync = false; - switch (Addon) - { - case CustomRoles.Tired: - Tired.Remove(target.PlayerId); - break; - case CustomRoles.Flash: - Flash.SetSpeed(target.PlayerId, true); - sync = true; - break; - case CustomRoles.Sloth: - Sloth.SetSpeed(target.PlayerId, true); - sync = true; - break; - case CustomRoles.Lucky: - Lucky.Remove(target.PlayerId); - break; - case CustomRoles.Clumsy: - Clumsy.Remove(target.PlayerId); - break; - case CustomRoles.Statue: - Statue.Remove(target.PlayerId); - break; - case CustomRoles.Glow: - Glow.Remove(target.PlayerId); - break; - case CustomRoles.Rebirth: - Rebirth.Remove(target.PlayerId); - break; - case CustomRoles.Prohibited: - Prohibited.Remove(target.PlayerId); - target?.RpcSetVentInteraction(); - break; - } - if (sync) Utils.MarkEveryoneDirtySettings(); + if (CustomRoleManager.AddonClasses.TryGetValue(Addon, out var IAddon)) + { + IAddon?.Remove(Killed.PlayerId); + IAddon?.Add(target.PlayerId, false); + } } public static bool RpcCheckAndMurder(this PlayerControl killer, PlayerControl target, bool check = false) { diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 34750f7d22..feaf5feeb2 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -5,7 +5,6 @@ using TOHE.Roles.Core; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Impostor; using static TOHE.Utils; using Hazel; @@ -205,18 +204,25 @@ public void SetSubRole(CustomRoles role, PlayerControl pc = null) break; } } - public void RemoveSubRole(CustomRoles role) + public void RemoveSubRole(CustomRoles addOn) { - if (SubRoles.Contains(role)) - SubRoles.Remove(role); + if (SubRoles.Contains(addOn)) + SubRoles.Remove(addOn); - PlayerId.GetPlayer()?.TaskAfterRemoveAddons(role); + if (CustomRoleManager.AddonClasses.TryGetValue(addOn, out var IAddon)) + { + var target = PlayerId.GetPlayer(); + if (target != null) + { + IAddon?.Remove(target.PlayerId); + } + } if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveSubRole, SendOption.Reliable); writer.Write(PlayerId); - writer.WritePacked((int)role); + writer.WritePacked((int)addOn); writer.EndMessage(); } diff --git a/Modules/RPC.cs b/Modules/RPC.cs index abbd44f770..9f9a70dfcd 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -106,7 +106,6 @@ enum CustomRPC : byte // 193/255 USED SetInvestgatorLimit, SetOverseerRevealedPlayer, SetOverseerTimer, - SpurtSync, SyncVultureBodyAmount, SpyRedNameSync, SpyRedNameRemove, @@ -636,9 +635,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.NemesisRevenge: Nemesis.ReceiveRPC_Custom(reader, __instance); break; - case CustomRPC.SpurtSync: - Spurt.RecieveRPC(reader); - break; case CustomRPC.RetributionistRevenge: Retributionist.ReceiveRPC_Custom(reader, __instance); break; @@ -970,47 +966,18 @@ public static void SetCustomRole(byte targetId, CustomRoles role) else if (role >= CustomRoles.NotAssigned) //500:NoSubRole 501~:SubRole { Main.PlayerStates[targetId].SetSubRole(role); - } - - switch (role) - { - case CustomRoles.LastImpostor: - LastImpostor.Add(targetId); - break; - case CustomRoles.Aware: - Aware.Add(targetId); - break; - case CustomRoles.Glow: - Glow.Add(targetId); - break; - case CustomRoles.Workhorse: - Workhorse.Add(targetId); - break; - case CustomRoles.Diseased: - Diseased.Add(); - break; - case CustomRoles.Antidote: - Antidote.Add(); - break; - case CustomRoles.Burst: - Burst.Add(); - break; - case CustomRoles.Fool: - Fool.Add(); - break; - case CustomRoles.Ghoul: - Ghoul.Add(); - break; - case CustomRoles.Rainbow: - Rainbow.Add(); - break; - case CustomRoles.Statue: - Statue.Add(targetId); - break; - case CustomRoles.Evader: - Evader.Add(targetId); - break; + + if (CustomRoleManager.AddonClasses.TryGetValue(role, out var IAddon)) + { + IAddon?.Add(targetId); + } + switch (role) + { + case CustomRoles.LastImpostor: + LastImpostor.AddMidGame(targetId); + break; + } } if (!AmongUsClient.Instance.IsGameOver) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 20441e316d..3036d7ba03 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -891,24 +891,10 @@ public static void NotifyRoleSkillOnMeetingStart() // Madmate spawn mode: Self vote if (Madmate.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.GetCount() > 0) AddMsg(string.Format(GetString("Message.MadmateSelfVoteModeNotify"), GetString("MadmateSpawnMode.SelfVote"))); - + //Bait Notify - if (MeetingStates.FirstMeeting && CustomRoles.Bait.RoleExist() && Bait.BaitNotification.GetBool()) - { - foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Bait)).ToArray()) - { - Bait.BaitAlive.Add(pc.PlayerId); - } - List baitAliveList = []; - foreach (var whId in Bait.BaitAlive.ToArray()) - { - PlayerControl whpc = GetPlayerById(whId); - if (whpc == null) continue; - baitAliveList.Add(whpc.GetRealName()); - } - string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; - AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, ColorString(GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); - } + Bait.SendNotify(); + // Apocalypse Notify, thanks tommy var transformRoles = new CustomRoles[] { CustomRoles.Pestilence, CustomRoles.War, CustomRoles.Famine, CustomRoles.Death }; foreach (var role in transformRoles) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e308de736a..6acb88a91c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1164,7 +1164,7 @@ public static Task DoPostfix(PlayerControl __instance) if (CustomRoles.Lovers.IsEnable()) LoversSuicide(); - if (Rainbow.isEnabled) + if (Rainbow.IsEnabled) Rainbow.OnFixedUpdate(); if (Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 16f59c02ee..3386158701 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -201,41 +201,22 @@ public static void Postfix(AmongUsClient __instance) Main.RefixCooldownDelay = 0; } - // Initialize all custom roles + // Initialize all roles foreach (var role in EnumHelper.GetAllValues().Where(role => role < CustomRoles.NotAssigned).ToArray()) { var RoleClass = CustomRoleManager.GetStaticRoleClass(role); RoleClass?.OnInit(); } - LastImpostor.Init(); + // Initialize all add-ons + foreach (var addOn in CustomRoleManager.AddonClasses.Values.ToArray()) + { + addOn?.Init(); + } + TargetArrow.Init(); LocateArrow.Init(); DoubleTrigger.Init(); - Workhorse.Init(); - Diseased.Init(); - Clumsy.Init(); - Aware.Init(); - Glow.Init(); - Sleuth.Init(); - Bait.Init(); - Antidote.Init(); - Fool.Init(); - Burst.Init(); - DoubleShot.Init(); - Lucky.Init(); - Bewilder.Init(); - //ChiefOfPolice.Init(); - Cyber.Init(); - Oiiai.Init(); - Tired.Init(); - Statue.Init(); - Ghoul.Init(); - Rainbow.Init(); - Rebirth.Init(); - Evader.Init(); - Radar.Init(); - Prohibited.Init(); //FFA FFAManager.Init(); @@ -491,70 +472,9 @@ public static System.Collections.IEnumerator AssignRoles() // if based role is Shapeshifter if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - foreach (var subRole in pc.GetCustomSubRoles().ToArray()) + foreach (var subRole in CustomRoleManager.AddonClasses.Values.ToArray()) { - switch (subRole) - { - case CustomRoles.Aware: - Aware.Add(pc.PlayerId); - break; - case CustomRoles.Glow: - Glow.Add(pc.PlayerId); - break; - case CustomRoles.Oiiai: - Oiiai.Add(pc.PlayerId); - break; - case CustomRoles.Tired: - Tired.Add(pc.PlayerId); - break; - case CustomRoles.Rainbow: - Rainbow.Add(); - break; - case CustomRoles.Statue: - Statue.Add(pc.PlayerId); - break; - case CustomRoles.Ghoul: - Ghoul.Add(); - break; - case CustomRoles.Diseased: - Diseased.Add(); - break; - case CustomRoles.Antidote: - Antidote.Add(); - break; - case CustomRoles.Burst: - Burst.Add(); - break; - case CustomRoles.Bewilder: - Bewilder.Add(); - break; - case CustomRoles.Lucky: - Lucky.Add(pc.PlayerId); - break; - case CustomRoles.Clumsy: - Clumsy.Add(pc.PlayerId); - break; - case CustomRoles.Fool: - Fool.Add(); - break; - case CustomRoles.Bloodthirst: - Bloodthirst.Add(); - break; - case CustomRoles.Rebirth: - Rebirth.Add(pc.PlayerId); - break; - case CustomRoles.Evader: - Evader.Add(pc.PlayerId); - break; - case CustomRoles.Spurt: - Spurt.Add(); - break; - case CustomRoles.Prohibited: - Prohibited.Add(pc.PlayerId); - break; - default: - break; - } + subRole?.Add(pc.PlayerId); } } diff --git a/Roles/AddOns/Common/Antidote.cs b/Roles/AddOns/Common/Antidote.cs index 731dd38844..129eb40811 100644 --- a/Roles/AddOns/Common/Antidote.cs +++ b/Roles/AddOns/Common/Antidote.cs @@ -14,7 +14,8 @@ public class Antidote : IAddon private static OptionItem AntidoteCDOpt; private static OptionItem AntidoteCDReset; - private static Dictionary KilledAntidote = []; + private static readonly HashSet playerList = []; + private static readonly Dictionary KilledAntidote = []; public void SetupCustomOption() { @@ -24,15 +25,24 @@ public void SetupCustomOption() AntidoteCDReset = BooleanOptionItem.Create(Id + 14, "AntidoteCDReset", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); } - public static void Init() + public void Init() { - KilledAntidote = []; IsEnable = false; + playerList.Clear(); + KilledAntidote.Clear(); } - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) { + playerList.Add(playerId); IsEnable = true; } + public void Remove(byte playerId) + { + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnable = false; + } public static void ReduceKCD(PlayerControl player) { @@ -56,7 +66,7 @@ public static void AfterMeetingTasks() if (kapc == null) continue; kapc.ResetKillCooldown(); } - KilledAntidote = []; + KilledAntidote.Clear(); } } diff --git a/Roles/AddOns/Common/Autopsy.cs b/Roles/AddOns/Common/Autopsy.cs index 2aada77a1b..affe2375f6 100644 --- a/Roles/AddOns/Common/Autopsy.cs +++ b/Roles/AddOns/Common/Autopsy.cs @@ -10,4 +10,10 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Autopsy, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Avanger.cs b/Roles/AddOns/Common/Avanger.cs index c4044ecc9c..83af4214ad 100644 --- a/Roles/AddOns/Common/Avanger.cs +++ b/Roles/AddOns/Common/Avanger.cs @@ -15,6 +15,12 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Avanger, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void OnMurderPlayer(PlayerControl target) { diff --git a/Roles/AddOns/Common/Aware.cs b/Roles/AddOns/Common/Aware.cs index bd22bfca4b..5ea1572e0e 100644 --- a/Roles/AddOns/Common/Aware.cs +++ b/Roles/AddOns/Common/Aware.cs @@ -14,7 +14,7 @@ public class Aware : IAddon public static OptionItem NeutralCanBeAware; private static OptionItem AwareknowRole; - public static Dictionary> AwareInteracted = []; + public static readonly Dictionary> AwareInteracted = []; public void SetupCustomOption() { @@ -22,19 +22,28 @@ public void SetupCustomOption() AwareknowRole = BooleanOptionItem.Create(Id + 13, "AwareKnowRole", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Aware]); } - public static void Init() + public void Init() { - AwareInteracted = []; + AwareInteracted.Clear(); IsEnable = false; } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) { AwareInteracted[playerId] = []; IsEnable = true; } + public void Remove(byte playerId) + { + AwareInteracted.Remove(playerId); + + if (!AwareInteracted.Any()) + IsEnable = false; + } public static void OnCheckMurder(CustomRoles killerRole, PlayerControl target) { + if (!target.Is(CustomRoles.Aware)) return; + switch (killerRole) { case CustomRoles.Consigliere: @@ -53,16 +62,16 @@ public static void OnCheckMurder(CustomRoles killerRole, PlayerControl target) public static void OnReportDeadBody() { - foreach (var pid in AwareInteracted.Keys.ToArray()) + foreach (var (pid, list) in AwareInteracted) { - var Awarepc = Utils.GetPlayerById(pid); - if (AwareInteracted[pid].Any() && Awarepc.IsAlive()) + var Awarepc = pid.GetPlayer(); + if (list.Any() && Awarepc.IsAlive()) { string rolelist = "Someone"; _ = new LateTask(() => { if (AwareknowRole.GetBool()) - rolelist = string.Join(", ", AwareInteracted[pid]); + rolelist = string.Join(", ", list); Utils.SendMessage(string.Format(GetString("AwareInteracted"), rolelist), pid, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Aware), GetString("AwareTitle"))); AwareInteracted[pid] = []; @@ -77,9 +86,7 @@ public static void OnVoted(PlayerControl pc, PlayerVoteArea pva) { case CustomRoles.FortuneTeller: case CustomRoles.Oracle: - if (!AwareInteracted.ContainsKey(pva.VotedFor)) AwareInteracted[pva.VotedFor] = []; - if (!AwareInteracted[pva.VotedFor].Contains(Utils.GetRoleName(pc.GetCustomRole()))) - AwareInteracted[pva.VotedFor].Add(Utils.GetRoleName(pc.GetCustomRole())); + AwareInteracted[pva.VotedFor].Add(Utils.GetRoleName(pc.GetCustomRole())); break; } } diff --git a/Roles/AddOns/Common/Bait.cs b/Roles/AddOns/Common/Bait.cs index a63b425bde..ee334ec961 100644 --- a/Roles/AddOns/Common/Bait.cs +++ b/Roles/AddOns/Common/Bait.cs @@ -16,7 +16,7 @@ public class Bait : IAddon public static OptionItem BaitNotification; public static OptionItem BaitCanBeReportedUnderAllConditions; - public static List BaitAlive = []; + public static readonly HashSet BaitAlive = []; public void SetupCustomOption() { @@ -30,9 +30,36 @@ public void SetupCustomOption() BaitCanBeReportedUnderAllConditions = BooleanOptionItem.Create(Id + 17, "BaitCanBeReportedUnderAllConditions", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); } - public static void Init() + public void Init() { - BaitAlive = []; + BaitAlive.Clear(); + } + public void Add(byte playerId, bool gameIsLoading = true) + { + BaitAlive.Add(playerId); + } + public void Remove(byte playerId) + { + BaitAlive.Remove(playerId); + } + public static void SendNotify() + { + if (MeetingStates.FirstMeeting && CustomRoles.Bait.RoleExist() && BaitNotification.GetBool()) + { + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Bait) && !BaitAlive.Contains(x.PlayerId)).ToArray()) + { + BaitAlive.Add(pc.PlayerId); + } + List baitAliveList = []; + foreach (var whId in BaitAlive.ToArray()) + { + PlayerControl whpc = whId.GetPlayer(); + if (whpc == null) continue; + baitAliveList.Add(whpc.GetRealName()); + } + string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; + MeetingHudStartPatch.AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); + } } public static void BaitAfterDeathTasks(PlayerControl killer, PlayerControl target) { diff --git a/Roles/AddOns/Common/Beartrap.cs b/Roles/AddOns/Common/Beartrap.cs index c9536a8a50..a465b70002 100644 --- a/Roles/AddOns/Common/Beartrap.cs +++ b/Roles/AddOns/Common/Beartrap.cs @@ -15,6 +15,12 @@ public void SetupCustomOption() TrapperBlockMoveTime = FloatOptionItem.Create(Id + 13, "TrapperBlockMoveTime", new(1f, 180f, 1f), 5f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]) .SetValueFormat(OptionFormat.Seconds); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } public static class TrapperExtension { diff --git a/Roles/AddOns/Common/Bewilder.cs b/Roles/AddOns/Common/Bewilder.cs index 255fe00802..dbd1f02ba6 100644 --- a/Roles/AddOns/Common/Bewilder.cs +++ b/Roles/AddOns/Common/Bewilder.cs @@ -11,6 +11,7 @@ public class Bewilder : IAddon private static OptionItem BewilderVision; private static OptionItem KillerGetBewilderVision; + private static readonly HashSet playerList = []; public static bool IsEnable; public void SetupCustomOption() @@ -20,16 +21,23 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Multiplier); KillerGetBewilderVision = BooleanOptionItem.Create(Id + 14, "KillerGetBewilderVision", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]); } - - public static void Init() + public void Init() { IsEnable = false; + playerList.Clear(); } - - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) { + playerList.Add(playerId); IsEnable = true; } + public void Remove(byte playerId) + { + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnable = false; + } public static void ApplyVisionOptions(IGameOptions opt) { diff --git a/Roles/AddOns/Common/Burst.cs b/Roles/AddOns/Common/Burst.cs index 45668a2e61..f87214610d 100644 --- a/Roles/AddOns/Common/Burst.cs +++ b/Roles/AddOns/Common/Burst.cs @@ -11,7 +11,9 @@ public class Burst : IAddon private static OptionItem BurstKillDelay; - private static readonly List BurstBodies = []; + private static readonly HashSet BurstBodies = []; + private static readonly HashSet playerList = []; + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Burst, canSetNum: true, teamSpawnOptions: true); @@ -19,15 +21,24 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); } - public static void Init() + public void Init() { - BurstBodies.Clear(); IsEnable = false; + BurstBodies.Clear(); + playerList.Clear(); } - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) { + playerList.Add(playerId); IsEnable = true; } + public void Remove(byte playerId) + { + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnable = false; + } public static void AfterMeetingTasks() { diff --git a/Roles/AddOns/Common/Cyber.cs b/Roles/AddOns/Common/Cyber.cs index 7ecb3378c0..4afeda11a5 100644 --- a/Roles/AddOns/Common/Cyber.cs +++ b/Roles/AddOns/Common/Cyber.cs @@ -7,7 +7,7 @@ public class Cyber : IAddon private const int Id = 19100; public AddonTypes Type => AddonTypes.Helpful; - public static List CyberDead = []; + public static readonly HashSet CyberDead = []; public static OptionItem ImpKnowCyberDead; public static OptionItem CrewKnowCyberDead; @@ -23,10 +23,15 @@ public void SetupCustomOption() CyberKnown = BooleanOptionItem.Create(Id + 16, "CyberKnown", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); } - public static void Init() + public void Init() { - CyberDead = []; + CyberDead.Clear(); } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } + public static void Clear() { CyberDead.Clear(); diff --git a/Roles/AddOns/Common/Diseased.cs b/Roles/AddOns/Common/Diseased.cs index ac8b77c984..75732fd3a4 100644 --- a/Roles/AddOns/Common/Diseased.cs +++ b/Roles/AddOns/Common/Diseased.cs @@ -11,7 +11,8 @@ public class Diseased : IAddon private static OptionItem DiseasedCDOpt; private static OptionItem DiseasedCDReset; - private static Dictionary KilledDiseased; + private static readonly HashSet playerList = []; + private static readonly Dictionary KilledDiseased = []; public void SetupCustomOption() { @@ -21,15 +22,24 @@ public void SetupCustomOption() DiseasedCDReset = BooleanOptionItem.Create(Id + 14, "DiseasedCDReset", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); } - public static void Init() + public void Init() { - KilledDiseased = []; IsEnable = false; + playerList.Clear(); + KilledDiseased.Clear(); } - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) { + playerList.Add(playerId); IsEnable = true; } + public void Remove(byte playerId) + { + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnable = false; + } public static void IncreaseKCD(PlayerControl player) { @@ -51,7 +61,7 @@ public static void AfterMeetingTasks() if (kdpc == null) continue; kdpc.ResetKillCooldown(); } - KilledDiseased = []; + KilledDiseased.Clear(); } } diff --git a/Roles/AddOns/Common/DoubleShot.cs b/Roles/AddOns/Common/DoubleShot.cs index 573c718023..a277cd5c88 100644 --- a/Roles/AddOns/Common/DoubleShot.cs +++ b/Roles/AddOns/Common/DoubleShot.cs @@ -4,7 +4,7 @@ namespace TOHE.Roles.AddOns.Common; public class DoubleShot : IAddon { - public static HashSet IsActive = []; + public static readonly HashSet IsActive = []; public AddonTypes Type => AddonTypes.Guesser; @@ -22,8 +22,12 @@ public void SetupCustomOption() NeutralCanBeDoubleShot = BooleanOptionItem.Create(19212, "NeutralCanBeDoubleShot", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); } - public static void Init() + public void Init() { - IsActive = []; + IsActive.Clear(); } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Egoist.cs b/Roles/AddOns/Common/Egoist.cs index ba90e9182c..f7d44af415 100644 --- a/Roles/AddOns/Common/Egoist.cs +++ b/Roles/AddOns/Common/Egoist.cs @@ -20,4 +20,11 @@ public void SetupCustomOption() ImpEgoistVisibalToAllies = BooleanOptionItem.Create(Id + 12, "ImpEgoistVisibalToAllies", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Egoist]); EgoistCountAsConverted = BooleanOptionItem.Create(Id + 13, "EgoistCountAsConverted", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Egoist]); } + + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Evader.cs b/Roles/AddOns/Common/Evader.cs index 3a9549a8f5..6c5c7da5f4 100644 --- a/Roles/AddOns/Common/Evader.cs +++ b/Roles/AddOns/Common/Evader.cs @@ -23,16 +23,21 @@ public void SetupCustomOption() .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evader]) .SetValueFormat(OptionFormat.Percent); } - public static void Init() + public void Init() { AlredyCheck.Clear(); SkillLimit.Clear(); } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) { AlredyCheck[playerId] = false; SkillLimit[playerId] = SkillLimitTimes.GetInt(); } + public void Remove(byte playerId) + { + AlredyCheck.Remove(playerId); + SkillLimit.Remove(playerId); + } public static void ReportDeadBody() { if (AlredyCheck.Any()) @@ -49,8 +54,6 @@ public static void RememberRandom() } public static void CheckExile(byte evaderId, ref int VoteNum) { - CheckAddEvader(evaderId); - if (AlredyCheck[evaderId] && RememberRandomForExile < ChanceNotExiled.GetInt()) { VoteNum = 0; @@ -65,12 +68,4 @@ public static void CheckExile(byte evaderId, ref int VoteNum) VoteNum = 0; } } - private static void CheckAddEvader(byte evaderId) - { - if (!SkillLimit.ContainsKey(evaderId)) - SkillLimit[evaderId] = SkillLimitTimes.GetInt(); - - if (!AlredyCheck.ContainsKey(evaderId)) - AlredyCheck[evaderId] = false; - } } diff --git a/Roles/AddOns/Common/Flash.cs b/Roles/AddOns/Common/Flash.cs index 7b38ffdfcc..2f61da7dd9 100644 --- a/Roles/AddOns/Common/Flash.cs +++ b/Roles/AddOns/Common/Flash.cs @@ -17,11 +17,19 @@ public void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Flash]) .SetValueFormat(OptionFormat.Multiplier); } - public static void SetSpeed(byte playerId, bool clearAddOn) + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) { - if (!clearAddOn) - Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); - else - Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); + } + public void Remove(byte playerId) + { + Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + playerId.GetPlayer()?.MarkDirtySettings(); + } + public static void SetSpeed(byte playerId) + { + Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Fool.cs b/Roles/AddOns/Common/Fool.cs index 33864f8513..8338f3f3c1 100644 --- a/Roles/AddOns/Common/Fool.cs +++ b/Roles/AddOns/Common/Fool.cs @@ -7,19 +7,30 @@ public class Fool : IAddon private const int Id = 25600; public static bool IsEnable = false; public AddonTypes Type => AddonTypes.Harmful; + + private static readonly HashSet playerList = []; public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Fool, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } - public static void Init() + public void Init() { IsEnable = false; + playerList.Clear(); } - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) { + playerList.Add(playerId); IsEnable = true; } + public void Remove(byte playerId) + { + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnable = false; + } public static bool BlockFixSabotage(PlayerControl player, SystemTypes systemType) { diff --git a/Roles/AddOns/Common/Fragile.cs b/Roles/AddOns/Common/Fragile.cs index 693f7be371..b667681db5 100644 --- a/Roles/AddOns/Common/Fragile.cs +++ b/Roles/AddOns/Common/Fragile.cs @@ -7,7 +7,7 @@ public class Fragile : IAddon private const int Id = 20600; public AddonTypes Type => AddonTypes.Harmful; - public static OptionItem ImpCanKillFragile; + private static OptionItem ImpCanKillFragile; private static OptionItem CrewCanKillFragile; private static OptionItem NeutralCanKillFragile; private static OptionItem FragileKillerLunge; @@ -20,7 +20,12 @@ public void SetupCustomOption() NeutralCanKillFragile = BooleanOptionItem.Create(Id + 15, "NeutralCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); FragileKillerLunge = BooleanOptionItem.Create(Id + 16, "FragileKillerLunge", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool KillFragile(PlayerControl killer, PlayerControl target) { if (target == null || !target.IsAlive()) return false; diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index 20f4159283..2eb333b8a2 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -30,22 +30,25 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Multiplier); } - public static void Init() + public void Init() { - InRadius.Clear(); IsEnable = false; + InRadius.Clear(); MarkedOnce.Clear(); } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) { MarkedOnce[playerId] = false; InRadius[playerId] = []; IsEnable = true; } - public static void Remove(byte playerId) + public void Remove(byte playerId) { MarkedOnce.Remove(playerId); InRadius.Remove(playerId); + + if (!MarkedOnce.Any()) + IsEnable = false; } public static void ApplyGameOptions(IGameOptions opt, PlayerControl player) { diff --git a/Roles/AddOns/Common/Gravestone.cs b/Roles/AddOns/Common/Gravestone.cs index 9aac266c92..bfba077dd3 100644 --- a/Roles/AddOns/Common/Gravestone.cs +++ b/Roles/AddOns/Common/Gravestone.cs @@ -11,6 +11,12 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Gravestone, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool EveryoneKnowRole(PlayerControl player) => player.Is(CustomRoles.Gravestone) && !player.IsAlive(); } diff --git a/Roles/AddOns/Common/Guesser.cs b/Roles/AddOns/Common/Guesser.cs index c44a9a16f5..1085fba427 100644 --- a/Roles/AddOns/Common/Guesser.cs +++ b/Roles/AddOns/Common/Guesser.cs @@ -26,5 +26,11 @@ public void SetupCustomOption() GTryHideMsg = BooleanOptionItem.Create(Id + 15, "GuesserTryHideMsg", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]) .SetColor(Color.green); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } diff --git a/Roles/AddOns/Common/Influenced.cs b/Roles/AddOns/Common/Influenced.cs index 82446aefb4..6b0008d57d 100644 --- a/Roles/AddOns/Common/Influenced.cs +++ b/Roles/AddOns/Common/Influenced.cs @@ -9,6 +9,12 @@ public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Influenced, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void ChangeVotingData(Dictionary VotingData) { //The incoming votedata does not count influenced votes @@ -23,24 +29,19 @@ public static void ChangeVotingData(Dictionary VotingData) int max = 0; bool tie = false; byte exileId = byte.MaxValue; - //var voteLog = Logger.Handler("Influenced check Vote"); foreach (var data in VotingData) { - //voteLog.Info($"{data.Key}({Utils.GetVoteName(data.Key)}):{data.Value}票"); if (data.Value > max) { - //voteLog.Info(data.Key + "拥有更高票数(" + data.Value + ")"); exileId = data.Key; max = data.Value; tie = false; } else if (data.Value == max) { - //voteLog.Info(data.Key + "与" + exileId + "的票数相同(" + data.Value + ")"); exileId = byte.MaxValue; tie = true; } - //voteLog.Info($"驱逐ID: {exileId}, 最大: {max}票"); } if (tie) return; diff --git a/Roles/AddOns/Common/Loyal.cs b/Roles/AddOns/Common/Loyal.cs index 798e3c4fa3..c01c8460c8 100644 --- a/Roles/AddOns/Common/Loyal.cs +++ b/Roles/AddOns/Common/Loyal.cs @@ -17,4 +17,10 @@ public void SetupCustomOption() CrewCanBeLoyal = BooleanOptionItem.Create(Id + 11, "CrewCanBeLoyal", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Lucky.cs b/Roles/AddOns/Common/Lucky.cs index 90d2102d3d..5960b3386a 100644 --- a/Roles/AddOns/Common/Lucky.cs +++ b/Roles/AddOns/Common/Lucky.cs @@ -9,7 +9,7 @@ public class Lucky : IAddon private static OptionItem LuckyProbability; - private static Dictionary LuckyAvoid; + private static readonly Dictionary LuckyAvoid = []; public void SetupCustomOption() { @@ -18,15 +18,15 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Percent); } - public static void Init() + public void Init() { - LuckyAvoid = []; + LuckyAvoid.Clear(); } - public static void Add(byte PlayerId) + public void Add(byte playerId, bool gameIsLoading = true) { - LuckyAvoid.Add(PlayerId, false); + LuckyAvoid[playerId] = false; } - public static void Remove(byte player) + public void Remove(byte player) { LuckyAvoid.Remove(player); } diff --git a/Roles/AddOns/Common/Mundane.cs b/Roles/AddOns/Common/Mundane.cs index dc23ed0f21..6e5b30824d 100644 --- a/Roles/AddOns/Common/Mundane.cs +++ b/Roles/AddOns/Common/Mundane.cs @@ -16,10 +16,17 @@ public void SetupCustomOption() CanBeOnCrew = BooleanOptionItem.Create(Id + 11, "CrewCanBeMundane", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mundane]); CanBeOnNeutral = BooleanOptionItem.Create(Id + 12, "NeutralCanBeMundane", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mundane]); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } + public static bool OnGuess(PlayerControl pc) { if (pc == null || !pc.Is(CustomRoles.Mundane)) return true; - return (pc.GetPlayerTaskState().IsTaskFinished); + return pc.GetPlayerTaskState().IsTaskFinished; } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Necroview.cs b/Roles/AddOns/Common/Necroview.cs index f64e8460fd..759cb96dd8 100644 --- a/Roles/AddOns/Common/Necroview.cs +++ b/Roles/AddOns/Common/Necroview.cs @@ -11,6 +11,12 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Necroview, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static string NameColorOptions(PlayerControl target) { diff --git a/Roles/AddOns/Common/Oblivious.cs b/Roles/AddOns/Common/Oblivious.cs index a1a36c2e6e..6b3296c32e 100644 --- a/Roles/AddOns/Common/Oblivious.cs +++ b/Roles/AddOns/Common/Oblivious.cs @@ -14,5 +14,11 @@ public void SetupCustomOption() SetupAdtRoleOptions(Id, CustomRoles.Oblivious, canSetNum: true, teamSpawnOptions: true); ObliviousBaitImmune = BooleanOptionItem.Create(Id + 13, "ObliviousBaitImmune", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Oiiai.cs b/Roles/AddOns/Common/Oiiai.cs index 24e421051a..1b73db716a 100644 --- a/Roles/AddOns/Common/Oiiai.cs +++ b/Roles/AddOns/Common/Oiiai.cs @@ -36,17 +36,28 @@ public void SetupCustomOption() CanPassOn = BooleanOptionItem.Create(Id + 14, "OiiaiCanPassOn", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); ChangeNeutralRole = StringOptionItem.Create(Id + 15, "NeutralChangeRolesForOiiai", EnumHelper.GetAllNames(), 1, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oiiai]); } - public static void Init() + public void Init() { - playerIdList.Clear(); - Eraser.ErasedRoleStorage.Clear(); IsEnable = false; + playerIdList.Clear(); } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) { playerIdList.Add(playerId); IsEnable = true; } + public static void PassOnKiller(byte playerId) + { + playerIdList.Add(playerId); + IsEnable = true; + } + public void Remove(byte playerId) + { + playerIdList.Remove(playerId); + + if (!playerIdList.Any()) + IsEnable = false; + } public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) { @@ -58,7 +69,7 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) if (CanPassOn.GetBool() && !playerIdList.Contains(killer.PlayerId)) { - Add(killer.PlayerId); + PassOnKiller(killer.PlayerId); killer.RpcSetCustomRole(CustomRoles.Oiiai); Logger.Info(killer.GetNameWithRole() + " gets Oiiai addon by " + target.GetNameWithRole(), "Oiiai"); } @@ -75,7 +86,7 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) } var killerRole = killer.GetCustomRole(); - if (killerRole.IsTasklessCrewmate() || killerRole.IsGhostRole() || Main.TasklessCrewmate.Contains(killer.PlayerId) || CopyCat.playerIdList.Contains(killer.PlayerId) || killer.Is(CustomRoles.Stubborn)) + if (killerRole.IsTasklessCrewmate() || killer.HasGhostRole() || Main.TasklessCrewmate.Contains(killer.PlayerId) || CopyCat.playerIdList.Contains(killer.PlayerId) || killer.Is(CustomRoles.Stubborn)) { Logger.Info($"Oiiai {killer.GetNameWithRole().RemoveHtmlTags()} cannot eraser crew imp-based role", "Oiiai"); return; diff --git a/Roles/AddOns/Common/Onbound.cs b/Roles/AddOns/Common/Onbound.cs index 1a46143f64..b48ece0e49 100644 --- a/Roles/AddOns/Common/Onbound.cs +++ b/Roles/AddOns/Common/Onbound.cs @@ -11,5 +11,11 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Onbound, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } diff --git a/Roles/AddOns/Common/Overlocked.cs b/Roles/AddOns/Common/Overlocked.cs index 09b098045c..12bca68234 100644 --- a/Roles/AddOns/Common/Overlocked.cs +++ b/Roles/AddOns/Common/Overlocked.cs @@ -15,5 +15,11 @@ public void SetupCustomOption() OverclockedReduction = FloatOptionItem.Create(Id + 10, "OverclockedReduction", new(0f, 90f, 5f), 40f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Overclocked]) .SetValueFormat(OptionFormat.Percent); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } diff --git a/Roles/AddOns/Common/Paranoia.cs b/Roles/AddOns/Common/Paranoia.cs index a1851beb2e..7a5fb07a39 100644 --- a/Roles/AddOns/Common/Paranoia.cs +++ b/Roles/AddOns/Common/Paranoia.cs @@ -20,6 +20,12 @@ public void SetupCustomOption() DualVotes = BooleanOptionItem.Create(Id + 12, "DualVotes", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]); HideAdditionalVotes = BooleanOptionItem.Create(Id + 13, "HideAdditionalVotes", false, TabGroup.Addons, false).SetParent(DualVotes); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool IsExistInGame(PlayerControl player) => player.Is(CustomRoles.Paranoia); diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index 3d9c466189..89e86c6154 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -30,19 +30,19 @@ public void SetupCustomOption() CountBlockedVentsInFungle = IntegerOptionItem.Create(Id + 15, "Prohibited_CountBlockedVentsInFungle", new(0, 10, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Prohibited]); } - public static void Init() + public void Init() { RememberBlokcedVents.Clear(); } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) { SetBlockedVents(playerId); } - public static void Remove(byte playerId) + public void Remove(byte playerId) { if (!RememberBlokcedVents.TryGetValue(playerId, out var ventListId)) return; - foreach(var ventId in ventListId) + foreach (var ventId in ventListId) { CustomRoleManager.BlockedVentsList[playerId].Remove(ventId); } diff --git a/Roles/AddOns/Common/Radar.cs b/Roles/AddOns/Common/Radar.cs index dbab822cf9..f64de2dc63 100644 --- a/Roles/AddOns/Common/Radar.cs +++ b/Roles/AddOns/Common/Radar.cs @@ -13,11 +13,14 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Radar, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } - - public static void Init() - { + public void Init() + { ClosestPlayer.Clear(); } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public void OnFixedUpdateLowLoad(PlayerControl seer) { if (!seer.Is(CustomRoles.Radar) || seer.inVent || !seer.IsAlive() || !GameStates.IsInTask) return; diff --git a/Roles/AddOns/Common/Rainbow.cs b/Roles/AddOns/Common/Rainbow.cs index 1c000efb86..50cf04d6a1 100644 --- a/Roles/AddOns/Common/Rainbow.cs +++ b/Roles/AddOns/Common/Rainbow.cs @@ -11,8 +11,10 @@ public class Rainbow : IAddon private static OptionItem RainbowColorChangeCoolDown; private static OptionItem ChangeInCamouflage; - public static bool isEnabled = false; - public static long LastColorChange; + private static readonly HashSet playerList = []; + public static bool IsEnabled = false; + private static long LastColorChange; + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Rainbow, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); @@ -21,14 +23,23 @@ public void SetupCustomOption() ChangeInCamouflage = BooleanOptionItem.Create(Id + 14, "RainbowInCamouflage", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Rainbow]); } - public static void Init() + public void Init() { + IsEnabled = false; + playerList.Clear(); LastColorChange = Utils.GetTimeStamp(); - isEnabled = false; } - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) + { + playerList.Add(playerId); + IsEnabled = true; + } + public void Remove(byte playerId) { - isEnabled = true; + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnabled = false; } public static void OnFixedUpdate() { diff --git a/Roles/AddOns/Common/Reach.cs b/Roles/AddOns/Common/Reach.cs index fc92c6daf6..02c6e97699 100644 --- a/Roles/AddOns/Common/Reach.cs +++ b/Roles/AddOns/Common/Reach.cs @@ -13,5 +13,11 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Reach, canSetNum: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void ApplyGameOptions(IGameOptions opt) => opt.SetInt(Int32OptionNames.KillDistance, 2); } \ No newline at end of file diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index f6019bcbd8..dbffa64607 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -21,17 +21,17 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Times); OnlyVoted = BooleanOptionItem.Create(Id + 12, "RebirthCountVotes", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Rebirth]); } - public static void Init() + public void Init() { Rebirths.Clear(); VotedCount.Clear(); } - public static void Add(byte Playerid) + public void Add(byte playerId, bool gameIsLoading = true) { - Rebirths[Playerid] = RebirthUses.GetInt(); - VotedCount[Playerid] = []; + Rebirths[playerId] = RebirthUses.GetInt(); + VotedCount[playerId] = []; } - public static void Remove(byte Playerid) + public void Remove(byte Playerid) { Rebirths.Remove(Playerid); } diff --git a/Roles/AddOns/Common/Rebound.cs b/Roles/AddOns/Common/Rebound.cs index f26edb62e2..6e0b2c4d47 100644 --- a/Roles/AddOns/Common/Rebound.cs +++ b/Roles/AddOns/Common/Rebound.cs @@ -12,4 +12,10 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Rebound, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Seer.cs b/Roles/AddOns/Common/Seer.cs index f804ea26e3..e177d16bbb 100644 --- a/Roles/AddOns/Common/Seer.cs +++ b/Roles/AddOns/Common/Seer.cs @@ -10,4 +10,10 @@ public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Seer, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } diff --git a/Roles/AddOns/Common/Silent.cs b/Roles/AddOns/Common/Silent.cs index c9f0b852a2..924473f112 100644 --- a/Roles/AddOns/Common/Silent.cs +++ b/Roles/AddOns/Common/Silent.cs @@ -9,4 +9,10 @@ public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Silent, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } diff --git a/Roles/AddOns/Common/Sleuth.cs b/Roles/AddOns/Common/Sleuth.cs index 3a1caebd4b..869555ebc4 100644 --- a/Roles/AddOns/Common/Sleuth.cs +++ b/Roles/AddOns/Common/Sleuth.cs @@ -7,7 +7,7 @@ public class Sleuth : IAddon public static OptionItem SleuthCanKnowKillerRole; - public static Dictionary SleuthNotify = []; + public static readonly Dictionary SleuthNotify = []; public void SetupCustomOption() { @@ -15,10 +15,14 @@ public void SetupCustomOption() SleuthCanKnowKillerRole = BooleanOptionItem.Create(Id + 13, "SleuthCanKnowKillerRole", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); } - public static void Init() + public void Init() { - SleuthNotify = []; + SleuthNotify.Clear(); } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void Clear() { SleuthNotify.Clear(); diff --git a/Roles/AddOns/Common/Sloth.cs b/Roles/AddOns/Common/Sloth.cs index a377700b50..fbf1c28591 100644 --- a/Roles/AddOns/Common/Sloth.cs +++ b/Roles/AddOns/Common/Sloth.cs @@ -17,11 +17,19 @@ public void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Sloth]) .SetValueFormat(OptionFormat.Multiplier); } - public static void SetSpeed(byte playerId, bool clearAddOn) + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) { - if (!clearAddOn) - Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); - else - Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); + } + public void Remove(byte playerId) + { + Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + playerId.GetPlayer()?.MarkDirtySettings(); + } + public static void SetSpeed(byte playerId) + { + Main.AllPlayerSpeed[playerId] = OptionSpeed.GetFloat(); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 353889402f..371a62e213 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -1,127 +1,123 @@ using static TOHE.Options; using UnityEngine; -using Hazel; -namespace TOHE.Roles.AddOns.Common +namespace TOHE.Roles.AddOns.Common; + +internal class Spurt : IAddon { - internal class Spurt : IAddon + private static OptionItem MinSpeed; + private static OptionItem Modulator; + private static OptionItem MaxSpeed; + private static OptionItem DisplaysCharge; + + private static readonly Dictionary LastPos = []; + public static readonly Dictionary StartingSpeed = []; + private static readonly Dictionary LastNum = []; + private static readonly Dictionary LastUpdate = []; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - private static OptionItem MinSpeed; - private static OptionItem Modulator; - private static OptionItem MaxSpeed; - private static OptionItem DisplaysCharge; - - private static readonly Dictionary LastPos = []; - public static readonly Dictionary StartingSpeed = []; - private static readonly Dictionary LastNum = []; - private static readonly Dictionary LastUpdate = []; - public AddonTypes Type => AddonTypes.Helpful; - - public void SetupCustomOption() - { - const int id = 28800; - SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); - MinSpeed = FloatOptionItem.Create(id + 6, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) - .SetValueFormat(OptionFormat.Multiplier); - MaxSpeed = FloatOptionItem.Create(id + 7, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) - .SetValueFormat(OptionFormat.Multiplier); - Modulator =FloatOptionItem.Create(id + 8, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) - .SetValueFormat(OptionFormat.Multiplier); - DisplaysCharge = BooleanOptionItem.Create(id + 9, "EnableSpurtCharge", false, TabGroup.Addons, false) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); - } - private static void Sendrpc(byte playerId) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SpurtSync, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(Main.AllPlayerSpeed[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - public static void RecieveRPC(MessageReader reader) - { - byte playerid = reader.ReadByte(); - float speed = reader.ReadSingle(); - Main.AllPlayerSpeed[playerid] = speed; - } - public static void Add() + const int id = 28800; + SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); + MinSpeed = FloatOptionItem.Create(id + 6, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + MaxSpeed = FloatOptionItem.Create(id + 7, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + Modulator =FloatOptionItem.Create(id + 8, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + DisplaysCharge = BooleanOptionItem.Create(id + 9, "EnableSpurtCharge", false, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); + } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { + foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) { - foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) + if (pc.Is(CustomRoles.Spurt)) { - if (pc.Is(CustomRoles.Spurt)) - { - LastPos[pc.PlayerId] = pc.GetCustomPosition(); - LastNum[pc.PlayerId] = 0; - LastUpdate[pc.PlayerId] = Utils.TimeStamp; - StartingSpeed[pc.PlayerId] = speed; - } + LastPos[pc.PlayerId] = pc.GetCustomPosition(); + LastNum[pc.PlayerId] = 0; + LastUpdate[pc.PlayerId] = Utils.TimeStamp; + StartingSpeed[pc.PlayerId] = speed; } } + } + public void Remove(byte playerId) + { + LastPos.Remove(playerId); + LastNum.Remove(playerId); + LastUpdate.Remove(playerId); + Main.AllPlayerSpeed[playerId] = StartingSpeed[playerId]; + StartingSpeed.Remove(playerId); + playerId.GetPlayer()?.MarkDirtySettings(); + } - public static void DeathTask(PlayerControl player) - { - if (!player.Is(CustomRoles.Spurt)) return; - - Main.AllPlayerSpeed[player.PlayerId] = StartingSpeed[player.PlayerId]; - player.MarkDirtySettings(); - } + public static void DeathTask(PlayerControl player) + { + if (!player.Is(CustomRoles.Spurt)) return; - private static int DetermineCharge(PlayerControl player) - { - float minSpeed = MinSpeed.GetFloat(); - float maxSpeed = MaxSpeed.GetFloat(); + Main.AllPlayerSpeed[player.PlayerId] = StartingSpeed[player.PlayerId]; + player.MarkDirtySettings(); + } - if (Mathf.Approximately(minSpeed, maxSpeed)) - return 100; + private static int DetermineCharge(PlayerControl player) + { + float minSpeed = MinSpeed.GetFloat(); + float maxSpeed = MaxSpeed.GetFloat(); - return (int)((Main.AllPlayerSpeed[player.PlayerId] - minSpeed) / (maxSpeed - minSpeed) * 100); - } + if (Mathf.Approximately(minSpeed, maxSpeed)) + return 100; - public static string GetSuffix(PlayerControl player, bool isforhud = false, bool isformeeting = false) - { - if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting || isformeeting) - return string.Empty; + return (int)((Main.AllPlayerSpeed[player.PlayerId] - minSpeed) / (maxSpeed - minSpeed) * 100); + } - int fontsize = isforhud ? 100 : 55; + public static string GetSuffix(PlayerControl player, bool isforhud = false, bool isformeeting = false) + { + if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting || isformeeting) + return string.Empty; - return $"{string.Format(Translator.GetString("SpurtSuffix"), DetermineCharge(player))}"; - } + int fontsize = isforhud ? 100 : 55; - public void OnFixedUpdate(PlayerControl player) - { - if (!player.Is(CustomRoles.Spurt) || !player.IsAlive()) return; + return $"{string.Format(Translator.GetString("SpurtSuffix"), DetermineCharge(player))}"; + } - var pos = player.GetCustomPosition(); - bool moving = Utils.GetDistance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); - LastPos[player.PlayerId] = pos; + public void OnFixedUpdate(PlayerControl player) + { + if (!player.Is(CustomRoles.Spurt) || !player.IsAlive()) return; - float modulator = Modulator.GetFloat(); - float ChargeBy = Mathf.Clamp(modulator / 20 * 1.5f, 0.05f, 0.6f); - float Decreaseby = Mathf.Clamp(modulator / 20 * 0.5f, 0.01f, 0.3f); + var pos = player.GetCustomPosition(); + bool moving = Utils.GetDistance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); + LastPos[player.PlayerId] = pos; - int charge = DetermineCharge(player); - if (DisplaysCharge.GetBool() && !player.IsModded() && LastNum[player.PlayerId] != charge) - { - LastNum[player.PlayerId] = charge; - long now = Utils.TimeStamp; - if (now != LastUpdate[player.PlayerId]) - { - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - LastUpdate[player.PlayerId] = now; - Sendrpc(player.PlayerId); - } - } + float modulator = Modulator.GetFloat(); + float ChargeBy = Mathf.Clamp(modulator / 20 * 1.5f, 0.05f, 0.6f); + float Decreaseby = Mathf.Clamp(modulator / 20 * 0.5f, 0.01f, 0.3f); - if (!moving) + int charge = DetermineCharge(player); + if (DisplaysCharge.GetBool() && !player.IsModded() && LastNum[player.PlayerId] != charge) + { + LastNum[player.PlayerId] = charge; + long now = Utils.TimeStamp; + if (now != LastUpdate[player.PlayerId]) { - Main.AllPlayerSpeed[player.PlayerId] += Mathf.Clamp(ChargeBy, 0f, MaxSpeed.GetFloat() - Main.AllPlayerSpeed[player.PlayerId]); - return; + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + LastUpdate[player.PlayerId] = now; + player.SyncGeneralOptions(); } + } - Main.AllPlayerSpeed[player.PlayerId] -= Mathf.Clamp(Decreaseby, 0f, Main.AllPlayerSpeed[player.PlayerId] - MinSpeed.GetFloat()); - player.MarkDirtySettings(); + if (!moving) + { + Main.AllPlayerSpeed[player.PlayerId] += Mathf.Clamp(ChargeBy, 0f, MaxSpeed.GetFloat() - Main.AllPlayerSpeed[player.PlayerId]); + return; } + + Main.AllPlayerSpeed[player.PlayerId] -= Mathf.Clamp(Decreaseby, 0f, Main.AllPlayerSpeed[player.PlayerId] - MinSpeed.GetFloat()); + player.MarkDirtySettings(); } } diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index 30ca0dd02e..196fc82a35 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -10,8 +10,8 @@ public class Statue : IAddon private static OptionItem PeopleAmount; private static bool Active; - private static HashSet CountNearplr; - private static Dictionary tempSpeed; + private static readonly HashSet CountNearplr = []; + private static readonly Dictionary TempSpeed = []; public void SetupCustomOption() { @@ -22,42 +22,42 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Times); } - public static void Init() + public void Init() { - CountNearplr = []; - tempSpeed = []; - Active = true; IsEnable = false; + CountNearplr.Clear(); + TempSpeed.Clear(); + Active = true; } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) { - tempSpeed.Add(playerId, Main.AllPlayerSpeed[playerId]); + var speed = Main.AllPlayerSpeed[playerId]; + TempSpeed[playerId] = speed; IsEnable = true; } - - public static void Remove(byte playerId) + public void Remove(byte playerId) { - if (Main.AllPlayerSpeed[playerId] == SlowDown.GetFloat()) - { - Main.AllPlayerSpeed[playerId] = Main.AllPlayerSpeed[playerId] - SlowDown.GetFloat() + tempSpeed[playerId]; - Utils.GetPlayerById(playerId)?.MarkDirtySettings(); - } - tempSpeed.Remove(playerId); + Main.AllPlayerSpeed[playerId] = Main.AllPlayerSpeed[playerId] - SlowDown.GetFloat() + TempSpeed[playerId]; + TempSpeed.Remove(playerId); + playerId.GetPlayer()?.MarkDirtySettings(); + + if (!TempSpeed.Any()) + IsEnable = false; } public static void AfterMeetingTasks() { - foreach (var Statue in tempSpeed.Keys) + foreach (var Statue in TempSpeed.Keys.ToArray()) { var pc = Utils.GetPlayerById(Statue); if (pc == null) continue; - float tmpFloat = tempSpeed[Statue]; + float tmpFloat = TempSpeed[Statue]; Main.AllPlayerSpeed[Statue] = Main.AllPlayerSpeed[Statue] - Main.AllPlayerSpeed[Statue] + tmpFloat; pc.MarkDirtySettings(); } Active = false; - CountNearplr = []; + CountNearplr.Clear(); _ = new LateTask(() => { Active = true; @@ -97,7 +97,7 @@ public void OnFixedUpdate(PlayerControl victim) } else if (Main.AllPlayerSpeed[victim.PlayerId] == SlowDown.GetFloat()) { - float tmpFloat = tempSpeed[victim.PlayerId]; + float tmpFloat = TempSpeed[victim.PlayerId]; Main.AllPlayerSpeed[victim.PlayerId] = Main.AllPlayerSpeed[victim.PlayerId] - SlowDown.GetFloat() + tmpFloat; victim.MarkDirtySettings(); } diff --git a/Roles/AddOns/Common/Stubborn.cs b/Roles/AddOns/Common/Stubborn.cs index 5856c4d906..5970d18ad9 100644 --- a/Roles/AddOns/Common/Stubborn.cs +++ b/Roles/AddOns/Common/Stubborn.cs @@ -11,5 +11,11 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Stubborn, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } diff --git a/Roles/AddOns/Common/Susceptible.cs b/Roles/AddOns/Common/Susceptible.cs index d1add8acf0..4151d635b7 100644 --- a/Roles/AddOns/Common/Susceptible.cs +++ b/Roles/AddOns/Common/Susceptible.cs @@ -15,7 +15,12 @@ public void SetupCustomOption() Options.SetupAdtRoleOptions(Id, CustomRoles.Susceptible, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); EnabledDeathReasons = BooleanOptionItem.Create(Id + 11, "OnlyEnabledDeathReasons", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Susceptible]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } private static void ChangeRandomDeath() { PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues(); diff --git a/Roles/AddOns/Common/Tiebreaker.cs b/Roles/AddOns/Common/Tiebreaker.cs index dae25a4d95..337e1de0ca 100644 --- a/Roles/AddOns/Common/Tiebreaker.cs +++ b/Roles/AddOns/Common/Tiebreaker.cs @@ -5,16 +5,21 @@ public class Tiebreaker : IAddon private const int Id = 20200; public AddonTypes Type => AddonTypes.Helpful; - public static List VoteFor = []; + public static readonly HashSet VoteFor = []; public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Tiebreaker, canSetNum: true, teamSpawnOptions: true); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void Clear() { - VoteFor = []; + VoteFor.Clear(); } public static void CheckVote(PlayerControl target, PlayerVoteArea ps) { diff --git a/Roles/AddOns/Common/Tired.cs b/Roles/AddOns/Common/Tired.cs index c2064e6391..c932880bec 100644 --- a/Roles/AddOns/Common/Tired.cs +++ b/Roles/AddOns/Common/Tired.cs @@ -8,13 +8,12 @@ public class Tired : IAddon { private const int Id = 27300; public AddonTypes Type => AddonTypes.Harmful; - private static Dictionary playerIdList; // Target Action player for Vision + private static readonly Dictionary playerIdList = []; // Target Action player for Vision private static OptionItem SetVision; private static OptionItem SetSpeed; private static OptionItem TiredDuration; - public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Tired, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); @@ -26,24 +25,29 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); } - public static void Init() + public void Init() + { + playerIdList.Clear(); + } + public void Add(byte playerId, bool gameIsLoading = true) { - playerIdList = []; + playerIdList[playerId] = false; } - public static void Add(byte playerId) + public void Remove(byte playerId) { - playerIdList.Add(playerId, false); + playerIdList.Remove(playerId); + playerId.GetPlayer()?.MarkDirtySettings(); } - public static void Remove(byte player) + public static void RemoveMidGame(byte playerId) { - playerIdList.Remove(player); + playerIdList.Remove(playerId); } public static void ApplyGameOptions(IGameOptions opt, PlayerControl player) { if (!playerIdList.ContainsKey(player.PlayerId)) return; - if (playerIdList[player.PlayerId]) + if (playerIdList.TryGetValue(player.PlayerId, out var isTired) && isTired) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, SetVision.GetFloat()); @@ -61,15 +65,17 @@ public static void AfterActionTasks(PlayerControl player) { // Speed player.Notify(GetString("TiredNotify")); - player.MarkDirtySettings(); var tmpSpeed = Main.AllPlayerSpeed[player.PlayerId]; Main.AllPlayerSpeed[player.PlayerId] = SetSpeed.GetFloat(); // Vision playerIdList[player.PlayerId] = true; + player.MarkDirtySettings(); _ = new LateTask(() => { + if (!playerIdList.ContainsKey(player.PlayerId)) return; + Main.AllPlayerSpeed[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId] - SetSpeed.GetFloat() + tmpSpeed; player.MarkDirtySettings(); playerIdList[player.PlayerId] = false; diff --git a/Roles/AddOns/Common/Unlucky.cs b/Roles/AddOns/Common/Unlucky.cs index c761494c75..4f5055cf73 100644 --- a/Roles/AddOns/Common/Unlucky.cs +++ b/Roles/AddOns/Common/Unlucky.cs @@ -36,6 +36,12 @@ public void SetupCustomOption() UnluckyOpenDoorSuicideChance = IntegerOptionItem.Create(Id + 14, "UnluckyOpenDoorSuicideChance", new(0, 100, 1), 4, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) .SetValueFormat(OptionFormat.Percent); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool SuicideRand(PlayerControl victim, StateSuicide state) { var shouldBeSuicide = IRandom.Instance.Next(1, 100) <= state switch diff --git a/Roles/AddOns/Common/Unreportable.cs b/Roles/AddOns/Common/Unreportable.cs index b14895c65a..eb23d25d70 100644 --- a/Roles/AddOns/Common/Unreportable.cs +++ b/Roles/AddOns/Common/Unreportable.cs @@ -11,4 +11,10 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Unreportable, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Voidballot.cs b/Roles/AddOns/Common/Voidballot.cs index ca98b90e32..44a76708b2 100644 --- a/Roles/AddOns/Common/Voidballot.cs +++ b/Roles/AddOns/Common/Voidballot.cs @@ -11,4 +11,10 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.VoidBallot, canSetNum: true, teamSpawnOptions: true); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Watcher.cs b/Roles/AddOns/Common/Watcher.cs index ba6bd61e87..9a7dc556bd 100644 --- a/Roles/AddOns/Common/Watcher.cs +++ b/Roles/AddOns/Common/Watcher.cs @@ -12,7 +12,12 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Watcher, canSetNum: true, teamSpawnOptions: true); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void RevealVotes(IGameOptions opt) => opt.SetBool(BoolOptionNames.AnonymousVotes, false); } diff --git a/Roles/AddOns/Common/Youtuber.cs b/Roles/AddOns/Common/Youtuber.cs index 7c46743191..96832f6fa5 100644 --- a/Roles/AddOns/Common/Youtuber.cs +++ b/Roles/AddOns/Common/Youtuber.cs @@ -16,6 +16,12 @@ public void SetupCustomOption() KillerWinsWithYouTuber = BooleanOptionItem.Create(Id + 10, "Youtuber_KillerWinsWithYouTuber", false, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Youtuber]); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) { target.SetDeathReason(PlayerState.DeathReason.Kill); diff --git a/Roles/AddOns/Crewmate/Bloodthirst.cs b/Roles/AddOns/Crewmate/Bloodthirst.cs index 3dcb4d1022..5ab9cbb597 100644 --- a/Roles/AddOns/Crewmate/Bloodthirst.cs +++ b/Roles/AddOns/Crewmate/Bloodthirst.cs @@ -12,11 +12,14 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Bloodthirst, canSetNum: true); } - - public static void Add() + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) { Alchemist.AddBloodlus(); } + public void Remove(byte playerId) + { } public static void OnTaskComplete(PlayerControl player) { diff --git a/Roles/AddOns/Crewmate/Ghoul.cs b/Roles/AddOns/Crewmate/Ghoul.cs index d86be8dadf..01226169ec 100644 --- a/Roles/AddOns/Crewmate/Ghoul.cs +++ b/Roles/AddOns/Crewmate/Ghoul.cs @@ -6,24 +6,29 @@ public class Ghoul : IAddon { private const int Id = 21900; public AddonTypes Type => AddonTypes.Mixed; - public static HashSet KillGhoul = []; + public static bool IsEnable; - + public static readonly HashSet KillGhoul = []; + public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Ghoul, canSetNum: true, tab: TabGroup.Addons); } - public static void Init() + public void Init() { - KillGhoul = []; + KillGhoul.Clear(); IsEnable = false; } - - public static void Add() + public void Add(byte playerId, bool gameIsLoading = true) { IsEnable = true; } + public void Remove(byte playerId) + { + if (!Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Ghoul))) + IsEnable = false; + } public static void ApplyGameOptions(PlayerControl player) { diff --git a/Roles/AddOns/Crewmate/Hurried.cs b/Roles/AddOns/Crewmate/Hurried.cs index 418226e0d2..e4c45f5539 100644 --- a/Roles/AddOns/Crewmate/Hurried.cs +++ b/Roles/AddOns/Crewmate/Hurried.cs @@ -17,7 +17,12 @@ public void SetupCustomOption() CanBeOnTaskBasedCrew = BooleanOptionItem.Create(Id + 12, "TaskBasedCrewCanBeHurried", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Hurried]); CanBeConverted = BooleanOptionItem.Create(Id + 13, "HurriedCanBeConverted", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Hurried]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool CheckWinState(PlayerControl pc) { if (pc == null) return false; diff --git a/Roles/AddOns/Crewmate/Lazy.cs b/Roles/AddOns/Crewmate/Lazy.cs index ef294fead9..9e342e6939 100644 --- a/Roles/AddOns/Crewmate/Lazy.cs +++ b/Roles/AddOns/Crewmate/Lazy.cs @@ -18,7 +18,12 @@ public void SetupCustomOption() TaskBasedCrewCanBeLazy = BooleanOptionItem.Create(Id + 11, "TaskBasedCrewCanBeLazy", false, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Lazy]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool CheckConflicts(PlayerControl player) { if (player.Is(CustomRoles.Ghoul) diff --git a/Roles/AddOns/Crewmate/Nimble.cs b/Roles/AddOns/Crewmate/Nimble.cs index b24c711a52..b22a4717cb 100644 --- a/Roles/AddOns/Crewmate/Nimble.cs +++ b/Roles/AddOns/Crewmate/Nimble.cs @@ -11,4 +11,10 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Nimble, canSetNum: true, tab: TabGroup.Addons); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Rascal.cs b/Roles/AddOns/Crewmate/Rascal.cs index 86083f1024..809552f938 100644 --- a/Roles/AddOns/Crewmate/Rascal.cs +++ b/Roles/AddOns/Crewmate/Rascal.cs @@ -15,6 +15,11 @@ public void SetupCustomOption() RascalAppearAsMadmate = BooleanOptionItem.Create(Id + 10, "RascalAppearAsMadmate", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Rascal]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool AppearAsMadmate(PlayerControl player) => RascalAppearAsMadmate.GetBool() && player.Is(CustomRoles.Rascal); } diff --git a/Roles/AddOns/Crewmate/Torch.cs b/Roles/AddOns/Crewmate/Torch.cs index 69e895eb79..e70f9d2015 100644 --- a/Roles/AddOns/Crewmate/Torch.cs +++ b/Roles/AddOns/Crewmate/Torch.cs @@ -17,7 +17,12 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Multiplier); TorchAffectedByLights = BooleanOptionItem.Create(Id +11, "TorchAffectedByLights", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Torch]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static void ApplyGameOptions(IGameOptions opt) { if (!Utils.IsActive(SystemTypes.Electrical)) diff --git a/Roles/AddOns/Crewmate/Workhorse.cs b/Roles/AddOns/Crewmate/Workhorse.cs index 635444d06b..38fe0cf423 100644 --- a/Roles/AddOns/Crewmate/Workhorse.cs +++ b/Roles/AddOns/Crewmate/Workhorse.cs @@ -32,20 +32,29 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Pieces); OptionSnitchCanBeWorkhorse = BooleanOptionItem.Create(Id + 14, "SnitchCanBeWorkhorse", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); } - public static void Init() + public void Init() { - playerIdList.Clear(); IsEnable = false; + playerIdList.Clear(); AssignOnlyToCrewmate = OptionAssignOnlyToCrewmate.GetBool(); NumLongTasks = OptionNumLongTasks.GetInt(); NumShortTasks = OptionNumShortTasks.GetInt(); } - public static void Add(byte playerId) + public void Add(byte playerId, bool gameIsLoading = true) + { } + public static void AddMidGame(byte playerId) { playerIdList.Add(playerId); IsEnable = true; } + public void Remove(byte playerId) + { + playerIdList.Remove(playerId); + + if (!playerIdList.Any()) + IsEnable = false; + } public static bool IsThisRole(byte playerId) => playerIdList.Contains(playerId); public static (bool, int, int) TaskData => (false, NumLongTasks, NumShortTasks); private static bool IsAssignTarget(PlayerControl pc) @@ -75,7 +84,7 @@ public static bool OnAddTask(PlayerControl pc) if (AmongUsClient.Instance.AmHost) { - Add(pc.PlayerId); + AddMidGame(pc.PlayerId); pc.Data.RpcSetTasks(new Il2CppStructArray(0)); // Redistribute tasks pc.SyncSettings(); Utils.NotifyRoles(SpecifySeer: pc); diff --git a/Roles/AddOns/IAddon.cs b/Roles/AddOns/IAddon.cs index 0305acaac0..3e0a780539 100644 --- a/Roles/AddOns/IAddon.cs +++ b/Roles/AddOns/IAddon.cs @@ -17,6 +17,9 @@ public interface IAddon public AddonTypes Type { get; } public void SetupCustomOption(); + public void Init(); + public void Add(byte playerId, bool gameIsLoading = true); + public void Remove(byte playerId); public void OnFixedUpdate(PlayerControl pc) { } public void OnFixedUpdateLowLoad(PlayerControl pc) diff --git a/Roles/AddOns/Impostor/Circumvent.cs b/Roles/AddOns/Impostor/Circumvent.cs index b3440e32e1..07ab4d8e3a 100644 --- a/Roles/AddOns/Impostor/Circumvent.cs +++ b/Roles/AddOns/Impostor/Circumvent.cs @@ -10,6 +10,11 @@ public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Circumvent, canSetNum: true, tab: TabGroup.Addons); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool CantUseVent(PlayerControl player) => player.Is(CustomRoles.Circumvent); } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Clumsy.cs b/Roles/AddOns/Impostor/Clumsy.cs index 1cb50bb9cd..b61f8265cf 100644 --- a/Roles/AddOns/Impostor/Clumsy.cs +++ b/Roles/AddOns/Impostor/Clumsy.cs @@ -9,7 +9,7 @@ public class Clumsy : IAddon private static OptionItem ChanceToMiss; - private static Dictionary HasMissed; + private static readonly Dictionary HasMissed = []; public void SetupCustomOption() { @@ -19,17 +19,17 @@ public void SetupCustomOption() .SetValueFormat(OptionFormat.Percent); } - public static void Init() + public void Init() { - HasMissed = []; + HasMissed.Clear(); } - public static void Add(byte PlayerId) + public void Add(byte playerId, bool gameIsLoading = true) { - HasMissed.Add(PlayerId, false); + HasMissed.Add(playerId, false); } - public static void Remove(byte player) + public void Remove(byte playerId) { - HasMissed.Remove(player); + HasMissed.Remove(playerId); } private static void MissChance(PlayerControl killer) diff --git a/Roles/AddOns/Impostor/LastImpostor.cs b/Roles/AddOns/Impostor/LastImpostor.cs index a253e6ca1f..4e47b270a5 100644 --- a/Roles/AddOns/Impostor/LastImpostor.cs +++ b/Roles/AddOns/Impostor/LastImpostor.cs @@ -16,8 +16,12 @@ public void SetupCustomOption() .SetParent(Options.CustomRoleSpawnChances[CustomRoles.LastImpostor]) .SetValueFormat(OptionFormat.Percent); } - public static void Init() => currentId = byte.MaxValue; - public static void Add(byte id) => currentId = id; + public void Init() => currentId = byte.MaxValue; + public void Add(byte playerId, bool gameIsLoading = true) + { } + public static void AddMidGame(byte id) => currentId = id; + public void Remove(byte playerId) + { } public static void SetKillCooldown() { if (currentId == byte.MaxValue) return; @@ -30,7 +34,6 @@ private static bool CanBeLastImpostor(PlayerControl pc) public static void SetSubRole() { - //ラストインポスターがすでにいれば処理不要 if (currentId != byte.MaxValue || !AmongUsClient.Instance.AmHost) return; if (Options.CurrentGameMode == CustomGameMode.FFA || !CustomRoles.LastImpostor.IsEnable() || Main.AliveImpostorCount != 1) return; @@ -39,7 +42,7 @@ public static void SetSubRole() if (CanBeLastImpostor(pc)) { pc.RpcSetCustomRole(CustomRoles.LastImpostor); - Add(pc.PlayerId); + AddMidGame(pc.PlayerId); SetKillCooldown(); pc.SyncSettings(); Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: false); diff --git a/Roles/AddOns/Impostor/Mare.cs b/Roles/AddOns/Impostor/Mare.cs index d2910379e9..94a26e88a0 100644 --- a/Roles/AddOns/Impostor/Mare.cs +++ b/Roles/AddOns/Impostor/Mare.cs @@ -7,7 +7,7 @@ public class Mare : IAddon { private const int Id = 23000; public AddonTypes Type => AddonTypes.Impostor; - public static List playerIdList = []; + public static readonly HashSet playerIdList = []; public static OptionItem KillCooldownInLightsOut; private static OptionItem SpeedInLightsOut; @@ -21,14 +21,17 @@ public void SetupCustomOption() KillCooldownInLightsOut = FloatOptionItem.Create(Id + 11, "MareKillCooldownInLightsOut", new(0f, 180f, 2.5f), 7.5f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) .SetValueFormat(OptionFormat.Seconds); } - - public static void Init() + public void Init() + { + playerIdList.Clear(); + } + public void Add(byte playerId, bool gameIsLoading = true) { - playerIdList = []; + playerIdList.Add(playerId); } - public static void Add(byte mare) + public void Remove(byte playerId) { - playerIdList.Add(mare); + playerIdList.Remove(playerId); } public static bool IsEnable => playerIdList.Any(); diff --git a/Roles/AddOns/Impostor/Mimic.cs b/Roles/AddOns/Impostor/Mimic.cs index 2ae38d26c7..2774d0a1e0 100644 --- a/Roles/AddOns/Impostor/Mimic.cs +++ b/Roles/AddOns/Impostor/Mimic.cs @@ -12,6 +12,11 @@ public void SetupCustomOption() SetupAdtRoleOptions(Id, CustomRoles.Mimic, canSetNum: true, tab: TabGroup.Addons); CanSeeDeadRolesOpt = BooleanOptionItem.Create(Id + 10, "MimicCanSeeDeadRoles", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Mimic]); } - + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool CanSeeDeadRoles(PlayerControl seer, PlayerControl target) => seer.Is(CustomRoles.Mimic) && CanSeeDeadRolesOpt.GetBool() && Main.VisibleTasksCount && !target.IsAlive(); } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Stealer.cs b/Roles/AddOns/Impostor/Stealer.cs index d907406361..a561a073df 100644 --- a/Roles/AddOns/Impostor/Stealer.cs +++ b/Roles/AddOns/Impostor/Stealer.cs @@ -18,6 +18,12 @@ public void SetupCustomOption() HideAdditionalVotes = BooleanOptionItem.Create(Id + 4, "HideAdditionalVotes", false, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Stealer]); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static int AddRealVotesNum(PlayerVoteArea ps) { return (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * TicketsPerKill.GetFloat()); diff --git a/Roles/AddOns/Impostor/Swift.cs b/Roles/AddOns/Impostor/Swift.cs index bed161fef1..f326ef865c 100644 --- a/Roles/AddOns/Impostor/Swift.cs +++ b/Roles/AddOns/Impostor/Swift.cs @@ -12,6 +12,12 @@ public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Swift, canSetNum: true, tab: TabGroup.Addons); } + public void Init() + { } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } public static bool OnCheckMurder(PlayerControl killer, PlayerControl target) { if (!DisableShieldAnimations.GetBool()) diff --git a/Roles/AddOns/Impostor/Tricky.cs b/Roles/AddOns/Impostor/Tricky.cs index 477bb5246a..25925151f1 100644 --- a/Roles/AddOns/Impostor/Tricky.cs +++ b/Roles/AddOns/Impostor/Tricky.cs @@ -13,10 +13,14 @@ public void SetupCustomOption() SetupAdtRoleOptions(Id, CustomRoles.Tricky, canSetNum: true, tab: TabGroup.Addons); EnabledDeathReasons = BooleanOptionItem.Create(Id + 11, "OnlyEnabledDeathReasons", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Tricky]); } - //public static void Init() - //{ - // randomReason = []; - //} + public void Init() + { + //randomReason = []; + } + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { } private static PlayerState.DeathReason ChangeRandomDeath() { PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().Where(IsReasonEnabled).ToArray(); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 4e61daeaa5..a19557de7e 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -116,10 +116,10 @@ public static void BuildCustomGameOptions(this PlayerControl player, ref IGameOp Watcher.RevealVotes(opt); break; case CustomRoles.Flash: - Flash.SetSpeed(player.PlayerId, false); + Flash.SetSpeed(player.PlayerId); break; case CustomRoles.Sloth: - Sloth.SetSpeed(player.PlayerId, false); + Sloth.SetSpeed(player.PlayerId); break; case CustomRoles.Torch: Torch.ApplyGameOptions(opt); diff --git a/Roles/Crewmate/Inspector.cs b/Roles/Crewmate/Inspector.cs index 6d85ad31df..6094cf1387 100644 --- a/Roles/Crewmate/Inspector.cs +++ b/Roles/Crewmate/Inspector.cs @@ -293,13 +293,11 @@ public static bool InspectCheckMsg(PlayerControl pc, string msg, bool isUI = fal { if (target1.Is(CustomRoles.Aware)) { - if (!Aware.AwareInteracted.ContainsKey(target1.PlayerId)) Aware.AwareInteracted[target1.PlayerId] = []; - if (!Aware.AwareInteracted[target1.PlayerId].Contains(GetRoleName(CustomRoles.Inspector))) Aware.AwareInteracted[target1.PlayerId].Add(GetRoleName(CustomRoles.Inspector)); + Aware.AwareInteracted[target1.PlayerId].Add(GetRoleName(CustomRoles.Inspector)); } if (target2.Is(CustomRoles.Aware)) { - if (!Aware.AwareInteracted.ContainsKey(target2.PlayerId)) Aware.AwareInteracted[target2.PlayerId] = []; - if (!Aware.AwareInteracted[target2.PlayerId].Contains(GetRoleName(CustomRoles.Inspector))) Aware.AwareInteracted[target2.PlayerId].Add(GetRoleName(CustomRoles.Inspector)); + Aware.AwareInteracted[target2.PlayerId].Add(GetRoleName(CustomRoles.Inspector)); } } MaxCheckLimit[pc.PlayerId] -= 1; diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 66bb6ec3f5..48023c1f0d 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -171,8 +171,8 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount target.RpcSetCustomRole(addon); target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSell"))); player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonDelivered"))); - - ExtendedPlayerControl.AddInSwitchAddons(target, target, addon); + + target.AddInSwitchAddons(target, addon); addonsSold[player.PlayerId] += 1; } diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 3d8a538edd..094ca18dae 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -176,6 +176,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = OverseerCooldown.GetFloat(); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { + Aware.OnCheckMurder(CustomRoles.Overseer, target); killer.SetKillCooldown(OverseerRevealTime.GetFloat()); if (!IsRevealed[(killer.PlayerId, target.PlayerId)] && !OverseerTimer.ContainsKey(killer.PlayerId)) { diff --git a/Roles/Impostor/Dazzler.cs b/Roles/Impostor/Dazzler.cs index c0765dc70d..2d1511fb8c 100644 --- a/Roles/Impostor/Dazzler.cs +++ b/Roles/Impostor/Dazzler.cs @@ -88,7 +88,7 @@ private static void DoDazzled(PlayerControl shapeshifter, PlayerControl target) { if (!PlayersDazzled[shapeshifter.PlayerId].Contains(target.PlayerId) && PlayersDazzled[shapeshifter.PlayerId].Count < DazzleLimit.GetInt()) { - Tired.Remove(shapeshifter.PlayerId); + Tired.RemoveMidGame(shapeshifter.PlayerId); target.Notify(ColorString(GetRoleColor(CustomRoles.Dazzler), GetString("DazzlerDazzled"))); PlayersDazzled[shapeshifter.PlayerId].Add(target.PlayerId); MarkEveryoneDirtySettings(); diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs index 2347536bd1..76ae58a64c 100644 --- a/Roles/Neutral/Bandit.cs +++ b/Roles/Neutral/Bandit.cs @@ -123,15 +123,13 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) } private void StealAddon(PlayerControl killer, PlayerControl target, CustomRoles? SelectedAddOn) { - ExtendedPlayerControl.AddInSwitchAddons(target, killer, IsAddon: SelectedAddOn); + target.AddInSwitchAddons(killer, IsAddon: SelectedAddOn); if (StealMode.GetValue() == 1) { Main.PlayerStates[target.PlayerId].RemoveSubRole((CustomRoles)SelectedAddOn); - if (SelectedAddOn == CustomRoles.Aware) Aware.AwareInteracted.Remove(target.PlayerId); Logger.Info($"Successfully removed {SelectedAddOn} addon from {target.GetNameWithRole()}", "Bandit"); - if (SelectedAddOn == CustomRoles.Aware && !Aware.AwareInteracted.ContainsKey(target.PlayerId)) Aware.AwareInteracted[target.PlayerId] = []; killer.RpcSetCustomRole((CustomRoles)SelectedAddOn); Logger.Info($"Successfully Added {SelectedAddOn} addon to {killer.GetNameWithRole()}", "Bandit"); } @@ -202,13 +200,12 @@ public override void OnReportDeadBody(PlayerControl reportash, NetworkedPlayerIn byte targetId = kvp2.Key; var target = Utils.GetPlayerById(targetId); if (target == null) continue; + CustomRoles role = kvp2.Value; Main.PlayerStates[targetId].RemoveSubRole(role); - if (role == CustomRoles.Aware) Aware.AwareInteracted.Remove(targetId); Logger.Info($"Successfully removed {role} addon from {target.GetNameWithRole()}", "Bandit"); SendRPC(targetId, role, true); - if (role == CustomRoles.Aware && !Aware.AwareInteracted.ContainsKey(targetId)) Aware.AwareInteracted[targetId] = []; _Player.RpcSetCustomRole(role); Logger.Info($"Successfully Added {role} addon to {_Player?.GetNameWithRole()}", "Bandit"); } From d58ed2482b653eee68e7b826be1ff6159b6649a3 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 22:04:51 +0800 Subject: [PATCH 553/778] Fix bug --- Patches/onGameStartedPatch.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3386158701..3c7320475f 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -472,9 +472,12 @@ public static System.Collections.IEnumerator AssignRoles() // if based role is Shapeshifter if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - foreach (var subRole in CustomRoleManager.AddonClasses.Values.ToArray()) + foreach (var (subRole, IAddon) in CustomRoleManager.AddonClasses) { - subRole?.Add(pc.PlayerId); + if (pc.Is(subRole)) + { + IAddon?.Add(pc.PlayerId); + } } } From d3bf887fa0f83366781adb0da50b48a890a9f171 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 22:15:34 +0800 Subject: [PATCH 554/778] Shuffle All Vents --- Helpers/EnumHelper.cs | 3 +-- Modules/Extensions/CollectionExtensions.cs | 18 ++++++++++++++++++ Roles/AddOns/Common/Prohibited.cs | 1 + Roles/AddOns/Common/Rebirth.cs | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Helpers/EnumHelper.cs b/Helpers/EnumHelper.cs index 6b181413a9..e72dfab686 100644 --- a/Helpers/EnumHelper.cs +++ b/Helpers/EnumHelper.cs @@ -28,8 +28,7 @@ public static List Achunk(int chunkSize, bool shuffle = false, F { List chunkedList = []; TEnum[] allValues = GetAllValues(); - var rnd = IRandom.Instance; - if (shuffle) allValues = allValues.Shuffle(rnd).ToArray(); + if (shuffle) allValues = allValues.Shuffle().ToArray(); if (exclude != null) allValues = allValues.Where(exclude).ToArray(); for (int i = 0; i < allValues.Length; i += chunkSize) diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs index 4d73e45bb9..b5b0fbe66f 100644 --- a/Modules/Extensions/CollectionExtensions.cs +++ b/Modules/Extensions/CollectionExtensions.cs @@ -68,6 +68,24 @@ public static IEnumerable Shuffle(this IEnumerable collection, IRandom } return list; } + /// + /// Shuffles all elements in a collection randomly + /// + /// The type of the collection + /// The collection to be shuffled + /// The shuffled collection + public static IEnumerable Shuffle(this IEnumerable collection) + { + var list = collection.ToList(); + int n = list.Count; + while (n > 1) + { + n--; + int k = IRandom.Instance.Next(n + 1); + (list[n], list[k]) = (list[k], list[n]); + } + return list; + } /// /// Filters a IEnumerable() of any duplicates diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index 89e86c6154..257c6ec7f2 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -67,6 +67,7 @@ public static void SetBlockedVents(byte playerId) if (coutBlokedVents <= 0) return; var allVents = ShipStatus.Instance.AllVents.ToList(); + allVents = allVents.Shuffle().ToList(); RememberBlokcedVents[playerId] = []; diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index dbffa64607..efb8ab9629 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -59,7 +59,7 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled list = [..VotedCount[pc.PlayerId].Select(x => GetPlayerById(x))]; } - var ViablePlayer = list.Where(x => x != pc).Shuffle(IRandom.Instance) + var ViablePlayer = list.Where(x => x != pc).Shuffle() .FirstOrDefault(x => x != null && !x.IsHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && /*All converters */ !x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer) && !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.Is(CustomRoles.Doppelganger) && !x.GetCustomRole().IsImpostor()); From e4167341df4852bb8d52f6ec5124fea97a83b1ae Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:39:25 -0500 Subject: [PATCH 555/778] Set rpc as fixed number --- Modules/RPC.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index ab55c6d489..ffbc69c092 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -74,7 +74,7 @@ enum CustomRPC : byte // 193/255 USED SetLoversPlayers, SetExecutionerTarget, RemoveExecutionerTarget, - BetterCheck, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! + BetterCheck = 150, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! SendFireworkerState, SetCurrentDousingTarget, SetEvilTrackerTarget, @@ -1125,4 +1125,4 @@ public static void Prefix(/*InnerNet.InnerNetClient __instance,*/ [HarmonyArgume { RPC.SendRpcLogger(targetNetId, callId, targetClientId); } -} \ No newline at end of file +} From e965e9c19ed3af433af26a86641b4eaf92e0883a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 19 Sep 2024 23:42:41 +0800 Subject: [PATCH 556/778] List => HashSet --- Roles/AddOns/Common/Aware.cs | 2 +- Roles/AddOns/Common/Bait.cs | 2 +- Roles/Core/AssignManager/AddonAssign.cs | 4 ++-- Roles/Crewmate/Admirer.cs | 2 +- Roles/Impostor/Anonymous.cs | 2 +- Roles/Impostor/Consigliere.cs | 2 +- Roles/Impostor/Dazzler.cs | 2 +- Roles/Impostor/Devourer.cs | 2 +- Roles/Impostor/DoubleAgent.cs | 4 ++-- Roles/Impostor/Fireworker.cs | 2 +- Roles/Neutral/Agitater.cs | 2 +- Roles/Neutral/Baker.cs | 10 +++++----- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Roles/AddOns/Common/Aware.cs b/Roles/AddOns/Common/Aware.cs index 5ea1572e0e..e83998dcbc 100644 --- a/Roles/AddOns/Common/Aware.cs +++ b/Roles/AddOns/Common/Aware.cs @@ -14,7 +14,7 @@ public class Aware : IAddon public static OptionItem NeutralCanBeAware; private static OptionItem AwareknowRole; - public static readonly Dictionary> AwareInteracted = []; + public static readonly Dictionary> AwareInteracted = []; public void SetupCustomOption() { diff --git a/Roles/AddOns/Common/Bait.cs b/Roles/AddOns/Common/Bait.cs index ee334ec961..a885c438f9 100644 --- a/Roles/AddOns/Common/Bait.cs +++ b/Roles/AddOns/Common/Bait.cs @@ -50,7 +50,7 @@ public static void SendNotify() { BaitAlive.Add(pc.PlayerId); } - List baitAliveList = []; + HashSet baitAliveList = []; foreach (var whId in BaitAlive.ToArray()) { PlayerControl whpc = whId.GetPlayer(); diff --git a/Roles/Core/AssignManager/AddonAssign.cs b/Roles/Core/AssignManager/AddonAssign.cs index f070b3caf2..1d58f36b2f 100644 --- a/Roles/Core/AssignManager/AddonAssign.cs +++ b/Roles/Core/AssignManager/AddonAssign.cs @@ -5,7 +5,7 @@ namespace TOHE.Roles.Core.AssignManager; public static class AddonAssign { - public static List AddonRolesList = []; + private static readonly HashSet AddonRolesList = []; private static bool NotAssignAddOnInGameStarted(CustomRoles role) { @@ -33,7 +33,7 @@ public static void StartSelect() { if (Options.CurrentGameMode == CustomGameMode.FFA) return; - AddonRolesList = []; + AddonRolesList.Clear(); foreach (var cr in CustomRolesHelper.AllRoles) { CustomRoles role = (CustomRoles)Enum.Parse(typeof(CustomRoles), cr.ToString()); diff --git a/Roles/Crewmate/Admirer.cs b/Roles/Crewmate/Admirer.cs index a8419d6049..280d5d4eaf 100644 --- a/Roles/Crewmate/Admirer.cs +++ b/Roles/Crewmate/Admirer.cs @@ -24,7 +24,7 @@ internal class Admirer : RoleBase private static OptionItem KnowTargetRole; private static OptionItem SkillLimit; - public static readonly Dictionary> AdmiredList = []; + public static readonly Dictionary> AdmiredList = []; public override void SetupCustomOption() { diff --git a/Roles/Impostor/Anonymous.cs b/Roles/Impostor/Anonymous.cs index 3eaa30ad17..a5643c2c6a 100644 --- a/Roles/Impostor/Anonymous.cs +++ b/Roles/Impostor/Anonymous.cs @@ -81,7 +81,7 @@ public override void OnShapeshift(PlayerControl shapeshifter, PlayerControl ssTa // No body found. Look for another body if (targetId == byte.MaxValue && DeadBodyList.Any()) - targetId = DeadBodyList[IRandom.Instance.Next(0, DeadBodyList.Count)]; + targetId = DeadBodyList.RandomElement(); // Anonymous report Self if (targetId == byte.MaxValue) diff --git a/Roles/Impostor/Consigliere.cs b/Roles/Impostor/Consigliere.cs index 85a807a652..875ca9c656 100644 --- a/Roles/Impostor/Consigliere.cs +++ b/Roles/Impostor/Consigliere.cs @@ -19,7 +19,7 @@ internal class Consigliere : RoleBase private static OptionItem DivinationMaxCount; private static readonly Dictionary DivinationCount = []; - private static readonly Dictionary> DivinationTarget = []; + private static readonly Dictionary> DivinationTarget = []; public override void SetupCustomOption() { diff --git a/Roles/Impostor/Dazzler.cs b/Roles/Impostor/Dazzler.cs index 2d1511fb8c..18abaf63a1 100644 --- a/Roles/Impostor/Dazzler.cs +++ b/Roles/Impostor/Dazzler.cs @@ -24,7 +24,7 @@ internal class Dazzler : RoleBase private static OptionItem ResetDazzledVisionOnDeath; private static OptionItem ShowShapeshiftAnimationsOpt; - private static Dictionary> PlayersDazzled = []; + private static Dictionary> PlayersDazzled = []; public override void SetupCustomOption() { diff --git a/Roles/Impostor/Devourer.cs b/Roles/Impostor/Devourer.cs index cf200e842b..f4c8d88ad9 100644 --- a/Roles/Impostor/Devourer.cs +++ b/Roles/Impostor/Devourer.cs @@ -27,7 +27,7 @@ internal class Devourer : RoleBase private static OptionItem ShowShapeshiftAnimationsOpt; private static readonly Dictionary NowCooldown = []; - private static readonly Dictionary> PlayerSkinsCosumed = []; + private static readonly Dictionary> PlayerSkinsCosumed = []; public override void SetupCustomOption() { diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index fbaa48af0b..ecc5c57489 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -15,14 +15,14 @@ internal class DoubleAgent : RoleBase { //===========================SETUP================================\\ private const int Id = 29000; - private static readonly List playerIdList = []; + private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsEnable => HasEnabled; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorSupport; //==================================================================\\ private static readonly List createdButtonsList = []; - private static readonly List CurrentBombedPlayers = []; + private static readonly HashSet CurrentBombedPlayers = []; private static float CurrentBombedTime = float.MaxValue; public static bool BombIsActive = false; public static bool CanBombInMeeting = true; diff --git a/Roles/Impostor/Fireworker.cs b/Roles/Impostor/Fireworker.cs index 20351ef690..6957605d8b 100644 --- a/Roles/Impostor/Fireworker.cs +++ b/Roles/Impostor/Fireworker.cs @@ -31,7 +31,7 @@ private enum FireworkerState private static OptionItem CanKill; private static readonly Dictionary nowFireworkerCount = []; - private static readonly Dictionary> FireworkerPosition = []; + private static readonly Dictionary> FireworkerPosition = []; private static readonly Dictionary state = []; private static readonly Dictionary FireworkerBombKill = []; private static int fireworkerCount = 1; diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index cc0d123d07..b8b91be4a5 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -10,7 +10,7 @@ internal class Agitater : RoleBase { //===========================SETUP================================\\ private const int Id = 15800; - private static readonly List playerIdList = []; + private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 2709e69bed..05590c250d 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -25,10 +25,10 @@ internal class Baker : RoleBase private static OptionItem BTOS2Baker; private static byte BreadID = 0; - private static readonly Dictionary> BreadList = []; - private static readonly Dictionary> RevealList = []; - private static readonly Dictionary> BarrierList = []; - public static readonly Dictionary> FamineList = []; + private static readonly Dictionary> BreadList = []; + private static readonly Dictionary> RevealList = []; + private static readonly Dictionary> BarrierList = []; + public static readonly Dictionary> FamineList = []; private static bool CanUseAbility; public static bool StarvedNonBreaded; @@ -269,7 +269,7 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para if (!CustomRoles.Famine.RoleExist()) return; if (exileIds.Contains(playerIdList.First())) return; if (StarvedNonBreaded) return; - var deathList = new List(); + var deathList = new HashSet(); PlayerControl baker = GetPlayerById(playerIdList.First()); foreach (var pc in Main.AllAlivePlayerControls) { From 5d4f326f1e3f798da3aae02ec9d92e23187d214c Mon Sep 17 00:00:00 2001 From: D1GQ Date: Thu, 19 Sep 2024 12:47:28 -0500 Subject: [PATCH 557/778] Put rpc on top --- Modules/RPC.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index ffbc69c092..44f769526a 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -22,6 +22,7 @@ enum CustomRPC : byte // 193/255 USED VersionCheck = 80, RequestRetryVersionCheck = 81, SyncCustomSettings = 100, // AUM use 101 rpc + BetterCheck = 150, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! SetDeathReason = 102, EndGame, PlaySound, @@ -74,8 +75,7 @@ enum CustomRPC : byte // 193/255 USED SetLoversPlayers, SetExecutionerTarget, RemoveExecutionerTarget, - BetterCheck = 150, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! - SendFireworkerState, + SendFireworkerState = 151, // Skip 150 SetCurrentDousingTarget, SetEvilTrackerTarget, SetDrawPlayer, From 9c2d69bd33c6289609e705b7bb02ad8b8731ba3a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:03:17 -0400 Subject: [PATCH 558/778] fix --- Roles/Neutral/Terrorist.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/Neutral/Terrorist.cs b/Roles/Neutral/Terrorist.cs index 2e6081d8ea..aabb0bfe74 100644 --- a/Roles/Neutral/Terrorist.cs +++ b/Roles/Neutral/Terrorist.cs @@ -55,7 +55,7 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn private static void CheckTerroristWin(NetworkedPlayerInfo terrorist) { var taskState = Utils.GetPlayerById(terrorist.PlayerId).GetPlayerTaskState(); - if (taskState.IsTaskFinished && (!Main.PlayerStates[terrorist.PlayerId].IsSuicide || CanTerroristSuicideWin.GetBool())) + if (taskState.IsTaskFinished && (!Main.PlayerStates[terrorist.PlayerId].IsSuicide || CanTerroristSuicideWin.GetBool()) && (Main.PlayerStates[terrorist.PlayerId].deathReason != PlayerState.DeathReason.Armageddon)) { if (!CustomWinnerHolder.CheckForConvertedWinner(terrorist.PlayerId)) { @@ -66,7 +66,7 @@ private static void CheckTerroristWin(NetworkedPlayerInfo terrorist) { if (pc.Is(CustomRoles.Terrorist)) { - if (Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote || Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Armageddon) + if (Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote) { pc.SetDeathReason(PlayerState.DeathReason.etc); } From 904c18df331438f5fc76652c5bb9e3683d34cda8 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:41:15 -0400 Subject: [PATCH 559/778] a --- Roles/Neutral/Baker.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index e54b80e180..f52d36bd08 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using Hazel; +using InnerNet; using System.Text; using TOHE.Roles.Core; using static TOHE.Options; @@ -60,6 +61,7 @@ public override void Add(byte playerId) FamineList[playerId] = []; CanUseAbility = true; StarvedNonBreaded = false; + BreadID = 0; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } @@ -79,7 +81,7 @@ private static (int, int) BreadedPlayerCount(byte playerId) public static void SendRPC(PlayerControl player, PlayerControl target) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(_Player); + writer.WriteNetObject(player); writer.Write(player.PlayerId); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); From be8702459ee818d0249034583fc09f818cab3771 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Thu, 19 Sep 2024 21:02:21 -0500 Subject: [PATCH 560/778] move up --- Modules/RPC.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 44f769526a..330e65ac36 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -19,10 +19,10 @@ enum CustomRPC : byte // 193/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 + BetterCheck = 150, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! VersionCheck = 80, RequestRetryVersionCheck = 81, SyncCustomSettings = 100, // AUM use 101 rpc - BetterCheck = 150, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! SetDeathReason = 102, EndGame, PlaySound, From f4ef76c5ae866bbfe6e7b851d19e74ad75c7b305 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 20 Sep 2024 20:03:35 +0800 Subject: [PATCH 561/778] Fix bugs & Some changes --- Modules/Utils.cs | 10 ++---- Patches/PlayerControlPatch.cs | 31 +---------------- Patches/PlayerJoinAndLeftPatch.cs | 3 -- Patches/onGameStartedPatch.cs | 1 - Roles/(Ghosts)/Crewmate/Ghastly.cs | 35 +++++++++----------- Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs | 32 +++++++++--------- Roles/(Ghosts)/Impostor/Bloodmoon.cs | 29 ++++++++-------- Roles/AddOns/Common/Tired.cs | 2 -- Roles/Core/RoleBase.cs | 13 -------- Roles/Crewmate/Altruist.cs | 2 ++ Roles/Crewmate/Merchant.cs | 2 ++ Roles/Crewmate/Overseer.cs | 7 ++-- Roles/Impostor/Kamikaze.cs | 12 ++++--- Roles/Impostor/YinYanger.cs | 10 +++--- Roles/Neutral/Berserker.cs | 24 +++++++++++--- Roles/Neutral/Quizmaster.cs | 11 +++++- 16 files changed, 100 insertions(+), 124 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 0e3f2ffa53..7aa60ad032 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1841,6 +1841,7 @@ public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || OnPlayerLeftPatch.StartingProcessing) return; + if (Main.MeetingIsStarted && !isForMeeting) return; if (Main.AllPlayerControls == null) return; //Do not update NotifyRoles during meetings @@ -1857,6 +1858,7 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || OnPlayerLeftPatch.StartingProcessing) return Task.CompletedTask; + if (Main.MeetingIsStarted && !isForMeeting) return Task.CompletedTask; if (Main.AllPlayerControls == null) return Task.CompletedTask; //Do not update NotifyRoles during meetings @@ -2285,7 +2287,6 @@ var Breason when BannedReason(Breason) => false, _ => true, }; } - public static HashSet> LateExileTask = []; public static void AfterMeetingTasks() { ChatManager.ClearLastSysMsg(); @@ -2314,13 +2315,6 @@ public static void AfterMeetingTasks() } } - if (LateExileTask.Any()) - { - LateExileTask.Do(t => t.Invoke(true)); - LateExileTask.Clear(); - } - - if (Statue.IsEnable) Statue.AfterMeetingTasks(); if (Burst.IsEnable) Burst.AfterMeetingTasks(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 6acb88a91c..040cdd189f 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using UnityEngine; using TOHE.Modules; +using TOHE.Patches; using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.Core.AssignManager; @@ -17,7 +18,6 @@ using TOHE.Roles.Neutral; using TOHE.Roles.Core; using static TOHE.Translator; -using TOHE.Patches; namespace TOHE; @@ -1804,32 +1804,6 @@ public static void Postfix(PlayerControl __instance) // Skip Tasks while Anti Blackout but not for real exiled if (AntiBlackout.SkipTasks && AntiBlackout.ExilePlayerId != __instance.PlayerId) return; - try - { - if (GameStates.IsNormalGame && GameStates.IsInGame && !GameEndCheckerForNormal.ForEndGame) - { - CustomRoleManager.AllEnabledRoles.Do(x => x.OnOtherTargetsReducedToAtoms(__instance)); - - var playerclass = __instance.GetRoleClass(); - - Action SelfExile = Utils.LateExileTask.FirstOrDefault(x => x.Target is RoleBase rb && rb._state.PlayerId == __instance.PlayerId) ?? playerclass.OnSelfReducedToAtoms; - if (GameStates.IsInTask && !GameStates.IsExilling) - { - SelfExile(false); - Utils.LateExileTask.RemoveWhere(x => x.Target is RoleBase rb && rb._state.PlayerId == __instance.PlayerId); - } - else - { - Utils.LateExileTask.RemoveWhere(x => x.Target is RoleBase rb && rb._state.PlayerId == __instance.PlayerId); - Utils.LateExileTask.Add(SelfExile); - } - } - } - catch (Exception exx) - { - Logger.Error($"Error after Targetreducedtoatoms: {exx}", "PlayerControl.Die"); - } - __instance.RpcRemovePet(); } } @@ -1854,10 +1828,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol { try { - Action SelfExile = __instance.GetRoleClass().OnSelfReducedToAtoms; GhostRoleAssign.GhostAssignPatch(__instance); // Sets customrole ghost if succeed - - if (target.GetCustomRole().IsGhostRole()) Utils.LateExileTask.Add(SelfExile); } catch (Exception error) { diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 4bcbe76fab..750fc893ab 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -358,9 +358,6 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client { if (GameStates.IsNormalGame && GameStates.IsInGame) { - - CustomRoleManager.AllEnabledRoles.ForEach(r => r.OnOtherTargetsReducedToAtoms(data.Character)); - if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) { foreach (var lovers in Main.LoversPlayers.ToArray()) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 3c7320475f..73f9ccb689 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -76,7 +76,6 @@ public static void Postfix(AmongUsClient __instance) Main.UnShapeShifter.Clear(); Main.OvverideOutfit.Clear(); Main.GameIsLoaded = false; - Utils.LateExileTask.Clear(); Main.LastNotifyNames.Clear(); Main.PlayerColors.Clear(); diff --git a/Roles/(Ghosts)/Crewmate/Ghastly.cs b/Roles/(Ghosts)/Crewmate/Ghastly.cs index 183c99aad0..d33b89f7a8 100644 --- a/Roles/(Ghosts)/Crewmate/Ghastly.cs +++ b/Roles/(Ghosts)/Crewmate/Ghastly.cs @@ -46,6 +46,7 @@ public override void Add(byte playerId) AbilityLimit = MaxPossesions.GetInt(); CustomRoleManager.OnFixedUpdateOthers.Add(OnFixUpdateOthers); + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) @@ -111,10 +112,8 @@ public override bool OnCheckProtect(PlayerControl angel, PlayerControl target) return false; } - private bool CheckConflicts(PlayerControl target) - { - return target != null && (!GhastlyKillAllies.GetBool() || target.GetCountTypes() != _Player.GetCountTypes()); - } + private bool CheckConflicts(PlayerControl target) => target != null && (!GhastlyKillAllies.GetBool() || target.GetCountTypes() != _Player.GetCountTypes()); + public override void OnFixedUpdate(PlayerControl pc) { var speed = Main.AllPlayerSpeed[pc.PlayerId]; @@ -161,38 +160,36 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr public override string GetLowerTextOthers(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - var IsMeeting = GameStates.IsMeeting || isForMeeting; - if (IsMeeting || (seer != seen && seer.IsAlive())) return ""; + if (isForMeeting || (seer != seen && seer.IsAlive())) return string.Empty; - var killer = killertarget.Item1; - var target = killertarget.Item2; + var (killer, target) = killertarget; if (killer == seen.PlayerId && target != byte.MaxValue) { var arrows = TargetArrow.GetArrows(GetPlayerById(killer), target); - var tar = GetPlayerById(target).GetRealName(); - if (tar == null) return ""; + var tar = target.GetPlayer().GetRealName(); + if (tar == null) return string.Empty; var colorstring = ColorString(GetRoleColor(CustomRoles.Ghastly), "" + tar + arrows); return colorstring; } - - - return ""; + return string.Empty; } - public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { + if (inMeeting) return; + var tuple = killertarget; - if (DeadPlayer.PlayerId == tuple.Item1 || DeadPlayer.PlayerId == tuple.Item2) + if (target.PlayerId == tuple.Item1 || target.PlayerId == tuple.Item2) { - _Player?.Notify(string.Format($"\n{GetString("GhastlyExpired")}\n", Utils.GetPlayerById(killertarget.Item1))); + _Player?.Notify(string.Format($"\n{GetString("GhastlyExpired")}\n", GetPlayerById(killertarget.Item1))); TargetArrow.Remove(killertarget.Item1, killertarget.Item2); - LastTime.Remove(DeadPlayer.PlayerId); + LastTime.Remove(target.PlayerId); KillerIsChosen = false; killertarget = (byte.MaxValue, byte.MaxValue); } } - public override string GetProgressText(byte playerId, bool cooms) => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Ghastly).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); - + public override string GetProgressText(byte playerId, bool cooms) + => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Ghastly).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); } diff --git a/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs b/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs index 5253c209da..6e93ff345f 100644 --- a/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs +++ b/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs @@ -19,7 +19,7 @@ internal class GuardianAngelTOHE : RoleBase private static OptionItem ProtectDur; private static OptionItem ImpVis; - public static readonly Dictionary PlayerShield = []; + private readonly Dictionary PlayerShield = []; public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.GuardianAngelTOHE); @@ -36,7 +36,8 @@ public override void Init() } public override void Add(byte playerId) { - CustomRoleManager.OnFixedUpdateOthers.Add(OnOthersFixUpdate); + CustomRoleManager.OnFixedUpdateOthers.Add(OnOthersFixedUpdate); + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); PlayerIds.Add(playerId); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) @@ -47,20 +48,16 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override bool OnCheckProtect(PlayerControl angel, PlayerControl target) { - if (!PlayerShield.ContainsKey(target.PlayerId)) - { - PlayerShield.Add(target.PlayerId, Utils.GetTimeStamp()); - } - else - { - PlayerShield[target.PlayerId] = Utils.GetTimeStamp(); - } + PlayerShield[target.PlayerId] = Utils.GetTimeStamp(); return true; } - public override void OnOtherTargetsReducedToAtoms(PlayerControl target) + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { - if (PlayerShield.ContainsKey(target.PlayerId)) - PlayerShield.Remove(target.PlayerId); + if (inMeeting) return; + + var targetId = target.PlayerId; + if (PlayerShield.ContainsKey(targetId)) + PlayerShield.Remove(targetId); } public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { @@ -75,10 +72,13 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr } return false; } - - private void OnOthersFixUpdate(PlayerControl player) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + PlayerShield.Clear(); + } + private void OnOthersFixedUpdate(PlayerControl player) { - if (PlayerShield.ContainsKey(player.PlayerId) && PlayerShield[player.PlayerId] + ProtectDur.GetInt() <= Utils.GetTimeStamp()) + if (PlayerShield.TryGetValue(player.PlayerId, out var timer) && timer + ProtectDur.GetInt() <= Utils.GetTimeStamp()) { PlayerShield.Remove(player.PlayerId); } diff --git a/Roles/(Ghosts)/Impostor/Bloodmoon.cs b/Roles/(Ghosts)/Impostor/Bloodmoon.cs index fa21142901..0bcbc06d6c 100644 --- a/Roles/(Ghosts)/Impostor/Bloodmoon.cs +++ b/Roles/(Ghosts)/Impostor/Bloodmoon.cs @@ -22,9 +22,10 @@ internal class Bloodmoon : RoleBase public static OptionItem KillCooldown; public static OptionItem CanKillNum; private static OptionItem TimeTilDeath; - - public static readonly Dictionary PlayerDie = []; - public static readonly Dictionary LastTime = []; + + private readonly Dictionary PlayerDie = []; + private readonly Dictionary LastTime = []; + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Bloodmoon); @@ -44,7 +45,7 @@ public override void Add(byte PlayerId) { AbilityLimit = CanKillNum.GetInt(); CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOther); - + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } // EAC bans players when GA uses sabotage public override bool CanUseSabotage(PlayerControl pc) => false; @@ -53,7 +54,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.GuardianAngelCooldown = KillCooldown.GetFloat(); AURoleOptions.ProtectionDurationSeconds = 0f; } - public void SendRPC(byte targetId, bool add) + private void SendRPC(byte targetId, bool add) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); writer.WriteNetObject(_Player); @@ -99,12 +100,13 @@ public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) } return false; } - public override string GetProgressText(byte playerId, bool cooms) => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Bloodmoon).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override string GetProgressText(byte playerId, bool cooms) + => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Bloodmoon).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + private void OnFixedUpdateOther(PlayerControl player) { - var IsMeeting = GameStates.IsMeeting; var playerid = player.PlayerId; - if (LastTime.TryGetValue(playerid, out var lastTime) && lastTime + 1 <= GetTimeStamp() && !IsMeeting) + if (LastTime.TryGetValue(playerid, out var lastTime) && lastTime + 1 <= GetTimeStamp()) { LastTime[playerid] = GetTimeStamp(); PlayerDie[playerid]--; @@ -118,9 +120,9 @@ private void OnFixedUpdateOther(PlayerControl player) } } } - public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { - var DeadPlayerId = DeadPlayer.PlayerId; + var DeadPlayerId = target.PlayerId; if (LastTime.ContainsKey(DeadPlayerId)) LastTime.Remove(DeadPlayerId); @@ -133,10 +135,5 @@ public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) } public override string GetLowerTextOthers(PlayerControl seer, PlayerControl player = null, bool isForMeeting = false, bool isForHud = false) - { - if (GameStates.IsMeeting || isForMeeting) return string.Empty; - var playerid = player.PlayerId; - - return PlayerDie.TryGetValue(playerid, out var DeathTimer) ? ColorString(GetRoleColor(CustomRoles.Bloodmoon), GetString("DeathTimer").Replace("{DeathTimer}", DeathTimer.ToString())) : string.Empty; - } + => !isForMeeting && PlayerDie.TryGetValue(player.PlayerId, out var DeathTimer) ? ColorString(GetRoleColor(CustomRoles.Bloodmoon), GetString("DeathTimer").Replace("{DeathTimer}", DeathTimer.ToString())) : string.Empty; } diff --git a/Roles/AddOns/Common/Tired.cs b/Roles/AddOns/Common/Tired.cs index c932880bec..d863792248 100644 --- a/Roles/AddOns/Common/Tired.cs +++ b/Roles/AddOns/Common/Tired.cs @@ -74,8 +74,6 @@ public static void AfterActionTasks(PlayerControl player) _ = new LateTask(() => { - if (!playerIdList.ContainsKey(player.PlayerId)) return; - Main.AllPlayerSpeed[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId] - SetSpeed.GetFloat() + tmpSpeed; player.MarkDirtySettings(); playerIdList[player.PlayerId] = false; diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 9a48d3601e..1ee8352660 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -227,19 +227,6 @@ public virtual void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl t public virtual void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { } - /// - /// A method to always check the state when targets have died (murder, exiled, execute etc..) - /// - public virtual void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) - { } - - - /// - /// A method to always check the state player has died (murder, exiled, execute etc..). If there is a meeting it will only happen after it. - /// - public virtual void OnSelfReducedToAtoms(bool IsAfterMeeting) - { } - /// /// When someone was died and need to run kill flash for specific role /// diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 037a8790ac..90ee984b1c 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -61,6 +61,8 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) { + if (!IsRevivingMode) return true; + var deadPlayer = deadBody.Object; var deadPlayerId = deadPlayer.PlayerId; var deadBodyObject = deadBody.GetDeadBody(); diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 48023c1f0d..9135cd1441 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -126,6 +126,8 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount && (!x.Is(CustomRoles.Stubborn)) && + !addon.IsConverted() + && CustomRolesHelper.CheckAddonConfilct(addon, x, checkLimitAddons: false) && (!Cleanser.CantGetAddon() || (Cleanser.CantGetAddon() && !x.Is(CustomRoles.Cleansed))) diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 094ca18dae..7f3fde0730 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -253,8 +253,11 @@ public override void OnFixedUpdate(PlayerControl player) public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { if (_Player == null) return; - var farTarget = OverseerTimer[_Player.PlayerId].Item1; - farTarget?.RpcSetSpecificScanner(_Player, false); + if (OverseerTimer.TryGetValue(_Player.PlayerId, out var data)) + { + var farTarget = data.Item1; + farTarget?.RpcSetSpecificScanner(_Player, false); + } OverseerTimer.Clear(); SendTimerRPC(0, byte.MaxValue); diff --git a/Roles/Impostor/Kamikaze.cs b/Roles/Impostor/Kamikaze.cs index 6131a0de98..ce137407c6 100644 --- a/Roles/Impostor/Kamikaze.cs +++ b/Roles/Impostor/Kamikaze.cs @@ -37,11 +37,11 @@ public override void Add(byte playerId) var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) - { - return KamikazedList.Contains(seen.PlayerId) ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Kamikaze), "∇") : string.Empty; - } + => KamikazedList.Contains(seen.PlayerId) ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Kamikaze), "∇") : string.Empty; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { @@ -71,15 +71,17 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } - public override void OnSelfReducedToAtoms(bool IsAfterMeeting) + public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { + if (_Player == null || _Player.IsDisconnected()) return; + foreach (var BABUSHKA in KamikazedList) { var pc = Utils.GetPlayerById(BABUSHKA); if (!pc.IsAlive()) continue; pc.SetDeathReason(PlayerState.DeathReason.Targeted); - if (!IsAfterMeeting) + if (!inMeeting) { pc.RpcMurderPlayer(pc); } diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index e71b76239b..45dbcb4f6e 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -31,6 +31,7 @@ public override void Init() public override void Add(byte playerId) { Yanged[playerId] = new(); + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); private static bool CheckAvailability() @@ -69,10 +70,11 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf { Yanged[_state.PlayerId] = new(); } - public override void OnOtherTargetsReducedToAtoms(PlayerControl DeadPlayer) + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { - if (Yanged.TryGetValue(DeadPlayer.PlayerId, out _)) - Yanged[DeadPlayer.PlayerId] = new(); + if (inMeeting) return; + if (Yanged.TryGetValue(target.PlayerId, out _)) + Yanged[target.PlayerId] = new(); } public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) { @@ -86,7 +88,7 @@ public override void OnFixedUpdate(PlayerControl pc) var (yin, yang) = Yanged[pc.PlayerId]; if (!yin || !yang) return; - if (Utils.GetDistance(yin.GetCustomPosition(), yang.GetCustomPosition()) < 1.5f) + if (GetDistance(yin.GetCustomPosition(), yang.GetCustomPosition()) < 1.5f) { yin.SetDeathReason(PlayerState.DeathReason.Equilibrium); yin.RpcMurderPlayer(yang); diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 13287cd500..f8525a1473 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -1,9 +1,11 @@ -using TOHE.Modules; +using AmongUs.GameOptions; +using TOHE.Roles.Core; +using Hazel; +using InnerNet; +using TOHE.Modules; using TOHE.Roles.Impostor; using static TOHE.Options; using static TOHE.Translator; -using AmongUs.GameOptions; -using TOHE.Roles.Core; namespace TOHE.Roles.Neutral; @@ -38,7 +40,7 @@ internal class Berserker : RoleBase private static OptionItem BerserkerCanVent; public static OptionItem WarCanVent; - private static readonly Dictionary BerserkerKillMax = []; + private readonly Dictionary BerserkerKillMax = []; public override void SetupCustomOption() { @@ -86,7 +88,19 @@ public override void Remove(byte playerId) { BerserkerKillMax.Remove(playerId); } + private void SendRPC() + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(_Player); + writer.WritePacked(BerserkerKillMax[_Player.PlayerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + var killCount = reader.ReadPackedInt32(); + BerserkerKillMax[_Player.PlayerId] = killCount; + } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) @@ -99,6 +113,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (target.IsNeutralApocalypse()) return false; + bool noScav = true; if (BerserkerKillMax[killer.PlayerId] < BerserkerMax.GetInt()) { @@ -161,6 +176,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Main.AllPlayerKillCooldown[killer.PlayerId] = WarKillCooldown.GetFloat(); } + SendRPC(); return noScav; } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 533a633e84..ee7129bb56 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -257,6 +257,7 @@ private void DoQuestion() } public override void OnMeetingHudStart(PlayerControl pc) { + if (Player == null) return; if (pc.PlayerId == Player.PlayerId && MarkedPlayer != byte.MaxValue) { AddMsg(GetString("QuizmasterChat.Marked").Replace("{QMTARGET}", Utils.GetPlayerById(MarkedPlayer)?.GetRealName(isMeeting: true)).Replace("{QMQUESTION}", Question.HasQuestionTranslation ? GetString("QuizmasterQuestions." + Question.Question) : Question.Question), pc.PlayerId, GetString("QuizmasterChat.Title")); @@ -264,7 +265,7 @@ public override void OnMeetingHudStart(PlayerControl pc) } public override void OnOthersMeetingHudStart(PlayerControl pc) { - if (!Utils.GetPlayerById(MarkedPlayer).IsAlive()) return; + if (pc == null || Player == null || !MarkedPlayer.GetPlayer().IsAlive()) return; if (pc.PlayerId == MarkedPlayer) { @@ -288,6 +289,8 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex public override void AfterMeetingTasks() { + if (Player == null) return; + firstSabotageOfRound = Sabotages.None; //killsForRound = 0; //allowedVenting = true; @@ -301,6 +304,8 @@ public override void AfterMeetingTasks() public static void ResetMarkedPlayer(bool canMarkAgain = true) { + if (Player == null) return; + if (canMarkAgain) AlreadyMarked = false; @@ -376,6 +381,7 @@ private static void KillPlayer(PlayerControl plrToKill) private static void RightAnswer(PlayerControl target) { + if (Player == null || target == null) return; lastReportedColor = thisReportedColor; foreach (var plr in Main.AllPlayerControls) { @@ -391,6 +397,7 @@ private static void RightAnswer(PlayerControl target) private static void WrongAnswer(PlayerControl target, string wrongAnswer, string rightAnswer) { + if (Player == null) return; lastReportedColor = thisReportedColor; KillPlayer(target); foreach (var plr in Main.AllPlayerControls) @@ -405,6 +412,7 @@ private static void WrongAnswer(PlayerControl target, string wrongAnswer, string } public static void AnswerByChat(PlayerControl plr, string[] args) { + if (Player == null) return; if (MarkedPlayer == plr.PlayerId) { var answerSyntaxValid = args.Length == 2; @@ -439,6 +447,7 @@ public static void AnswerByChat(PlayerControl plr, string[] args) public static void ShowQuestion(PlayerControl plr) { + if (Player == null) return; if (plr.PlayerId == MarkedPlayer) { Utils.SendMessage(GetString("QuizmasterChat.MarkedBy").Replace("{QMCOLOR}", Utils.GetRoleColorCode(CustomRoles.Quizmaster)).Replace("{QMQUESTION}", Question.HasQuestionTranslation ? GetString("QuizmasterQuestions." + Question.Question) : Question.Question), MarkedPlayer, GetString("QuizmasterChat.Title")); From f926a989c9a8842f88f12f02be84487e861b33a9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 20 Sep 2024 20:09:18 +0800 Subject: [PATCH 562/778] Change --- Modules/RPC.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index e0058530bb..7913941646 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -15,7 +15,7 @@ namespace TOHE; -enum CustomRPC : byte // 193/255 USED +enum CustomRPC : byte // 192/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 @@ -74,8 +74,8 @@ enum CustomRPC : byte // 193/255 USED SniperSync, SetLoversPlayers, SetExecutionerTarget, - RemoveExecutionerTarget, - SendFireworkerState = 151, // Skip 150 + RemoveExecutionerTarget = 149, + SendFireworkerState = 151, // BetterCheck used 150 SetCurrentDousingTarget, SetEvilTrackerTarget, SetDrawPlayer, From e61c4e0251264151f1a069ce11b00c1f166f8354 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 20 Sep 2024 20:17:41 +0800 Subject: [PATCH 563/778] Fix IAddon.Add --- Modules/GameState.cs | 9 +++++++++ Modules/RPC.cs | 5 ----- Patches/IntroPatch.cs | 2 +- Patches/ShipStatusPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 8 -------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index feaf5feeb2..1bbc40dde1 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -128,6 +128,15 @@ public void SetSubRole(CustomRoles role, PlayerControl pc = null) if (!SubRoles.Contains(role)) SubRoles.Add(role); + if (CustomRoleManager.AddonClasses.TryGetValue(role, out var IAddOn)) + { + var target = PlayerId.GetPlayer(); + if (target != null) + { + IAddOn?.Add(target.PlayerId, !Main.IntroDestroyed); + } + } + if (role.IsConverted()) { SubRoles.RemoveAll(AddON => AddON != role && AddON.IsConverted()); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 7913941646..991c976767 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -992,11 +992,6 @@ public static void SetCustomRole(byte targetId, CustomRoles role) else if (role >= CustomRoles.NotAssigned) //500:NoSubRole 501~:SubRole { Main.PlayerStates[targetId].SetSubRole(role); - - if (CustomRoleManager.AddonClasses.TryGetValue(role, out var IAddon)) - { - IAddon?.Add(targetId); - } switch (role) { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 6d8b9e5915..4524c278bf 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -61,7 +61,7 @@ public static void Prefix() { Logger.Warn($"Game ended? {AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd}", "ShipStatus.Begin"); } - }, 4f, "Assing Task For All"); + }, 5f, "Assing Task For All"); } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index bba910b213..8ac2307f37 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -247,7 +247,7 @@ public static void Postfix() { Logger.CurrentMethod(); - if (RolesIsAssigned && !Main.IntroDestroyed && GameStates.IsNormalGame) + if (RolesIsAssigned && GameStates.IsNormalGame) { foreach (var player in Main.AllPlayerControls) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 73f9ccb689..00238ecb2f 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -470,14 +470,6 @@ public static System.Collections.IEnumerator AssignRoles() // if based role is Shapeshifter if (roleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - - foreach (var (subRole, IAddon) in CustomRoleManager.AddonClasses) - { - if (pc.Is(subRole)) - { - IAddon?.Add(pc.PlayerId); - } - } } EndOfSelectRolePatch: From b92c495856751673482fca768da079dfd62813ba Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 20 Sep 2024 21:42:36 +0800 Subject: [PATCH 564/778] OnCheckForEndVoting In RoleBase --- Patches/ExilePatch.cs | 2 +- Patches/MeetingHudPatch.cs | 23 ++++++------ Roles/Core/RoleBase.cs | 5 +++ Roles/Impostor/Witch.cs | 4 +- Roles/Neutral/Baker.cs | 69 +++++++++++++++------------------- Roles/Neutral/HexMaster.cs | 5 ++- Roles/Neutral/SoulCollector.cs | 50 ++++++++++++------------ Roles/Neutral/Virus.cs | 45 ++++++++++------------ 8 files changed, 95 insertions(+), 108 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 6a68d45224..f238570eee 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -158,7 +158,7 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { exiled.Object.RpcExileV2(); } - }, 1.1f, "Restore IsDead Task"); + }, 0.7f, "Restore IsDead Task"); _ = new LateTask(() => { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 3036d7ba03..9f25f25700 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -595,7 +595,7 @@ public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathR var AddedIdList = new List(); foreach (var playerId in playerIds) { - var pc = GetPlayerById(playerId); + var pc = playerId.GetPlayer(); if (pc == null) return; if (pc.Is(CustomRoles.Susceptible)) { @@ -610,23 +610,22 @@ public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathR } private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, params byte[] playerIds) { - Witch.OnCheckForEndVoting(deathReason, playerIds); - HexMaster.OnCheckForEndVoting(deathReason, playerIds); - Virus.OnCheckForEndVoting(deathReason, playerIds); - Famine.OnCheckForEndVoting(deathReason, playerIds); - Death.OnCheckForEndVoting(deathReason, playerIds); + foreach (var roleClass in CustomRoleManager.AllEnabledRoles.ToArray()) + { + roleClass?.OnCheckForEndVoting(deathReason, playerIds); + } foreach (var playerId in playerIds) { - if (CustomRoles.Lovers.IsEnable() && !Main.isLoversDead && Main.LoversPlayers.Any(lp => lp.PlayerId == playerId)) + if (CustomRoles.Lovers.IsEnable() && deathReason == PlayerState.DeathReason.Vote && !Main.isLoversDead && Main.LoversPlayers.FirstOrDefault(lp => lp.PlayerId == playerId) != null) { FixedUpdateInNormalGamePatch.LoversSuicide(playerId, true); } - RevengeOnExile(playerId/*, deathReason*/); + RevengeOnExile(playerId); } } - private static void RevengeOnExile(byte playerId/*, PlayerState.DeathReason deathReason*/) + private static void RevengeOnExile(byte playerId) { var player = GetPlayerById(playerId); if (player == null) return; @@ -638,12 +637,12 @@ private static void RevengeOnExile(byte playerId/*, PlayerState.DeathReason deat Logger.Info($"{player.GetNameWithRole()} revenge:{target.GetNameWithRole()}", "RevengeOnExile"); } - private static PlayerControl PickRevengeTarget(PlayerControl exiledplayer)//道連れ先選定 + private static PlayerControl PickRevengeTarget(PlayerControl exiledplayer) { List TargetList = []; foreach (var candidate in Main.AllAlivePlayerControls) { - if (candidate == exiledplayer || Main.AfterMeetingDeathPlayers.ContainsKey(candidate.PlayerId)) continue; + if (candidate.PlayerId == exiledplayer.PlayerId || Main.AfterMeetingDeathPlayers.ContainsKey(candidate.PlayerId)) continue; } if (TargetList == null || TargetList.Count == 0) return null; var rand = IRandom.Instance; @@ -692,7 +691,7 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP } - if (!voter.GetRoleClass().HasVoted && !voter.GetRoleClass().CheckVote(voter, target)) + if (!voter.GetRoleClass().HasVoted && voter.GetRoleClass().CheckVote(voter, target) == false) { Logger.Info($"Canceling {voter.GetRealName()}'s vote because of {voter.GetCustomRole()}", "CastVotePatch.RoleBase.CheckVote"); voter.GetRoleClass().HasVoted = true; diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 1ee8352660..b17497c6b0 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -338,6 +338,11 @@ public virtual void MeetingHudClear() /// public virtual string PVANameText(PlayerVoteArea pva, PlayerControl seer, PlayerControl target) => string.Empty; + /// + /// Used when player should be dead after meeting + /// + public virtual void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + { } /// /// Notify a specific role about something after the meeting was ended. /// diff --git a/Roles/Impostor/Witch.cs b/Roles/Impostor/Witch.cs index 4ef5edc97b..ad89b69130 100644 --- a/Roles/Impostor/Witch.cs +++ b/Roles/Impostor/Witch.cs @@ -155,9 +155,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; + if (deathReason != PlayerState.DeathReason.Vote) return; foreach (var id in exileIds) { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index c0cb461552..35db8d1b6a 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -26,7 +26,7 @@ internal class Baker : RoleBase private static OptionItem BTOS2Baker; private static byte BreadID = 0; - private static readonly Dictionary> BreadList = []; + public static readonly Dictionary> BreadList = []; private static readonly Dictionary> RevealList = []; private static readonly Dictionary> BarrierList = []; public static readonly Dictionary> FamineList = []; @@ -133,10 +133,7 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b public override bool CanUseImpostorVentButton(PlayerControl pc) => true; public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Main.AllPlayerKillCooldown[id]; public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("BakerKillButtonText")); - public static bool HasBread(byte pc, byte target) - { - return BreadList[pc].Contains(target); - } + public static bool HasBread(byte pc, byte target) => BreadList.TryGetValue(pc, out var breadList) && breadList.Contains(target); private static bool AllHasBread(PlayerControl player) { if (!player.Is(CustomRoles.Baker)) return false; @@ -265,34 +262,6 @@ public override void OnFixedUpdate(PlayerControl player) player.Notify(GetString("BakerToFamine")); player.RpcGuardAndKill(player); } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) - { - if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; - if (!CustomRoles.Famine.RoleExist()) return; - if (exileIds.Contains(playerIdList.First())) return; - if (StarvedNonBreaded) return; - var deathList = new HashSet(); - PlayerControl baker = GetPlayerById(playerIdList.First()); - foreach (var pc in Main.AllAlivePlayerControls) - { - if (pc.IsNeutralApocalypse() || HasBread(baker.PlayerId, pc.PlayerId)) continue; - if (baker != null && baker.IsAlive()) - { - if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) - { - pc.SetRealKiller(baker); - deathList.Add(pc.PlayerId); - } - } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } - } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Starved, [.. deathList]); - BreadList[baker.PlayerId].Clear(); - StarvedNonBreaded = true; - } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -306,7 +275,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl internal class Famine : RoleBase { //===========================SETUP================================\\ - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Baker); + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Famine); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; @@ -326,6 +295,7 @@ public override void Add(byte playerId) public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("FamineKillButtonText")); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => Baker.FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"⁂" : string.Empty; + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (target.IsNeutralApocalypse()) killer.Notify(GetString("FamineCantStarveApoc")); @@ -357,8 +327,8 @@ public override void OnReportDeadBody(PlayerControl sylveon, NetworkedPlayerInfo { foreach (var tar in pc.Value) { - var target = GetPlayerById(tar); - var killer = GetPlayerById(pc.Key); + var target = tar.GetPlayer(); + var killer = pc.Key.GetPlayer(); if (killer == null || target == null) continue; target.RpcExileV2(); target.SetRealKiller(killer); @@ -370,9 +340,32 @@ public override void OnReportDeadBody(PlayerControl sylveon, NetworkedPlayerInfo } } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - Baker.OnCheckForEndVoting(deathReason, exileIds); + if (_Player == null || deathReason != PlayerState.DeathReason.Vote) return; + if (exileIds.Contains(_Player.PlayerId) || Baker.StarvedNonBreaded) return; + + var deathList = new HashSet(); + var baker = _Player; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.IsNeutralApocalypse() || Baker.HasBread(baker.PlayerId, pc.PlayerId)) continue; + if (baker.IsAlive()) + { + if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + { + pc.SetRealKiller(baker); + deathList.Add(pc.PlayerId); + } + } + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } + } + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Starved, [.. deathList]); + Baker.BreadList[baker.PlayerId].Clear(); + Baker.StarvedNonBreaded = true; } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index ed105bb1c9..e74069191c 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -178,9 +178,10 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t //キル処理終了させる return false; } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; + if (deathReason != PlayerState.DeathReason.Vote) return; + foreach (var id in exileIds) { if (HexedPlayer.ContainsKey(id)) diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index c49085a090..ce706ae1b3 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -175,31 +175,6 @@ public override void AfterMeetingTasks() } } } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) - { - if (!HasEnabled || deathReason != PlayerState.DeathReason.Vote) return; - if (!CustomRoles.Death.RoleExist()) return; - if (exileIds.Contains(playerIdList.First())) return; - var deathList = new List(); - PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); - foreach (var pc in Main.AllAlivePlayerControls) - { - if (pc.IsNeutralApocalypse()) continue; - if (sc != null && sc.IsAlive()) - { - if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) - { - pc.SetRealKiller(sc); - deathList.Add(pc.PlayerId); - } - } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } - } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [.. deathList]); - } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -226,9 +201,30 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override bool CanUseImpostorVentButton(PlayerControl pc) => SoulCollector.SoulCollectorCanVent.GetBool(); public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - SoulCollector.OnCheckForEndVoting(deathReason, exileIds); + if (_Player == null || deathReason != PlayerState.DeathReason.Vote) return; + if (exileIds.Contains(_Player.PlayerId)) return; + + var deathList = new List(); + var death = _Player; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.IsNeutralApocalypse()) continue; + if (death.IsAlive()) + { + if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + { + pc.SetRealKiller(death); + deathList.Add(pc.PlayerId); + } + } + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } + } + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Armageddon, [.. deathList]); } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs index f2bed789d2..27592ca9cc 100644 --- a/Roles/Neutral/Virus.cs +++ b/Roles/Neutral/Virus.cs @@ -28,9 +28,9 @@ internal class Virus : RoleBase private static OptionItem KillInfectedPlayerAfterMeeting; public static OptionItem ContagiousCountMode; - private static readonly HashSet InfectedBodies = []; - private static readonly HashSet InfectedPlayer = []; - private static readonly Dictionary VirusNotify = []; + private readonly HashSet InfectedBodies = []; + private readonly HashSet InfectedPlayer = []; + private readonly Dictionary VirusNotify = []; private enum ContagiousCountModeSelectList { @@ -41,7 +41,7 @@ private enum ContagiousCountModeSelectList public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Virus, 1, zeroOne: false); + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Virus); KillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Virus]) .SetValueFormat(OptionFormat.Seconds); CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); @@ -89,28 +89,23 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf if (KillInfectedPlayerAfterMeeting.GetBool()) { InfectedPlayer.Add(reporter.PlayerId); - - VirusNotify.Add(reporter.PlayerId, GetString("VirusNoticeMessage2")); + VirusNotify[reporter.PlayerId] = GetString("VirusNoticeMessage2"); } else { reporter.RpcSetCustomRole(CustomRoles.Contagious); - - VirusNotify.Add(reporter.PlayerId, GetString("VirusNoticeMessage")); + VirusNotify[reporter.PlayerId] = GetString("VirusNoticeMessage"); } Logger.Info("Setting up a career:" + reporter?.Data?.PlayerName + " = " + reporter.GetCustomRole().ToString() + " + " + CustomRoles.Contagious.ToString(), "Assign " + CustomRoles.Contagious.ToString()); } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (!KillInfectedPlayerAfterMeeting.GetBool()) return; - - PlayerControl virus = - Main.AllAlivePlayerControls.FirstOrDefault(a => a.GetCustomRole() == CustomRoles.Virus); - if (virus == null || deathReason != PlayerState.DeathReason.Vote) return; + if (!_Player.IsAlive() || deathReason != PlayerState.DeathReason.Vote || !KillInfectedPlayerAfterMeeting.GetBool()) return; + var virus = _Player; if (exileIds.Contains(virus.PlayerId)) { InfectedPlayer.Clear(); @@ -118,30 +113,28 @@ public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, para } var infectedIdList = new List(); - foreach (var pc in Main.AllAlivePlayerControls) + foreach (var infectedId in InfectedPlayer) { - bool isInfected = InfectedPlayer.Contains(pc.PlayerId); - if (!isInfected) continue; - - if (virus.IsAlive()) + var infected = infectedId.GetPlayer(); + if (virus.IsAlive() && infected != null) { - if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + if (!Main.AfterMeetingDeathPlayers.ContainsKey(infectedId)) { - pc.SetRealKiller(virus); - infectedIdList.Add(pc.PlayerId); + infected.SetRealKiller(virus); + infectedIdList.Add(infectedId); } } else { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + Main.AfterMeetingDeathPlayers.Remove(infectedId); } } CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Infected, [.. infectedIdList]); - RemoveInfectedPlayer(virus); + RemoveInfectedPlayer(); } - private static void RemoveInfectedPlayer(PlayerControl virus) + private void RemoveInfectedPlayer() { InfectedPlayer.Clear(); } @@ -157,7 +150,7 @@ public static string KnowRoleColor(PlayerControl seer, PlayerControl target) { if (seer.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Virus)) return Main.roleColors[CustomRoles.Virus]; if (seer.Is(CustomRoles.Virus) && target.Is(CustomRoles.Contagious)) return Main.roleColors[CustomRoles.Contagious]; - if (seer.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Contagious) && Virus.TargetKnowOtherTarget.GetBool()) return Main.roleColors[CustomRoles.Virus]; + if (seer.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Contagious) && TargetKnowOtherTarget.GetBool()) return Main.roleColors[CustomRoles.Virus]; return ""; } public override string GetProgressText(byte id, bool coooms) => Utils.ColorString(AbilityLimit >= 1 ? Utils.GetRoleColor(CustomRoles.Virus).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); From 37ef38fd0ef207f9ad7eb657d14fa52b583fcd10 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 00:39:22 +0800 Subject: [PATCH 565/778] Merge OnFixedUpdate && Improve some code --- Patches/MeetingHudPatch.cs | 14 ++++-- Patches/PlayerControlPatch.cs | 8 ++- Roles/(Ghosts)/Crewmate/Ghastly.cs | 13 ++--- Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs | 4 +- Roles/(Ghosts)/Impostor/Bloodmoon.cs | 8 +-- Roles/(Ghosts)/Impostor/Possessor.cs | 3 +- Roles/AddOns/Common/Glow.cs | 2 - Roles/AddOns/Common/Statue.cs | 31 +++++++++--- Roles/AddOns/Common/Tired.cs | 4 +- Roles/Core/CustomRoleManager.cs | 23 ++------- Roles/Core/RoleBase.cs | 7 +-- Roles/Crewmate/Addict.cs | 6 +-- Roles/Crewmate/Alchemist.cs | 14 +++--- Roles/Crewmate/Benefactor.cs | 19 ++++--- Roles/Crewmate/Chameleon.cs | 6 +-- Roles/Crewmate/Grenadier.cs | 3 +- Roles/Crewmate/Lighter.cs | 15 +++--- Roles/Crewmate/Overseer.cs | 8 +-- Roles/Crewmate/Spy.cs | 10 ++-- Roles/Crewmate/Telecommunication.cs | 7 +-- Roles/Crewmate/TimeMaster.cs | 4 +- Roles/Crewmate/Veteran.cs | 12 ++--- Roles/Crewmate/Witness.cs | 6 +-- Roles/Impostor/AntiAdminer.cs | 9 ++-- Roles/Impostor/BountyHunter.cs | 8 +-- Roles/Impostor/Butcher.cs | 16 +++--- Roles/Impostor/Chronomancer.cs | 12 ++--- Roles/Impostor/Deathpact.cs | 50 +++++++++--------- Roles/Impostor/DollMaster.cs | 53 +++++++++----------- Roles/Impostor/DoubleAgent.cs | 38 +++++++------- Roles/Impostor/EvilHacker.cs | 4 +- Roles/Impostor/Lightning.cs | 10 ++-- Roles/Impostor/Mastermind.cs | 7 +-- Roles/Impostor/Mercenary.cs | 2 +- Roles/Impostor/Penguin.cs | 4 +- Roles/Impostor/Pitfall.cs | 8 +-- Roles/Impostor/Puppeteer.cs | 9 ++-- Roles/Impostor/RiftMaker.cs | 26 +++++----- Roles/Impostor/Stealth.cs | 2 +- Roles/Impostor/Swooper.cs | 4 +- Roles/Impostor/Vampire.cs | 8 +-- Roles/Impostor/Warlock.cs | 2 +- Roles/Impostor/Wildling.cs | 27 +++++----- Roles/Impostor/YinYanger.cs | 7 +-- Roles/Neutral/Agitater.cs | 13 ++--- Roles/Neutral/Arsonist.cs | 2 +- Roles/Neutral/Baker.cs | 4 +- Roles/Neutral/BloodKnight.cs | 26 +++++----- Roles/Neutral/Glitch.cs | 19 +++---- Roles/Neutral/Jester.cs | 2 +- Roles/Neutral/Pelican.cs | 20 ++++---- Roles/Neutral/PlagueDoctor.cs | 2 +- Roles/Neutral/Poisoner.cs | 7 ++- Roles/Neutral/Revolutionist.cs | 13 +++-- Roles/Neutral/Seeker.cs | 4 +- Roles/Neutral/Shroud.cs | 12 ++--- Roles/Neutral/Solsticer.cs | 20 ++++---- Roles/Neutral/Spiritcaller.cs | 18 ++++--- Roles/Neutral/Vulture.cs | 4 +- Roles/Neutral/Wraith.cs | 16 +++--- 60 files changed, 361 insertions(+), 354 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9f25f25700..966fc5fd12 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1094,8 +1094,15 @@ public static void Postfix(MeetingHud __instance) if (MeetingStates.FirstMeeting) TemplateManager.SendTemplate("OnFirstMeeting", noErr: true); TemplateManager.SendTemplate("OnMeeting", noErr: true); - if (AmongUsClient.Instance.AmHost) - NotifyRoleSkillOnMeetingStart(); + try + { + if (AmongUsClient.Instance.AmHost) + NotifyRoleSkillOnMeetingStart(); + } + catch (Exception error) + { + Logger.Error($"Error after notify {error}", "NotifyRoleSkillOnMeetingStart"); + } if (AmongUsClient.Instance.AmHost) { @@ -1197,7 +1204,6 @@ public static void Postfix(MeetingHud __instance) { __instance.CheckForEndVoting(); } - // if (AmongUsClient.Instance.AmHost && Input.GetMouseButtonUp(1) && Input.GetKey(KeyCode.LeftControl)) { @@ -1218,14 +1224,12 @@ public static void Postfix(MeetingHud __instance) }); } - //投票结束时销毁全部技能按钮 if (!GameStates.IsVoting && __instance.lastSecond < 1) { if (GameObject.Find("ShootButton") != null) ClearShootButton(__instance, true); return; } - //会议技能UI处理 bufferTime--; if (bufferTime < 0 && __instance.discussionTimer > 0) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 3c622e5d74..605a750443 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1138,7 +1138,9 @@ public static Task DoPostfix(PlayerControl __instance) if (GameStates.IsInTask) { - CustomRoleManager.OnFixedUpdate(player); + CustomRoleManager.OnFixedUpdate(player, lowLoad, Utils.GetTimeStamp()); + + player.OnFixedAddonUpdate(lowLoad); if (Main.LateOutfits.TryGetValue(player.PlayerId, out var Method) && !player.CheckCamoflague()) { @@ -1147,12 +1149,8 @@ public static Task DoPostfix(PlayerControl __instance) Logger.Info($"Reset {player.GetRealName()}'s outfit", "LateOutfits..OnFixedUpdate"); } - player.OnFixedAddonUpdate(lowLoad); - if (!lowLoad) { - CustomRoleManager.OnFixedUpdateLowLoad(player); - if (Options.LadderDeath.GetBool() && player.IsAlive()) FallFromLadder.FixedUpdate(player); diff --git a/Roles/(Ghosts)/Crewmate/Ghastly.cs b/Roles/(Ghosts)/Crewmate/Ghastly.cs index d33b89f7a8..4bd7741d74 100644 --- a/Roles/(Ghosts)/Crewmate/Ghastly.cs +++ b/Roles/(Ghosts)/Crewmate/Ghastly.cs @@ -114,19 +114,20 @@ public override bool OnCheckProtect(PlayerControl angel, PlayerControl target) } private bool CheckConflicts(PlayerControl target) => target != null && (!GhastlyKillAllies.GetBool() || target.GetCountTypes() != _Player.GetCountTypes()); - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - var speed = Main.AllPlayerSpeed[pc.PlayerId]; + if (lowLoad) return; + var speed = Main.AllPlayerSpeed[player.PlayerId]; if (speed != GhastlySpeed.GetFloat()) { - Main.AllPlayerSpeed[pc.PlayerId] = GhastlySpeed.GetFloat(); - pc.MarkDirtySettings(); + Main.AllPlayerSpeed[player.PlayerId] = GhastlySpeed.GetFloat(); + player.MarkDirtySettings(); } } - public void OnFixUpdateOthers(PlayerControl player) + public void OnFixUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { if (killertarget.Item1 == player.PlayerId - && LastTime.TryGetValue(player.PlayerId, out var now) && now + PossessDur.GetInt() <= GetTimeStamp()) + && LastTime.TryGetValue(player.PlayerId, out var now) && now + PossessDur.GetInt() <= nowTime) { _Player?.Notify(string.Format($"\n{ GetString("GhastlyExpired")}\n", player.GetRealName())); TargetArrow.Remove(killertarget.Item1, killertarget.Item2); diff --git a/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs b/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs index 6e93ff345f..70374b9783 100644 --- a/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs +++ b/Roles/(Ghosts)/Crewmate/GuardianAngelTOHE.cs @@ -76,9 +76,9 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf { PlayerShield.Clear(); } - private void OnOthersFixedUpdate(PlayerControl player) + private void OnOthersFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (PlayerShield.TryGetValue(player.PlayerId, out var timer) && timer + ProtectDur.GetInt() <= Utils.GetTimeStamp()) + if (PlayerShield.TryGetValue(player.PlayerId, out var timer) && timer + ProtectDur.GetInt() <= nowTime) { PlayerShield.Remove(player.PlayerId); } diff --git a/Roles/(Ghosts)/Impostor/Bloodmoon.cs b/Roles/(Ghosts)/Impostor/Bloodmoon.cs index 0bcbc06d6c..57312a3111 100644 --- a/Roles/(Ghosts)/Impostor/Bloodmoon.cs +++ b/Roles/(Ghosts)/Impostor/Bloodmoon.cs @@ -103,12 +103,14 @@ public override bool OnCheckProtect(PlayerControl killer, PlayerControl target) public override string GetProgressText(byte playerId, bool cooms) => ColorString(AbilityLimit > 0 ? GetRoleColor(CustomRoles.Bloodmoon).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); - private void OnFixedUpdateOther(PlayerControl player) + private void OnFixedUpdateOther(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad || _Player == null) return; + var playerid = player.PlayerId; - if (LastTime.TryGetValue(playerid, out var lastTime) && lastTime + 1 <= GetTimeStamp()) + if (LastTime.TryGetValue(playerid, out var lastTime) && lastTime + 1 <= nowTime) { - LastTime[playerid] = GetTimeStamp(); + LastTime[playerid] = nowTime; PlayerDie[playerid]--; if (PlayerDie[playerid] <= 0) { diff --git a/Roles/(Ghosts)/Impostor/Possessor.cs b/Roles/(Ghosts)/Impostor/Possessor.cs index 87f63c9a93..5febb5e318 100644 --- a/Roles/(Ghosts)/Impostor/Possessor.cs +++ b/Roles/(Ghosts)/Impostor/Possessor.cs @@ -3,6 +3,7 @@ using UnityEngine; using static TOHE.Options; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles._Ghosts_.Impostor; @@ -48,7 +49,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.ProtectionDurationSeconds = 0f; } - private void OnFixedUpdateOther(PlayerControl target) + private void OnFixedUpdateOther(PlayerControl target, bool lowLoad, long nowTime) { if (_Player == null) return; diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index 2eb333b8a2..980a9bf1a5 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -82,9 +82,7 @@ public void OnFixedUpdateLowLoad(PlayerControl player) MarkedOnce[player.PlayerId] = false; return; } - if (!InRadius.ContainsKey(player.PlayerId)) InRadius[player.PlayerId] = []; var prevList = InRadius[player.PlayerId]; - if (!MarkedOnce.ContainsKey(player.PlayerId)) MarkedOnce[player.PlayerId] = false; InRadius[player.PlayerId] = Main.AllAlivePlayerControls .Where(target => target != null && !target.Is(CustomRoles.Glow) diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index 196fc82a35..9961eec273 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -1,4 +1,6 @@ -namespace TOHE.Roles.AddOns.Common; +using AmongUs.GameOptions; + +namespace TOHE.Roles.AddOns.Common; public class Statue : IAddon { @@ -48,12 +50,12 @@ public void Remove(byte playerId) public static void AfterMeetingTasks() { - foreach (var Statue in TempSpeed.Keys.ToArray()) + foreach (var (statue, speed) in TempSpeed) { - var pc = Utils.GetPlayerById(Statue); + var pc = statue.GetPlayer(); if (pc == null) continue; - float tmpFloat = TempSpeed[Statue]; - Main.AllPlayerSpeed[Statue] = Main.AllPlayerSpeed[Statue] - Main.AllPlayerSpeed[Statue] + tmpFloat; + + Main.AllPlayerSpeed[statue] = Main.AllPlayerSpeed[statue] - SlowDown.GetFloat() + speed; pc.MarkDirtySettings(); } Active = false; @@ -66,10 +68,25 @@ public static void AfterMeetingTasks() public void OnFixedUpdate(PlayerControl victim) { - if (!victim.Is(CustomRoles.Statue) || !victim.IsAlive()) return; + if (!victim.Is(CustomRoles.Statue)) return; + if (!victim.IsAlive() && victim != null) + { + var currentSpeed = Main.AllPlayerSpeed[victim.PlayerId]; + var normalSpeed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + if (currentSpeed != normalSpeed) + { + Main.AllPlayerSpeed[victim.PlayerId] = normalSpeed; + victim?.MarkDirtySettings(); + } + return; + } - foreach (var PVC in Main.AllAlivePlayerControls) + foreach (var PVC in Main.AllPlayerControls) { + if (!PVC.IsAlive()) + { + CountNearplr.Remove(PVC.PlayerId); + } if (CountNearplr.Contains(PVC.PlayerId) && Utils.GetDistance(PVC.transform.position, victim.transform.position) > 2f) { CountNearplr.Remove(PVC.PlayerId); diff --git a/Roles/AddOns/Common/Tired.cs b/Roles/AddOns/Common/Tired.cs index d863792248..d656b900ba 100644 --- a/Roles/AddOns/Common/Tired.cs +++ b/Roles/AddOns/Common/Tired.cs @@ -45,9 +45,9 @@ public static void RemoveMidGame(byte playerId) public static void ApplyGameOptions(IGameOptions opt, PlayerControl player) { - if (!playerIdList.ContainsKey(player.PlayerId)) return; + if (!playerIdList.TryGetValue(player.PlayerId, out var isTired)) return; - if (playerIdList.TryGetValue(player.PlayerId, out var isTired) && isTired) + if (isTired) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, SetVision.GetFloat()); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index a19557de7e..cc4e596542 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -95,7 +95,7 @@ public static void BuildCustomGameOptions(this PlayerControl player, ref IGameOp if (DollMaster.HasEnabled && DollMaster.IsDoll(player.PlayerId)) { - DollMaster.ApplySettingsToDoll(opt, player); + DollMaster.ApplySettingsToDoll(opt); return; } @@ -400,33 +400,21 @@ public static void CheckDeadBody(PlayerControl killer, PlayerControl deadBody, b } } - public static HashSet> OnFixedUpdateOthers = []; + public static HashSet> OnFixedUpdateOthers = []; /// /// Function always called in a task turn /// For interfering with other roles /// Registered with OnFixedUpdateOthers+= at initialization /// - public static void OnFixedUpdate(PlayerControl player) + public static void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - player.GetRoleClass()?.OnFixedUpdate(player); + player.GetRoleClass()?.OnFixedUpdate(player, lowLoad, nowTime); if (!OnFixedUpdateOthers.Any()) return; //Execute other viewpoint processing if any foreach (var onFixedUpdate in OnFixedUpdateOthers.ToArray()) { - onFixedUpdate(player); - } - } - public static HashSet> OnFixedUpdateLowLoadOthers = []; - public static void OnFixedUpdateLowLoad(PlayerControl player) - { - player.GetRoleClass()?.OnFixedUpdateLowLoad(player); - - if (!OnFixedUpdateLowLoadOthers.Any()) return; - //Execute other viewpoint processing if any - foreach (var onFixedUpdateLowLoad in OnFixedUpdateLowLoadOthers.ToArray()) - { - onFixedUpdateLowLoad(player); + onFixedUpdate(player, lowLoad, nowTime); } } @@ -488,7 +476,6 @@ public static void Initialize() { OtherCollectionsSet = false; OnFixedUpdateOthers.Clear(); - OnFixedUpdateLowLoadOthers.Clear(); CheckDeadBodyOthers.Clear(); BlockedVentsList.Clear(); } diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index b17497c6b0..143879da38 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -135,12 +135,7 @@ public virtual void ApplyGameOptions(IGameOptions opt, byte playerId) /// /// A local method to check conditions during gameplay, 30 times each second /// - public virtual void OnFixedUpdate(PlayerControl pc) - { } - /// - /// A local method to check conditions during gameplay, which aren't prioritized - /// - public virtual void OnFixedUpdateLowLoad(PlayerControl pc) + public virtual void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { } /// diff --git a/Roles/Crewmate/Addict.cs b/Roles/Crewmate/Addict.cs index 86e109c94c..076c9ce655 100644 --- a/Roles/Crewmate/Addict.cs +++ b/Roles/Crewmate/Addict.cs @@ -81,11 +81,11 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf Main.AllPlayerSpeed[player] = DefaultSpeed; } } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!SuicideTimer.ContainsKey(player.PlayerId) || !player.IsAlive()) return; + if (!player.IsAlive() || !SuicideTimer.TryGetValue(player.PlayerId, out var timer)) return; - if (SuicideTimer[player.PlayerId] >= TimeLimit.GetFloat()) + if (timer >= TimeLimit.GetFloat()) { player.SetDeathReason(PlayerState.DeathReason.Suicide); player.RpcMurderPlayer(player); diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index f9a1313839..c9278853a8 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -84,7 +84,7 @@ public static void AddBloodlus() { if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdatesBloodlus); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdatesBloodlus); } } @@ -173,9 +173,9 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t return false; } - private static void OnFixedUpdatesBloodlus(PlayerControl player) + private static void OnFixedUpdatesBloodlus(PlayerControl player, bool lowLoad, long nowTime) { - if (!IsBloodthirst(player.PlayerId)) return; + if (lowLoad || !IsBloodthirst(player.PlayerId)) return; if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) { @@ -199,7 +199,7 @@ private static void OnFixedUpdatesBloodlus(PlayerControl player) var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); PlayerControl target = Utils.GetPlayerById(min.Key); var KillRange = NormalGameOptionsV08.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; - if (min.Value <= KillRange && player.CanMove && target.CanMove) + if (min.Value <= KillRange && !player.inVent && !player.inMovingPlat && !target.inVent && !target.inMovingPlat) { if (player.RpcCheckAndMurder(target, true)) { @@ -216,13 +216,11 @@ private static void OnFixedUpdatesBloodlus(PlayerControl player) } } } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!IsInvis(player.PlayerId)) return; + if (!lowLoad || !IsInvis(player.PlayerId)) return; - var nowTime = Utils.GetTimeStamp(); var needSync = false; - foreach (var AlchemistInfo in InvisTime) { var alchemistId = AlchemistInfo.Key; diff --git a/Roles/Crewmate/Benefactor.cs b/Roles/Crewmate/Benefactor.cs index c62297e9f9..c1be891a4e 100644 --- a/Roles/Crewmate/Benefactor.cs +++ b/Roles/Crewmate/Benefactor.cs @@ -206,16 +206,19 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr return true; } - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - var now = Utils.GetTimeStamp(); - foreach (var x in shieldedPlayers.Where(x => x.Value + ShieldDuration.GetInt() < now).ToArray()) + if (lowLoad) return; + foreach (var shieldedData in shieldedPlayers.Where(x => x.Value + ShieldDuration.GetInt() < nowTime).ToArray()) { - var target = x.Key; - shieldedPlayers.Remove(target); - Utils.GetPlayerById(target)?.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Benefactor), GetString("BKProtectOut"))); - Utils.GetPlayerById(target)?.RpcGuardAndKill(); - SendRPC(type: 4, targetId: target); //remove shieldedPlayer + var targetId = shieldedData.Key; + var target = targetId.GetPlayer(); + + shieldedPlayers.Remove(targetId); + target?.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Benefactor), GetString("BKProtectOut"))); + target?.RpcGuardAndKill(); + + SendRPC(type: 4, targetId: targetId); } } } diff --git a/Roles/Crewmate/Chameleon.cs b/Roles/Crewmate/Chameleon.cs index 83a1cd8e86..dcf4147be4 100644 --- a/Roles/Crewmate/Chameleon.cs +++ b/Roles/Crewmate/Chameleon.cs @@ -138,9 +138,9 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount } return true; } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - var nowTime = GetTimeStamp(); + if (lowLoad) return; var playerId = player.PlayerId; var needSync = false; @@ -154,7 +154,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) foreach (var chameleonInfo in InvisDuration) { var chameleonId = chameleonInfo.Key; - var chameleon = GetPlayerById(chameleonId); + var chameleon = chameleonId.GetPlayer(); if (chameleon == null) continue; var remainTime = chameleonInfo.Value + (long)ChameleonDuration.GetFloat() - nowTime; diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs index d834991b26..6590f44d54 100644 --- a/Roles/Crewmate/Grenadier.cs +++ b/Roles/Crewmate/Grenadier.cs @@ -122,8 +122,9 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) public static bool stopGrenadierSkill = false; public static bool stopMadGrenadierSkill = false; - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad) return; if (!GrenadierBlinding.ContainsKey(player.PlayerId) && !MadGrenadierBlinding.ContainsKey(player.PlayerId)) return; var nowStamp = GetTimeStamp(); diff --git a/Roles/Crewmate/Lighter.cs b/Roles/Crewmate/Lighter.cs index e42bb4e567..ea2ba8ca54 100644 --- a/Roles/Crewmate/Lighter.cs +++ b/Roles/Crewmate/Lighter.cs @@ -60,21 +60,22 @@ public override void Remove(byte playerId) playerIdList.Remove(playerId); LighterNumOfUsed.Remove(playerId); } - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (Timer.TryGetValue(pc.PlayerId, out var ltime) && ltime + LighterSkillDuration.GetInt() < GetTimeStamp()) + if (lowLoad) return; + if (Timer.TryGetValue(player.PlayerId, out var ltime) && ltime + LighterSkillDuration.GetInt() < nowTime) { - Timer.Remove(pc.PlayerId); + Timer.Remove(player.PlayerId); if (!Options.DisableShieldAnimations.GetBool()) { - pc.RpcGuardAndKill(); + player.RpcGuardAndKill(); } else { - pc.RpcResetAbilityCooldown(); + player.RpcResetAbilityCooldown(); } - pc.Notify(GetString("LighterSkillStop")); - pc.MarkDirtySettings(); + player.Notify(GetString("LighterSkillStop")); + player.MarkDirtySettings(); } } public override void OnEnterVent(PlayerControl pc, Vent vent) diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index 7f3fde0730..fac7e40631 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -188,15 +188,15 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } return false; } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!OverseerTimer.ContainsKey(player.PlayerId)) return; + if (!OverseerTimer.TryGetValue(player.PlayerId, out var data)) return; var playerId = player.PlayerId; if (!player.IsAlive() || Pelican.IsEaten(playerId)) { - OverseerTimer[playerId].Item1.RpcSetSpecificScanner(player, false); + data.Item1.RpcSetSpecificScanner(player, false); OverseerTimer.Remove(playerId); SendTimerRPC(2, playerId); NotifyRoles(SpecifySeer: player); @@ -204,7 +204,7 @@ public override void OnFixedUpdate(PlayerControl player) } else { - var (farTarget, farTime) = OverseerTimer[playerId]; + var (farTarget, farTime) = data; if (!farTarget.IsAlive()) { diff --git a/Roles/Crewmate/Spy.cs b/Roles/Crewmate/Spy.cs index 3b5a95d8e7..f82509341c 100644 --- a/Roles/Crewmate/Spy.cs +++ b/Roles/Crewmate/Spy.cs @@ -100,15 +100,15 @@ public bool OnKillAttempt(PlayerControl killer, PlayerControl target) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => OnKillAttempt(killer, target); - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (pc == null) return; - if (SpyRedNameList.Count == 0) return; + if (lowLoad) return; + if (!SpyRedNameList.Any()) return; change = false; foreach (var x in SpyRedNameList) { - if (x.Value + SpyRedNameDur.GetInt() < GetTimeStamp() || !GameStates.IsInTask) + if (x.Value + SpyRedNameDur.GetInt() < nowTime) { if (SpyRedNameList.ContainsKey(x.Key)) { @@ -118,7 +118,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) } } } - if (change && GameStates.IsInTask) { NotifyRoles(SpecifySeer: pc, ForceLoop: true); } + if (change) { NotifyRoles(SpecifySeer: player, ForceLoop: true); } } public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { diff --git a/Roles/Crewmate/Telecommunication.cs b/Roles/Crewmate/Telecommunication.cs index a9b4e8b981..7962128d06 100644 --- a/Roles/Crewmate/Telecommunication.cs +++ b/Roles/Crewmate/Telecommunication.cs @@ -58,14 +58,15 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.EngineerCooldown = 1f; AURoleOptions.EngineerInVentMaxTime = 0f; } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad) return; Count--; if (Count > 0) return; Count = 5; bool Admin = false, Camera = false, DoorLog = false, Vital = false; foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (Pelican.IsEaten(pc.PlayerId) || pc.inVent) continue; + if (pc.inVent) continue; try { Vector2 PlayerPos = pc.transform.position; @@ -145,7 +146,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) { foreach (var pc in playerIdList) { - var antiAdminer = GetPlayerById(pc); + var antiAdminer = pc.GetPlayer(); NotifyRoles(SpecifySeer: antiAdminer, ForceLoop: false); } } diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs index cc8fc9dd61..2d892d1e73 100644 --- a/Roles/Crewmate/TimeMaster.cs +++ b/Roles/Crewmate/TimeMaster.cs @@ -67,9 +67,9 @@ public override void SetAbilityButtonText(HudManager hud, byte id) hud.ReportButton.OverrideText(GetString("ReportButtonText")); hud.AbilityButton.buttonLabelText.text = GetString("TimeMasterVentButtonText"); } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (TimeMasterInProtect.TryGetValue(player.PlayerId, out var vtime) && vtime + TimeMasterSkillDuration.GetInt() < GetTimeStamp()) + if (!lowLoad && TimeMasterInProtect.TryGetValue(player.PlayerId, out var vtime) && vtime + TimeMasterSkillDuration.GetInt() < nowTime) { TimeMasterInProtect.Remove(player.PlayerId); if (!DisableShieldAnimations.GetBool()) player.RpcGuardAndKill(); diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs index 3b8f5b15fa..f2ff07ccb2 100644 --- a/Roles/Crewmate/Veteran.cs +++ b/Roles/Crewmate/Veteran.cs @@ -95,22 +95,22 @@ or CustomRoles.Bodyguard } return true; } - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (VeteranInProtect.TryGetValue(pc.PlayerId, out var vtime) && vtime + VeteranSkillDuration.GetInt() < GetTimeStamp()) + if (!lowLoad && VeteranInProtect.TryGetValue(player.PlayerId, out var vtime) && vtime + VeteranSkillDuration.GetInt() < nowTime) { - VeteranInProtect.Remove(pc.PlayerId); + VeteranInProtect.Remove(player.PlayerId); if (!DisableShieldAnimations.GetBool()) { - pc.RpcGuardAndKill(); + player.RpcGuardAndKill(); } else { - pc.RpcResetAbilityCooldown(); + player.RpcResetAbilityCooldown(); } - pc.Notify(string.Format(GetString("VeteranOffGuard"), AbilityLimit)); + player.Notify(string.Format(GetString("VeteranOffGuard"), AbilityLimit)); } } public override void OnEnterVent(PlayerControl pc, Vent vent) diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index 5e1f758ff5..d47ed83966 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -38,7 +38,7 @@ public override void Add(byte playerId) if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateLowLoadOthers); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateLowLoadOthers); } } public override bool CanUseKillButton(PlayerControl pc) => true; @@ -60,9 +60,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.Notify(GetString("WitnessFoundInnocent")); return false; } - public static void OnFixedUpdateLowLoadOthers(PlayerControl player) + public static void OnFixedUpdateLowLoadOthers(PlayerControl player, bool lowLoad, long nowTime) { - if (Main.AllKillers.TryGetValue(player.PlayerId, out var ktime) && ktime + WitnessTime.GetInt() < Utils.GetTimeStamp()) + if (!lowLoad && Main.AllKillers.TryGetValue(player.PlayerId, out var ktime) && ktime + WitnessTime.GetInt() < nowTime) Main.AllKillers.Remove(player.PlayerId); } } diff --git a/Roles/Impostor/AntiAdminer.cs b/Roles/Impostor/AntiAdminer.cs index 42b7591383..6f3c0dbb67 100644 --- a/Roles/Impostor/AntiAdminer.cs +++ b/Roles/Impostor/AntiAdminer.cs @@ -50,14 +50,15 @@ public override void Remove(byte playerId) } private static int Count = 0; - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad) return; Count--; if (Count > 0) return; Count = 3; bool Admin = false, Camera = false, DoorLog = false, Vital = false; foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (Pelican.IsEaten(pc.PlayerId) || pc.inVent || pc.GetCustomRole().IsImpostor()) continue; + if (pc.inVent || pc.GetCustomRole().IsImpostor()) continue; try { @@ -138,12 +139,12 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) { foreach (var pc in playerIdList.ToArray()) { - var antiAdminer = GetPlayerById(pc); + var antiAdminer = pc.GetPlayer(); NotifyRoles(SpecifySeer: antiAdminer, ForceLoop: false); } } } - public override string GetSuffix(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + public override string GetSuffix(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) { if (seer.PlayerId != seen.PlayerId || isForMeeting) return string.Empty; diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index 46bd2a7d5f..d727c3b69f 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -118,9 +118,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return true; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) => ChangeTimer.Clear(); - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!ChangeTimer.ContainsKey(player.PlayerId)) return; + if (!ChangeTimer.TryGetValue(player.PlayerId, out var timer)) return; if (!player.IsAlive()) ChangeTimer.Remove(player.PlayerId); @@ -129,12 +129,12 @@ public override void OnFixedUpdate(PlayerControl player) var targetId = GetTarget(player); if (targetId == byte.MaxValue) return; - if (ChangeTimer[player.PlayerId] >= TargetChangeTime) + if (timer >= TargetChangeTime) { ResetTarget(player); Utils.NotifyRoles(SpecifySeer: player, ForceLoop: true); } - if (ChangeTimer[player.PlayerId] >= 0) + if (timer >= 0) ChangeTimer[player.PlayerId] += Time.fixedDeltaTime; if (Main.PlayerStates[targetId].IsDead) diff --git a/Roles/Impostor/Butcher.cs b/Roles/Impostor/Butcher.cs index b2dd43a4fc..c610e97b20 100644 --- a/Roles/Impostor/Butcher.cs +++ b/Roles/Impostor/Butcher.cs @@ -91,15 +91,15 @@ public override void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl public override void AfterMeetingTasks() => MurderTargetLateTask = []; public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) => MurderTargetLateTask.Clear(); - public static void OnFixedUpdateOthers(PlayerControl target) + public static void OnFixedUpdateOthers(PlayerControl target, bool lowLoad, long nowTime) { - if (!MurderTargetLateTask.ContainsKey(target.PlayerId)) return; - if (target == null || !target.Data.IsDead) return; - var ops = MurderTargetLateTask[target.PlayerId].Item3; + if (!target.IsAlive()) return; + if (!MurderTargetLateTask.TryGetValue(target.PlayerId, out var data)) return; + var ops = data.Item3; - if (MurderTargetLateTask[target.PlayerId].Item1 > 19) //on fix update updates 30 times pre second + if (data.Item1 > 19) //on fix update updates 30 times pre second { - if (MurderTargetLateTask[target.PlayerId].Item2 < 5) + if (data.Item2 < 5) { var rd = IRandom.Instance; @@ -107,12 +107,12 @@ public static void OnFixedUpdateOthers(PlayerControl target) target.RpcTeleport(location); target.RpcMurderPlayer(target); target.SetRealKiller(Utils.GetPlayerById(PlayerIds.First()), true); - MurderTargetLateTask[target.PlayerId] = (0, MurderTargetLateTask[target.PlayerId].Item2 + 1, ops); + MurderTargetLateTask[target.PlayerId] = (0, data.Item2 + 1, ops); } else MurderTargetLateTask.Remove(target.PlayerId); } else - MurderTargetLateTask[target.PlayerId] = (MurderTargetLateTask[target.PlayerId].Item1 + 1, MurderTargetLateTask[target.PlayerId].Item2, ops); + MurderTargetLateTask[target.PlayerId] = (data.Item1 + 1, data.Item2, ops); } } \ No newline at end of file diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index 2c1429cbc6..506dfc3339 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -129,21 +129,21 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.SetKillCooldown(); return true; } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (GameStates.IsMeeting || !Main.IntroDestroyed) return; + if (!Main.IntroDestroyed) return; var oldChargedTime = ChargedTime; if (LastCD != GetCharge()) { LastCD = GetCharge(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + Utils.NotifyRoles(SpecifySeer: player, ForceLoop: false); } if (ChargedTime != FullCharge - && now + 1 <= Utils.GetTimeStamp() && !IsInMassacre) + && now + 1 <= nowTime && !IsInMassacre) { - now = Utils.GetTimeStamp(); + now = nowTime; ChargedTime++; } else if (IsInMassacre && ChargedTime > 0 && countnowF >= LastNowF) @@ -155,7 +155,7 @@ public override void OnFixedUpdate(PlayerControl pc) if (IsInMassacre && ChargedTime < 1) { IsInMassacre = false; - pc.MarkDirtySettings(); + player.MarkDirtySettings(); } if (oldChargedTime != ChargedTime) diff --git a/Roles/Impostor/Deathpact.cs b/Roles/Impostor/Deathpact.cs index d4527afe30..8068b1be6f 100644 --- a/Roles/Impostor/Deathpact.cs +++ b/Roles/Impostor/Deathpact.cs @@ -68,10 +68,16 @@ public override void Init() } public override void Add(byte playerId) { - PlayersInDeathpact.TryAdd(playerId, []); - DeathpactTime.TryAdd(playerId, 0); + PlayersInDeathpact[playerId] = []; + DeathpactTime[playerId] = 0; Playerids.Add(playerId); } + public override void Remove(byte playerId) + { + PlayersInDeathpact.Remove(playerId); + DeathpactTime.Remove(playerId); + Playerids.Remove(playerId); + } public override void ApplyGameOptions(IGameOptions opt, byte playerId) { @@ -149,12 +155,12 @@ public static void SetDeathpactVision(PlayerControl player, IGameOptions opt) } } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!ActiveDeathpacts.Contains(player.PlayerId)) return; + if (lowLoad || !ActiveDeathpacts.Contains(player.PlayerId)) return; if (CheckCancelDeathpact(player)) return; - if (DeathpactTime[player.PlayerId] < GetTimeStamp() && DeathpactTime[player.PlayerId] != 0) + if (DeathpactTime.TryGetValue(player.PlayerId, out var time) && time < nowTime && time != 0) { foreach (var playerInDeathpact in PlayersInDeathpact[player.PlayerId]) { @@ -179,7 +185,9 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay private static bool CheckCancelDeathpact(PlayerControl deathpact) { - if (PlayersInDeathpact[deathpact.PlayerId].Any(a => a.Data.Disconnected || a.Data.IsDead)) + if (!PlayersInDeathpact.TryGetValue(deathpact.PlayerId, out var playerList)) return false; + + if (playerList.Any(a => a.Data.Disconnected || a.Data.IsDead)) { ClearDeathpact(deathpact.PlayerId); deathpact.Notify(GetString("DeathpactAverted")); @@ -188,10 +196,10 @@ private static bool CheckCancelDeathpact(PlayerControl deathpact) bool cancelDeathpact = true; - foreach (var player in PlayersInDeathpact[deathpact.PlayerId]) + foreach (var player in playerList) { float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(Reach.IsReach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - foreach (var otherPlayerInPact in PlayersInDeathpact[deathpact.PlayerId].Where(a => a.PlayerId != player.PlayerId).ToArray()) + foreach (var otherPlayerInPact in playerList.Where(a => a.PlayerId != player.PlayerId).ToArray()) { float dis = GetDistance(player.transform.position, otherPlayerInPact.transform.position); cancelDeathpact = cancelDeathpact && (dis <= range); @@ -217,18 +225,12 @@ private static void KillPlayerInDeathpact(PlayerControl deathpact, PlayerControl target.SetRealKiller(deathpact); } - public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - { - seen ??= seer; - if (!seer.Is(CustomRoles.Deathpact) || !IsInDeathpact(seer.PlayerId, seen)) return string.Empty; - return ColorString(Palette.ImpostorRed, "◀"); - } + public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) + => IsInDeathpact(seer.PlayerId, seen) ? ColorString(Palette.ImpostorRed, "◀") : string.Empty; - public override string GetSuffix(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + public override string GetSuffix(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) { if (isForMeeting || !ShowArrowsToOtherPlayersInPact.GetBool()) return string.Empty; - - seen ??= seer; if (seer.PlayerId != seen.PlayerId) return string.Empty; if (!IsInActiveDeathpact(seer)) return string.Empty; @@ -248,7 +250,7 @@ public override string GetSuffix(PlayerControl seer, PlayerControl seen = null, public static bool IsInActiveDeathpact(PlayerControl player) { - if (ActiveDeathpacts.Count == 0) return false; + if (!ActiveDeathpacts.Any() || !PlayersInDeathpact.Any()) return false; if (PlayersInDeathpact.Any(a => ActiveDeathpacts.Contains(a.Key) && a.Value.Any(b => b.PlayerId == player.PlayerId))) return true; return false; } @@ -282,11 +284,11 @@ public static string GetDeathpactString(PlayerControl player) private static void ClearDeathpact(byte deathpact) { - if (ShowArrowsToOtherPlayersInPact.GetBool()) + if (ShowArrowsToOtherPlayersInPact.GetBool() && PlayersInDeathpact.TryGetValue(deathpact, out var playerList)) { - foreach (var player in PlayersInDeathpact[deathpact]) + foreach (var player in playerList) { - foreach (var otherPlayerInPact in PlayersInDeathpact[deathpact].Where(a => a.PlayerId != player.PlayerId).ToArray()) + foreach (var otherPlayerInPact in playerList.Where(a => a.PlayerId != player.PlayerId).ToArray()) { TargetArrow.Remove(player.PlayerId, otherPlayerInPact.PlayerId); } @@ -295,7 +297,7 @@ private static void ClearDeathpact(byte deathpact) DeathpactTime[deathpact] = 0; ActiveDeathpacts.Remove(deathpact); - PlayersInDeathpact[deathpact].Clear(); + PlayersInDeathpact[deathpact] = []; if (ReduceVisionWhileInPact.GetBool()) { @@ -309,8 +311,8 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf { if (KillDeathpactPlayersOnMeeting.GetBool()) { - var deathpactPlayer = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == deathpact); - if (deathpactPlayer == null || deathpactPlayer.Data.IsDead) + var deathpactPlayer = deathpact.GetPlayer(); + if (!deathpactPlayer.IsAlive()) { continue; } diff --git a/Roles/Impostor/DollMaster.cs b/Roles/Impostor/DollMaster.cs index 9cc9715011..a96a5d08d4 100644 --- a/Roles/Impostor/DollMaster.cs +++ b/Roles/Impostor/DollMaster.cs @@ -58,7 +58,6 @@ public override void Add(byte playerId) DollMasterTarget = Utils.GetPlayerById(playerId); IsControllingPlayer = false; CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(CheckIfPlayerDC); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) @@ -73,24 +72,41 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public static bool IsDoll(byte id) => ReducedVisionPlayers.Contains(id); // Set Vision and Speed to 0 for possessed Target. - public static void ApplySettingsToDoll(IGameOptions opt, PlayerControl target) + public static void ApplySettingsToDoll(IGameOptions opt) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, 0f * 0); opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0f * 0); } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad) return; if (controllingTarget != null && DollMasterTarget != null) { // If DollMaster can't be teleported start waiting to unpossess. WaitToUnPossessUpdate(DollMasterTarget, controllingTarget); } - } + if (IsControllingPlayer && (controllingTarget == null || DollMasterTarget == null)) + { + ReducedVisionPlayers.Clear(); + + if (controllingTarget != null) + { + Main.AllPlayerSpeed[controllingTarget.PlayerId] = originalSpeed; + controllingTarget.MarkDirtySettings(); + } - private static void OnFixedUpdateOthers(PlayerControl target) + DollMasterTarget?.RpcShapeshift(DollMasterTarget, false); + controllingTarget?.ResetPlayerOutfit(); + + IsControllingPlayer = false; + ResetPlayerSpeed = true; + } + } + private static void OnFixedUpdateOthers(PlayerControl target, bool lowLoad, long nowTime) { + if (lowLoad) return; if (controllingTarget != null && target == controllingTarget) { // Boot Possessed Player from vent if inside of a vent and if waiting. @@ -228,7 +244,7 @@ private static bool CanKillerUseAbility(PlayerControl player) { var CanUseAbility = true; var cRole = player.GetCustomRole(); - var subRoles = player.GetCustomSubRoles(); // May use later on! + //var subRoles = player.GetCustomSubRoles(); // May use later on! switch (cRole) // Check role. { @@ -345,29 +361,8 @@ public override bool OnCheckShapeshift(PlayerControl pc, PlayerControl target, r return false; } - // A fix when the DollMaster or Possessed Player DC's from the game. - public static void CheckIfPlayerDC(PlayerControl player) - { - if (IsControllingPlayer && (controllingTarget == null || DollMasterTarget == null)) - { - ReducedVisionPlayers.Clear(); - - if (controllingTarget != null) - { - Main.AllPlayerSpeed[controllingTarget.PlayerId] = originalSpeed; - controllingTarget.MarkDirtySettings(); - } - - DollMasterTarget?.RpcShapeshift(DollMasterTarget, false); - controllingTarget?.ResetPlayerOutfit(); - - IsControllingPlayer = false; - ResetPlayerSpeed = true; - } - } - // Possess Player - private static void Possess(PlayerControl pc, PlayerControl target, bool shouldAnimate = false) + private static void Possess(PlayerControl pc, PlayerControl target) { (target.MyPhysics.FlipX, pc.MyPhysics.FlipX) = (pc.MyPhysics.FlipX, target.MyPhysics.FlipX); // Copy the players directions that they are facing, Note this only works for modded clients! pc?.RpcShapeshift(target, false); @@ -378,7 +373,7 @@ private static void Possess(PlayerControl pc, PlayerControl target, bool shouldA } // UnPossess Player - private static void UnPossess(PlayerControl pc, PlayerControl target, bool shouldAnimate = false) + private static void UnPossess(PlayerControl pc, PlayerControl target) { (target.MyPhysics.FlipX, pc.MyPhysics.FlipX) = (pc.MyPhysics.FlipX, target.MyPhysics.FlipX); // Copy the players directions that they are facing, Note this only works for modded clients! pc?.RpcShapeshift(pc, false); diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index ecc5c57489..00fd955ea7 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -166,14 +166,14 @@ public override void AfterMeetingTasks() } // Active bomb timer update and check. - private void OnFixedUpdateOthers(PlayerControl player) + private void OnFixedUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { if (!CurrentBombedPlayers.Contains(player.PlayerId)) return; if (!player.IsAlive()) // If Player is dead clear bomb. ClearBomb(); - if (BombIsActive && GameStates.IsInTask && GameStates.IsInGame && !(GameStates.IsMeeting && GameStates.IsExilling)) + if (BombIsActive && !GameStates.IsExilling) { var OldCurrentBombedTime = (int)CurrentBombedTime; @@ -188,14 +188,16 @@ private void OnFixedUpdateOthers(PlayerControl player) } // Set timer on Double Agent for Non-Modded Clients. - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad) return; + if (BombIsActive) { - if (!pc.IsModded()) + if (!player.IsModded()) { - string Duration = ColorString(pc.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); - if ((!NameNotifyManager.Notice.TryGetValue(pc.PlayerId, out var a) || a.Text != Duration) && Duration != string.Empty) pc.Notify(Duration, 1.1f); + string Duration = ColorString(player.GetRoleColor(), string.Format(GetString("DoubleAgent_BombExplodesIn"), (int)CurrentBombedTime)); + if ((!NameNotifyManager.Notice.TryGetValue(player.PlayerId, out var a) || a.Text != Duration) && Duration != string.Empty) player.Notify(Duration, 1.1f); } if (CurrentBombedPlayers.Any(playerId => !GetPlayerById(playerId).IsAlive())) // If playerId is a null Player clear bomb. @@ -203,9 +205,9 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) } // If enabled and if DoubleAgent is last Impostor become set role. - if (ChangeRoleToOnLast.GetValue() != 0 && StartedWithMoreThanOneImp && GameStates.IsInTask && !GameStates.IsMeeting && !GameStates.IsExilling) + if (ChangeRoleToOnLast.GetValue() != 0 && StartedWithMoreThanOneImp && !GameStates.IsExilling && !AntiBlackout.SkipTasks) { - if (pc.Is(CustomRoles.DoubleAgent) && pc.IsAlive() && Main.AliveImpostorCount < 2) + if (player.Is(CustomRoles.DoubleAgent) && player.IsAlive() && Main.AliveImpostorCount < 2) { var Role = CRoleChangeRoles[ChangeRoleToOnLast.GetValue()]; if (ChangeRoleToOnLast.GetValue() == 1) // Random @@ -214,27 +216,27 @@ public override void OnFixedUpdateLowLoad(PlayerControl pc) // If role is not on Impostor team remove all Impostor addons if any. if (!Role.IsImpostorTeam()) { - foreach (CustomRoles allAddons in pc.GetCustomSubRoles()) + foreach (CustomRoles allAddons in player.GetCustomSubRoles()) { if (allAddons.IsImpOnlyAddon()) { - pc.GetCustomSubRoles()?.Remove(allAddons); + Main.PlayerStates[player.PlayerId].RemoveSubRole(allAddons); } } } // If Role is ImpostorTOHE aka Admired Impostor opt give Admired Addon if player dose not already have it. - if (Role == CustomRoles.ImpostorTOHE && !pc.GetCustomSubRoles().Contains(CustomRoles.Admired)) - pc.GetCustomSubRoles()?.Add(CustomRoles.Admired); + if (Role == CustomRoles.ImpostorTOHE && !player.GetCustomSubRoles().Contains(CustomRoles.Admired)) + player.GetCustomSubRoles()?.Add(CustomRoles.Admired); Init(); - pc.RpcSetCustomRole(Role); - pc.GetRoleClass()?.Add(pc.PlayerId); - pc.MarkDirtySettings(); + player.RpcSetCustomRole(Role); + player.GetRoleClass()?.Add(player.PlayerId); + player.MarkDirtySettings(); - string RoleName = ColorString(GetRoleColor(pc.GetCustomRole()), GetRoleName(pc.GetCustomRole())); + string RoleName = ColorString(GetRoleColor(player.GetCustomRole()), GetRoleName(player.GetCustomRole())); if (Role == CustomRoles.ImpostorTOHE) RoleName = ColorString(GetRoleColor(CustomRoles.Admired), $"{GetString("Admired")} {GetString("ImpostorTOHE")}"); - pc.Notify(ColorString(GetRoleColor(pc.GetCustomRole()), GetString("DoubleAgentRoleChange") + RoleName)); + player.Notify(ColorString(GetRoleColor(player.GetCustomRole()), GetString("DoubleAgentRoleChange") + RoleName)); } } } @@ -258,7 +260,7 @@ private void BoomBoom(PlayerControl player) CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); ClearBomb(); - _Player.Notify(ColorString(GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); + _Player?.Notify(ColorString(GetRoleColor(CustomRoles.DoubleAgent), GetString("DoubleAgent_BombExploded"))); } // Set bomb mark on player. diff --git a/Roles/Impostor/EvilHacker.cs b/Roles/Impostor/EvilHacker.cs index 4b31e70399..409233f070 100644 --- a/Roles/Impostor/EvilHacker.cs +++ b/Roles/Impostor/EvilHacker.cs @@ -182,9 +182,9 @@ private static void CreateMurderNotify(SystemTypes room) Utils.NotifyRoles(SpecifySeer: evilHackerPlayer); } } - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!activeNotifies.Any()) + if (lowLoad || !activeNotifies.Any()) { return; } diff --git a/Roles/Impostor/Lightning.cs b/Roles/Impostor/Lightning.cs index 25d1142eb7..e5a9e8b759 100644 --- a/Roles/Impostor/Lightning.cs +++ b/Roles/Impostor/Lightning.cs @@ -118,15 +118,15 @@ public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl RealKiller.TryAdd(killer.PlayerId, target); StartConvertCountDown(target, killer); } - public override void OnFixedUpdateLowLoad(PlayerControl lightning) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!GhostPlayer.Any()) return; + if (lowLoad || !GhostPlayer.Any()) return; List deList = []; foreach (var ghost in GhostPlayer.ToArray()) { - var gs = Utils.GetPlayerById(ghost); - if (gs == null || !gs.IsAlive() || gs.Data.Disconnected) + var gs = ghost.GetPlayer(); + if (!gs.IsAlive()) { deList.Add(gs.PlayerId); continue; @@ -159,7 +159,7 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf { foreach (var ghost in GhostPlayer.ToArray()) { - var gs = Utils.GetPlayerById(ghost); + var gs = ghost.GetPlayer(); if (gs == null) continue; CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Quantization, gs.PlayerId); gs.SetRealKiller(RealKiller[gs.PlayerId]); diff --git a/Roles/Impostor/Mastermind.cs b/Roles/Impostor/Mastermind.cs index e2a2675015..c221fbc546 100644 --- a/Roles/Impostor/Mastermind.cs +++ b/Roles/Impostor/Mastermind.cs @@ -77,8 +77,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t }); } - public override void OnFixedUpdateLowLoad(PlayerControl mastermind) + public override void OnFixedUpdate(PlayerControl mastermind, bool lowLoad, long nowTime) { + if (lowLoad) return; if (ManipulatedPlayers.Count == 0 && ManipulateDelays.Count == 0) return; foreach (var x in ManipulateDelays) @@ -90,10 +91,10 @@ public override void OnFixedUpdateLowLoad(PlayerControl mastermind) ManipulateDelays.Remove(x.Key); continue; } - if (x.Value + Delay.GetInt() < GetTimeStamp()) + if (x.Value + Delay.GetInt() < nowTime) { ManipulateDelays.Remove(x.Key); - ManipulatedPlayers.TryAdd(x.Key, GetTimeStamp()); + ManipulatedPlayers.TryAdd(x.Key, nowTime); TempKCDs.TryAdd(pc.PlayerId, pc.killTimer); pc.SetKillCooldown(time: 1f); diff --git a/Roles/Impostor/Mercenary.cs b/Roles/Impostor/Mercenary.cs index aefb34a841..5eedb8b600 100644 --- a/Roles/Impostor/Mercenary.cs +++ b/Roles/Impostor/Mercenary.cs @@ -74,7 +74,7 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf public static void ClearSuicideTimer() => SuicideTimer.Clear(); - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { if (!HasKilled(player)) { diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index fb5f57c0c4..1f04f6b302 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -191,10 +191,8 @@ public override bool OnCoEnterVentOthers(PlayerPhysics physics, int ventId) } return false; } - public override void OnFixedUpdate(PlayerControl penguin) + public override void OnFixedUpdate(PlayerControl penguin, bool lowLoad, long nowTime) { - if (GameStates.IsMeeting) return; - if (!stopCount) AbductTimer -= Time.fixedDeltaTime; diff --git a/Roles/Impostor/Pitfall.cs b/Roles/Impostor/Pitfall.cs index 30ed0692bd..b374acd3e8 100644 --- a/Roles/Impostor/Pitfall.cs +++ b/Roles/Impostor/Pitfall.cs @@ -73,7 +73,7 @@ public override void Add(byte playerId) if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); } } @@ -114,11 +114,11 @@ public override void UnShapeShiftButton(PlayerControl shapeshifter) shapeshifter.Notify(GetString("RejectShapeshift.AbilityWasUsed"), time: 2f); } - private void OnFixedUpdateOthers(PlayerControl player) + private void OnFixedUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { - if (Pelican.IsEaten(player.PlayerId) || !player.IsAlive()) return; + if (lowLoad || Pelican.IsEaten(player.PlayerId) || !player.IsAlive()) return; - if (player.GetCustomRole().IsImpostor()) + if (player.Is(Custom_Team.Impostor)) { var traps = Traps.Where(a => a.PitfallPlayerId == player.PlayerId && a.IsActive).ToArray(); foreach (var trap in traps) diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index e15be939fb..1a8ade36cf 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -47,7 +47,7 @@ public override void Add(byte playerId) if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); } } @@ -96,13 +96,12 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t SendRPC(killer.PlayerId, target.PlayerId, 1); killer.RPCPlayCustomSound("Line"); Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - } - ); + }); } - private void OnFixedUpdateOthers(PlayerControl puppet) + private void OnFixedUpdateOthers(PlayerControl puppet, bool lowLoad, long nowTime) { - if (!PuppetIsActive(puppet.PlayerId)) return; + if (lowLoad || !PuppetIsActive(puppet.PlayerId)) return; if (!puppet.IsAlive() || Pelican.IsEaten(puppet.PlayerId)) { diff --git a/Roles/Impostor/RiftMaker.cs b/Roles/Impostor/RiftMaker.cs index 354988cca2..7fdb32b310 100644 --- a/Roles/Impostor/RiftMaker.cs +++ b/Roles/Impostor/RiftMaker.cs @@ -198,33 +198,31 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) }, 0.5f, "RiftMakerOnVent"); } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (player == null) return; - if (Pelican.IsEaten(player.PlayerId) || !player.IsAlive()) return; - byte playerId = player.PlayerId; - if (!MarkedLocation.ContainsKey(playerId)) MarkedLocation[playerId] = []; - if (MarkedLocation[playerId].Count != 2) return; + if (lowLoad || Pelican.IsEaten(playerId) || !player.IsAlive()) return; + if (!MarkedLocation.TryGetValue(playerId, out var locationList)) return; - var now = Utils.GetTimeStamp(); - if (!LastTP.ContainsKey(playerId)) LastTP[playerId] = now; - if (now - LastTP[playerId] <= TPCooldown) return; + if (locationList.Count != 2) return; + + if (!LastTP.ContainsKey(playerId)) LastTP[playerId] = nowTime; + if (nowTime - LastTP[playerId] <= TPCooldown) return; Vector2 position = player.GetCustomPosition(); Vector2 TPto; - if (Utils.GetDistance(position, MarkedLocation[playerId][0]) <= RiftRadius.GetFloat()) + if (Utils.GetDistance(position, locationList[0]) <= RiftRadius.GetFloat()) { - TPto = MarkedLocation[playerId][1]; + TPto = locationList[1]; } - else if (Utils.GetDistance(position, MarkedLocation[playerId][1]) <= RiftRadius.GetFloat()) + else if (Utils.GetDistance(position, locationList[1]) <= RiftRadius.GetFloat()) { - TPto = MarkedLocation[playerId][0]; + TPto = locationList[0]; } else return; - LastTP[playerId] = now; + LastTP[playerId] = nowTime; //SENDRPC SendRPC(playerId, 2); player.RpcTeleport(TPto); diff --git a/Roles/Impostor/Stealth.cs b/Roles/Impostor/Stealth.cs index 70c9d942e5..065bd1e7b3 100644 --- a/Roles/Impostor/Stealth.cs +++ b/Roles/Impostor/Stealth.cs @@ -78,7 +78,7 @@ private void DarkenPlayers(PlayerControl[] playersToDarken) player.MarkDirtySettings(); } } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { // when you're darkening someone if (darkenedPlayers == null) return; diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs index 89a989b60d..85de85808a 100644 --- a/Roles/Impostor/Swooper.cs +++ b/Roles/Impostor/Swooper.cs @@ -115,9 +115,9 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) }, 0.8f, "Swooper Vent"); } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - var nowTime = Utils.GetTimeStamp(); + if (lowLoad) return; var playerId = player.PlayerId; var needSync = false; diff --git a/Roles/Impostor/Vampire.cs b/Roles/Impostor/Vampire.cs index ca8f96a05c..c4dbbf6c95 100644 --- a/Roles/Impostor/Vampire.cs +++ b/Roles/Impostor/Vampire.cs @@ -96,10 +96,10 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } - public override void OnFixedUpdate(PlayerControl vampire) + public override void OnFixedUpdate(PlayerControl vampire, bool lowLoad, long nowTime) { - var vampireID = vampire.PlayerId; - List targetList = new(BittenPlayers.Where(b => b.Value.VampireId == vampireID).Select(b => b.Key)); + var vampireId = vampire.PlayerId; + List targetList = new(BittenPlayers.Where(b => b.Value.VampireId == vampireId).Select(b => b.Key)); foreach (var targetId in targetList) { @@ -109,7 +109,7 @@ public override void OnFixedUpdate(PlayerControl vampire) { Logger.Info("KillTimer >= KillDelay", "Vampire"); - var target = Utils.GetPlayerById(targetId); + var target = targetId.GetPlayer(); KillBitten(vampire, target); BittenPlayers.Remove(targetId); } diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index f84d601c08..df0c0c49f0 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -143,7 +143,7 @@ public override void OnShapeshift(PlayerControl shapeshifter, PlayerControl targ } } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { if (WarlockTimer.TryGetValue(player.PlayerId, out var warlockTimer)) { diff --git a/Roles/Impostor/Wildling.cs b/Roles/Impostor/Wildling.cs index beb783815c..313b8a0c36 100644 --- a/Roles/Impostor/Wildling.cs +++ b/Roles/Impostor/Wildling.cs @@ -21,7 +21,7 @@ internal class Wildling : RoleBase private static OptionItem ShapeshiftCD; private static OptionItem ShapeshiftDur; - private long? TimeStamp; + private long TimeStamp; public override void SetupCustomOption() { @@ -33,18 +33,19 @@ public override void SetupCustomOption() ShapeshiftDur = FloatOptionItem.Create(Id + 16, GeneralOption.ShapeshifterBase_ShapeshiftDuration, new(1f, 180f, 1f), 25f, TabGroup.ImpostorRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]) .SetValueFormat(OptionFormat.Seconds); } - - private void SendRPC(byte playerId) + public override void Init() + { + TimeStamp = 0; + } + private void SendRPC() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); writer.WriteNetObject(_Player); - writer.Write(playerId); writer.Write(TimeStamp.ToString()); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - byte PlayerId = reader.ReadByte(); string Time = reader.ReadString(); TimeStamp = long.Parse(Time); } @@ -56,11 +57,11 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override bool CanUseImpostorVentButton(PlayerControl pc) => false; - private bool InProtect(byte playerId) => TimeStamp > Utils.GetTimeStamp(DateTime.Now); + private bool InProtect() => TimeStamp > Utils.GetTimeStamp(DateTime.Now); public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { - if (InProtect(target.PlayerId)) + if (InProtect()) { killer.RpcGuardAndKill(target); if (!DisableShieldAnimations.GetBool()) target.RpcGuardAndKill(); @@ -75,16 +76,16 @@ public override void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl if (inMeeting || isSuicide) return; TimeStamp = Utils.GetTimeStamp(DateTime.Now) + (long)ProtectDuration.GetFloat(); - SendRPC(killer.PlayerId); + SendRPC(); killer.Notify(Translator.GetString("BKInProtect")); } - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (TimeStamp != null && TimeStamp < Utils.GetTimeStamp(DateTime.Now)) + if (!lowLoad && TimeStamp != 0 && TimeStamp < nowTime) { TimeStamp = 0; - pc.Notify(Translator.GetString("BKProtectOut")); + player.Notify(Translator.GetString("BKProtectOut")); } } public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) @@ -92,7 +93,7 @@ public override string GetLowerText(PlayerControl seer, PlayerControl seen = nul if (seer == null || isForMeeting || !isForHud || !seer.IsAlive()) return string.Empty; var str = new StringBuilder(); - if (InProtect(seer.PlayerId)) + if (InProtect()) { var remainTime = TimeStamp - Utils.GetTimeStamp(DateTime.Now); str.Append(string.Format(Translator.GetString("BKSkillTimeRemain"), remainTime)); diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 45dbcb4f6e..f75ca7d8d8 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -83,9 +83,10 @@ public override string GetMark(PlayerControl seer, PlayerControl seen, bool isFo return seen.PlayerId == yin?.PlayerId || seen.PlayerId == yang?.PlayerId ? ColorString(col, "☯") : string.Empty; } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - var (yin, yang) = Yanged[pc.PlayerId]; + if (lowLoad) return; + var (yin, yang) = Yanged[player.PlayerId]; if (!yin || !yang) return; if (GetDistance(yin.GetCustomPosition(), yang.GetCustomPosition()) < 1.5f) @@ -95,7 +96,7 @@ public override void OnFixedUpdate(PlayerControl pc) yang.SetDeathReason(PlayerState.DeathReason.Equilibrium); yang.RpcMurderPlayer(yin); - Yanged[pc.PlayerId] = new(); + Yanged[player.PlayerId] = new(); } } diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index b8b91be4a5..be472ee4a5 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -56,7 +56,7 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); } public static void ResetBomb() @@ -66,8 +66,9 @@ public static void ResetBomb() LastBombedPlayer = byte.MaxValue; AgitaterHasBombed = false; } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (!lowLoad) return; if (CurrentBombedPlayer == 254) { SendRPC(CurrentBombedPlayer, LastBombedPlayer); @@ -133,9 +134,9 @@ public override void OnReportDeadBody(PlayerControl reported, NetworkedPlayerInf ResetBomb(); Logger.Info($"{killer.GetRealName()} bombed {target.GetRealName()} on report", "Agitater"); } - private void OnFixedUpdateOthers(PlayerControl player) + private void OnFixedUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { - if (!AgitaterHasBombed || CurrentBombedPlayer != player.PlayerId) return; + if (lowLoad || !AgitaterHasBombed || CurrentBombedPlayer != player.PlayerId) return; if (!player.IsAlive()) { @@ -159,9 +160,9 @@ private void OnFixedUpdateOthers(PlayerControl player) if (targetDistance.Any()) { var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); - var target = Utils.GetPlayerById(min.Key); + var target = min.Key.GetPlayer(); var KillRange = GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; - if (min.Value <= KillRange && !player.inVent && !target.inVent && player.RpcCheckAndMurder(target, true)) + if (min.Value <= KillRange && !player.inVent && !player.inMovingPlat && !target.inVent && !target.inMovingPlat && player.RpcCheckAndMurder(target, true)) { PassBomb(player, target); } diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index c183b5d59b..6b42da3dd2 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -131,7 +131,7 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe _Player.RpcSetVentInteraction(); _ = new LateTask(() => { NotifyRoles(SpecifySeer: _Player, ForceLoop: false); }, 1f, $"Update name for Arsonist {_Player?.PlayerId}", shoudLog: false); } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { if (ArsonistTimer.TryGetValue(player.PlayerId, out var arsonistTimerData)) { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 35db8d1b6a..e50f6659f8 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -254,9 +254,9 @@ public override void AfterMeetingTasks() if (playerIdList.Any()) BarrierList[playerIdList.First()].Clear(); } - public override void OnFixedUpdate(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!AllHasBread(player) || player.Is(CustomRoles.Famine)) return; + if (!lowLoad || !AllHasBread(player) || player.Is(CustomRoles.Famine)) return; player.RpcSetCustomRole(CustomRoles.Famine); player.Notify(GetString("BakerToFamine")); diff --git a/Roles/Neutral/BloodKnight.cs b/Roles/Neutral/BloodKnight.cs index 004df0774f..3feda46ed1 100644 --- a/Roles/Neutral/BloodKnight.cs +++ b/Roles/Neutral/BloodKnight.cs @@ -23,7 +23,7 @@ internal class BloodKnight : RoleBase private static OptionItem HasImpostorVision; private static OptionItem ProtectDuration; - private long? TimeStamp; + private long TimeStamp; public override void SetupCustomOption() { @@ -39,17 +39,15 @@ public override void Add(byte playerId) { TimeStamp = 0; } - private void SendRPC(byte playerId) + private void SendRPC() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); writer.WriteNetObject(_Player); - writer.Write(playerId); writer.Write(TimeStamp.ToString()); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - byte PlayerId = reader.ReadByte(); string Time = reader.ReadString(); TimeStamp = long.Parse(Time); } @@ -57,11 +55,11 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); - private bool InProtect(byte playerId) => TimeStamp > Utils.GetTimeStamp(); + private bool InProtect() => TimeStamp > Utils.GetTimeStamp(); public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { - if (InProtect(target.PlayerId)) + if (InProtect()) { killer.RpcGuardAndKill(target); if (!DisableShieldAnimations.GetBool()) target.RpcGuardAndKill(); @@ -76,27 +74,27 @@ public override void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl if (inMeeting || isSuicide) return; TimeStamp = Utils.GetTimeStamp() + (long)ProtectDuration.GetFloat(); - SendRPC(killer.PlayerId); + SendRPC(); killer.Notify(GetString("BKInProtect")); } public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool CanUseKillButton(PlayerControl pc) => true; - public override void OnFixedUpdateLowLoad(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (TimeStamp < Utils.GetTimeStamp() && TimeStamp != 0) + if (lowLoad && TimeStamp < nowTime && TimeStamp != 0) { TimeStamp = 0; - pc.Notify(GetString("BKProtectOut"), sendInLog: false); + player.Notify(GetString("BKProtectOut"), sendInLog: false); } } - public override string GetLowerText(PlayerControl pc, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + public override string GetLowerText(PlayerControl seer, PlayerControl seen, bool isForMeeting = false, bool isForHud = false) { - if (pc == null || isForMeeting || !isForHud || !pc.IsAlive()) return string.Empty; + if (!seer.IsAlive() || seer.PlayerId != seen.PlayerId || isForMeeting || !isForHud) return string.Empty; var str = new StringBuilder(); - if (InProtect(pc.PlayerId)) + if (InProtect()) { var remainTime = TimeStamp - Utils.GetTimeStamp(); str.Append(string.Format(GetString("BKSkillTimeRemain"), remainTime)); diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index 54babf8f34..a94c2b5308 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -133,8 +133,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } else return false; } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { + if (lowLoad) return; if (HackCDTimer > 180 || HackCDTimer < 0) HackCDTimer = 0; if (KCDTimer > 180 || KCDTimer < 0) KCDTimer = 0; if (MimicCDTimer > 180 || MimicCDTimer < 0) MimicCDTimer = 0; @@ -143,7 +144,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) bool change = false; foreach (var pc in hackedIdList) { - if (pc.Value + HackDuration.GetInt() < Utils.GetTimeStamp()) + if (pc.Value + HackDuration.GetInt() < nowTime) { hackedIdList.Remove(pc.Key); change = true; @@ -162,7 +163,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) MimicCDTimer = 0; MimicDurTimer = 0; - if (lastRpcSend <= Utils.GetTimeStamp() + 500) + if (lastRpcSend <= nowTime + 500) { SendRPC(); lastRpcSend += 9999; @@ -172,7 +173,7 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) if (MimicDurTimer > 0) { - try { MimicDurTimer = (int)(MimicDuration.GetInt() - (Utils.GetTimeStamp() - LastMimic)); } + try { MimicDurTimer = (int)(MimicDuration.GetInt() - (nowTime - LastMimic)); } catch { MimicDurTimer = 0; } if (MimicDurTimer > 180) MimicDurTimer = 0; } @@ -195,15 +196,15 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) if (HackCDTimer <= 0 && KCDTimer <= 0 && MimicCDTimer <= 0 && MimicDurTimer <= 0) return; - try { HackCDTimer = (int)(HackCooldown.GetInt() - (Utils.GetTimeStamp() - LastHack)); } + try { HackCDTimer = (int)(HackCooldown.GetInt() - (nowTime - LastHack)); } catch { HackCDTimer = 0; } if (HackCDTimer > 180 || HackCDTimer < 0) HackCDTimer = 0; - try { KCDTimer = (int)(KillCooldown.GetInt() - (Utils.GetTimeStamp() - LastKill)); } + try { KCDTimer = (int)(KillCooldown.GetInt() - (nowTime - LastKill)); } catch { KCDTimer = 0; } if (KCDTimer > 180 || KCDTimer < 0) KCDTimer = 0; - try { MimicCDTimer = (int)(MimicCooldown.GetInt() - (Utils.GetTimeStamp() - LastMimic)); } + try { MimicCDTimer = (int)(MimicCooldown.GetInt() - (nowTime - LastMimic)); } catch { MimicCDTimer = 0; } if (MimicCDTimer > 180 || MimicCDTimer < 0) MimicCDTimer = 0; @@ -214,10 +215,10 @@ public override void OnFixedUpdateLowLoad(PlayerControl player) } if (player.IsNonHostModdedClient()) // For mooded non host players, sync kcd per second { - if (lastRpcSend < Utils.GetTimeStamp()) + if (lastRpcSend < nowTime) { SendRPC(); - lastRpcSend = Utils.GetTimeStamp(); + lastRpcSend = nowTime; } } } diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 1cb1413c08..2019f897c0 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -31,7 +31,7 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); CanVent = BooleanOptionItem.Create(Id + 3, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - CantMoveInVents = BooleanOptionItem.Create(Id + 10, GeneralOption.CantMoveOnVents, false, TabGroup.NeutralRoles, false) + CantMoveInVents = BooleanOptionItem.Create(Id + 10, GeneralOption.CantMoveOnVents, true, TabGroup.NeutralRoles, false) .SetParent(CanVent); HasImpostorVision = BooleanOptionItem.Create(Id + 4, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index 2d6c6f66ca..44802e8ff9 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -25,7 +25,7 @@ internal class Pelican : RoleBase private static OptionItem HasImpostorVision; private static OptionItem CanVent; - private static readonly Dictionary> eatenList = []; + private static readonly Dictionary> eatenList = []; private static readonly Dictionary originalSpeed = []; public static Dictionary PelicanLastPosition = []; @@ -81,7 +81,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { int eatenNum = reader.ReadInt32(); eatenList.Remove(playerId); - List list = []; + HashSet list = []; for (int i = 0; i < eatenNum; i++) list.Add(reader.ReadByte()); eatenList.Add(playerId, list); @@ -263,20 +263,22 @@ private void ReturnEatenPlayerBack(PlayerControl pelican) GameEndCheckerForNormal.ShouldNotCheck = false; } - public override void OnFixedUpdateLowLoad(PlayerControl pelican) - { + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) + { + if (lowLoad) return; + Count--; if (Count > 0) return; - Count = 2; + Count = 4; - foreach (var pc in eatenList.Values) + if (eatenList.TryGetValue(player.PlayerId, out var playerList)) { - foreach (var tar in pc.ToArray()) + foreach (var tar in playerList.ToArray()) { - var target = Utils.GetPlayerById(tar); - if (target == null) continue; + var target = tar.GetPlayer(); + if (!target.IsAlive()) continue; var pos = GetBlackRoomPSForPelican(); var dis = Utils.GetDistance(pos, target.GetCustomPosition()); diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index 9f6bcaf199..e6570f7200 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -154,7 +154,7 @@ public override void OnReportDeadBody(PlayerControl W, NetworkedPlayerInfo L) { InfectActive = false; } - private void OnCheckPlayerPosition(PlayerControl player) + private void OnCheckPlayerPosition(PlayerControl player, bool lowLoad, long nowTime) { if (LateCheckWin) { diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Neutral/Poisoner.cs index 8be8e2c5cf..d8b9386101 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Neutral/Poisoner.cs @@ -71,19 +71,18 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } - public override void OnFixedUpdate(PlayerControl poisoner) + public override void OnFixedUpdate(PlayerControl poisoner, bool lowLoad, long nowTime) { var poisonerID = poisoner.PlayerId; List targetList = new(PoisonedPlayers.Where(b => b.Value.PoisonerId == poisonerID).Select(b => b.Key)); - for (var id = 0; id < targetList.Count; id++) + foreach (var targetId in targetList) { - var targetId = targetList[id]; var poisonedPoisoner = PoisonedPlayers[targetId]; if (poisonedPoisoner.KillTimer >= KillDelay) { - var target = Utils.GetPlayerById(targetId); + var target = targetId.GetPlayer(); KillPoisoned(poisoner, target); PoisonedPlayers.Remove(targetId); } diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs index 66198793e9..9e5113309c 100644 --- a/Roles/Neutral/Revolutionist.cs +++ b/Roles/Neutral/Revolutionist.cs @@ -207,7 +207,7 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe _Player.RpcSetVentInteraction(); _ = new LateTask(() => { NotifyRoles(SpecifySeer: _Player, ForceLoop: false); }, 1f, $"Update name for Revolutionist {_Player?.PlayerId}", shoudLog: false); } - private static void OnFixUpdateOthers(PlayerControl player) // jesus christ + private static void OnFixUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { if (RevolutionistTimer.TryGetValue(player.PlayerId, out var revolutionistTimerData)) { @@ -262,18 +262,17 @@ private static void OnFixUpdateOthers(PlayerControl player) // jesus christ } } } - if (IsDrawDone(player) && player.IsAlive()) + if (!lowLoad && IsDrawDone(player) && player.IsAlive()) { var playerId = player.PlayerId; if (RevolutionistStart.TryGetValue(playerId, out long startTime)) { if (RevolutionistLastTime.TryGetValue(playerId, out long lastTime)) { - long nowtime = GetTimeStamp(); - if (lastTime != nowtime) + if (lastTime != nowTime) { - RevolutionistLastTime[playerId] = nowtime; - lastTime = nowtime; + RevolutionistLastTime[playerId] = nowTime; + lastTime = nowTime; } int time = (int)(lastTime - startTime); int countdown = RevolutionistVentCountDown.GetInt() - time; @@ -283,7 +282,7 @@ private static void OnFixUpdateOthers(PlayerControl player) // jesus christ { GetDrawPlayerCount(playerId, out var list); - foreach (var pc in list.Where(x => x != null && x.IsAlive()).ToArray()) + foreach (var pc in list.Where(x => x.IsAlive()).ToArray()) { pc.Data.IsDead = true; pc.SetDeathReason(PlayerState.DeathReason.Sacrifice); diff --git a/Roles/Neutral/Seeker.cs b/Roles/Neutral/Seeker.cs index 7095d3eee7..e0cdc36507 100644 --- a/Roles/Neutral/Seeker.cs +++ b/Roles/Neutral/Seeker.cs @@ -112,9 +112,9 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf Main.AllPlayerSpeed[_state.PlayerId] = DefaultSpeed[_state.PlayerId]; } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (player == null) return; + if (lowLoad) return; var targetId = GetTarget(player); if (targetId == 0xff) return; diff --git a/Roles/Neutral/Shroud.cs b/Roles/Neutral/Shroud.cs index 025bbfadb9..d8d6641aa7 100644 --- a/Roles/Neutral/Shroud.cs +++ b/Roles/Neutral/Shroud.cs @@ -97,9 +97,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } - private void OnFixedUpdateOthers(PlayerControl shroud) + private void OnFixedUpdateOthers(PlayerControl shroud, bool lowLoad, long nowTime) { - if (!ShroudList.ContainsKey(shroud.PlayerId)) return; + if (lowLoad || !ShroudList.TryGetValue(shroud.PlayerId, out var shroudId)) return; if (!shroud.IsAlive() || Pelican.IsEaten(shroud.PlayerId)) { @@ -122,13 +122,12 @@ private void OnFixedUpdateOthers(PlayerControl shroud) if (targetDistance.Any()) { var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); - var target = Utils.GetPlayerById(min.Key); + var target = min.Key.GetPlayer(); var KillRange = NormalGameOptionsV08.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; - if (min.Value <= KillRange && shroud.CanMove && target.CanMove) + if (min.Value <= KillRange && !shroud.inVent && !shroud.inMovingPlat && !target.inVent && !target.inMovingPlat) { if (shroud.RpcCheckAndMurder(target, true)) { - var shroudId = ShroudList[shroud.PlayerId]; RPC.PlaySoundRPC(shroudId, Sounds.KillSound); target.SetDeathReason(PlayerState.DeathReason.Shrouded); shroud.RpcMurderPlayer(target); @@ -136,8 +135,7 @@ private void OnFixedUpdateOthers(PlayerControl shroud) Utils.MarkEveryoneDirtySettings(); ShroudList.Remove(shroud.PlayerId); SendRPC(byte.MaxValue, shroud.PlayerId, 2); - //Utils.NotifyRoles(SpecifySeer: shroud); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(shroudId), SpecifyTarget: shroud, ForceLoop: true); + Utils.NotifyRoles(SpecifySeer: shroudId.GetPlayer(), SpecifyTarget: shroud, ForceLoop: true); } } } diff --git a/Roles/Neutral/Solsticer.cs b/Roles/Neutral/Solsticer.cs index ff63d731f4..4ecfd7f8d7 100644 --- a/Roles/Neutral/Solsticer.cs +++ b/Roles/Neutral/Solsticer.cs @@ -165,30 +165,32 @@ public override void AfterMeetingTasks() MurderMessage = ""; patched = false; } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (patched && GameStates.IsInTask) + if (lowLoad) return; + if (patched) { Count--; if (Count > 0) return; - Count = 15; + Count = 4; var pos = ExtendedPlayerControl.GetBlackRoomPosition(); - var dis = Utils.GetDistance(pos, pc.GetCustomPosition()); + var dis = Utils.GetDistance(pos, player.GetCustomPosition()); if (dis < 1f) return; - if (GameStates.IsMeeting || !patched) return; - pc.RpcTeleport(pos); + if (!patched) return; + player.RpcTeleport(pos); } else if (GameStates.IsInGame) { - if (Main.AllPlayerSpeed[pc.PlayerId] != SolsticerSpeed.GetFloat()) + var playerId = player.PlayerId; + if (Main.AllPlayerSpeed[playerId] != SolsticerSpeed.GetFloat()) { - Main.AllPlayerSpeed[pc.PlayerId] = SolsticerSpeed.GetFloat(); - pc.MarkDirtySettings(); + Main.AllPlayerSpeed[playerId] = SolsticerSpeed.GetFloat(); + player.MarkDirtySettings(); } } } diff --git a/Roles/Neutral/Spiritcaller.cs b/Roles/Neutral/Spiritcaller.cs index 122fc166e4..a2509d74c3 100644 --- a/Roles/Neutral/Spiritcaller.cs +++ b/Roles/Neutral/Spiritcaller.cs @@ -63,7 +63,7 @@ public override void Add(byte playerId) if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.OnFixedUpdateLowLoadOthers.Add(OnFixedUpdateOthers); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); } } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); @@ -101,23 +101,25 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return true; } - private void OnFixedUpdateOthers(PlayerControl pc) + private void OnFixedUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { - if (pc.Is(CustomRoles.Spiritcaller)) + if (lowLoad) return; + var playerId = player.PlayerId; + if (player.Is(CustomRoles.Spiritcaller)) { - if (ProtectTimeStamp < Utils.GetTimeStamp() && ProtectTimeStamp != 0) + if (ProtectTimeStamp < nowTime && ProtectTimeStamp != 0) { ProtectTimeStamp = 0; } } - else if (PlayersHaunted.ContainsKey(pc.PlayerId) && PlayersHaunted[pc.PlayerId] < Utils.GetTimeStamp()) + else if (PlayersHaunted.TryGetValue(playerId, out var time) && time < nowTime) { - PlayersHaunted.Remove(pc.PlayerId); - pc.MarkDirtySettings(); + PlayersHaunted.Remove(playerId); + player?.MarkDirtySettings(); } } - public override string GetProgressText(byte PlayerId, bool cooooms) => Utils.ColorString(AbilityLimit >= 1 ? Utils.GetRoleColor(CustomRoles.Spiritcaller) : Color.gray, $"({AbilityLimit})"); + public override string GetProgressText(byte PlayerId, bool comms) => Utils.ColorString(AbilityLimit >= 1 ? Utils.GetRoleColor(CustomRoles.Spiritcaller) : Color.gray, $"({AbilityLimit})"); public static void HauntPlayer(PlayerControl target) { diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index 6b9c6e7e5d..fdf743476d 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -99,9 +99,9 @@ public static void ReceiveBodyRPC(MessageReader reader) else BodyReportCount[playerId] = body; } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!player.IsAlive()) return; + if (lowLoad || !player.IsAlive()) return; if (BodyReportCount[player.PlayerId] >= NumberOfReportsToWin.GetInt()) { diff --git a/Roles/Neutral/Wraith.cs b/Roles/Neutral/Wraith.cs index 150caf91d2..464a41515c 100644 --- a/Roles/Neutral/Wraith.cs +++ b/Roles/Neutral/Wraith.cs @@ -100,30 +100,30 @@ public override void AfterMeetingTasks() SendRPC(wraith); } } - public override void OnFixedUpdateLowLoad(PlayerControl player) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - var now = Utils.GetTimeStamp(); + if (lowLoad) return; - if (lastTime.TryGetValue(player.PlayerId, out var time) && time + (long)WraithCooldown.GetFloat() < now) + if (lastTime.TryGetValue(player.PlayerId, out var time) && time + (long)WraithCooldown.GetFloat() < nowTime) { lastTime.Remove(player.PlayerId); if (!player.IsModded()) player.Notify(GetString("WraithCanVent")); SendRPC(player); } - if (lastFixedTime != now) + if (lastFixedTime != nowTime) { - lastFixedTime = now; + lastFixedTime = nowTime; Dictionary newList = []; List refreshList = []; foreach (var it in InvisTime) { - var pc = Utils.GetPlayerById(it.Key); + var pc = it.Key.GetPlayer(); if (pc == null) continue; - var remainTime = it.Value + (long)WraithDuration.GetFloat() - now; + var remainTime = it.Value + (long)WraithDuration.GetFloat() - nowTime; if (remainTime < 0 || !pc.IsAlive()) { - lastTime.Add(pc.PlayerId, now); + lastTime.Add(pc.PlayerId, nowTime); pc?.MyPhysics?.RpcBootFromVent(ventedId.TryGetValue(pc.PlayerId, out var id) ? id : Main.LastEnteredVent[pc.PlayerId].Id); ventedId.Remove(pc.PlayerId); pc.Notify(GetString("WraithInvisStateOut")); From 52205d97f123142980f4909ddc76a5fc89f52d7c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 00:54:57 +0800 Subject: [PATCH 566/778] Use lowLoad & fix bugs --- Modules/BAUPlayersData.cs | 2 +- Modules/ExtendedPlayerControl.cs | 1 - Modules/RPC.cs | 2 -- Modules/Utils.cs | 1 - Patches/PlayerJoinAndLeftPatch.cs | 1 - Patches/onGameStartedPatch.cs | 3 --- Roles/(Ghosts)/Crewmate/Ghastly.cs | 2 +- Roles/(Ghosts)/Impostor/Possessor.cs | 1 - Roles/Crewmate/Alchemist.cs | 2 +- Roles/Crewmate/Lighter.cs | 3 +-- Roles/Crewmate/Spy.cs | 5 ++--- Roles/Crewmate/Telecommunication.cs | 1 - Roles/Impostor/AntiAdminer.cs | 1 - Roles/Neutral/Agitater.cs | 3 +-- Roles/Neutral/Baker.cs | 2 +- Roles/Neutral/Bandit.cs | 1 - Roles/Neutral/BloodKnight.cs | 2 +- 17 files changed, 9 insertions(+), 24 deletions(-) diff --git a/Modules/BAUPlayersData.cs b/Modules/BAUPlayersData.cs index be1d894753..e41fe4d0a0 100644 --- a/Modules/BAUPlayersData.cs +++ b/Modules/BAUPlayersData.cs @@ -3,7 +3,7 @@ namespace TOHE.Modules; public class BAUPlayersData { - private Dictionary _players = new Dictionary(); + private readonly Dictionary _players = new Dictionary(); public string this[NetworkedPlayerInfo key] { diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f0c3edb032..89b8ccca4a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -2,7 +2,6 @@ using Hazel; using InnerNet; using System; -using System.Linq; using System.Text; using TOHE.Modules; using TOHE.Patches; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 991c976767..95de319f93 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -4,8 +4,6 @@ using System; using System.Threading.Tasks; using TOHE.Modules; -using TOHE.Roles.AddOns.Common; -using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; using TOHE.Roles.Crewmate; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 7aa60ad032..d67ade4736 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -23,7 +23,6 @@ using TOHE.Roles.Core; using static TOHE.Translator; using TOHE.Patches; -using static UnityEngine.GraphicsBuffer; namespace TOHE; diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 750fc893ab..2a940fd5dc 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -6,7 +6,6 @@ using System.Text.RegularExpressions; using TOHE.Modules; using TOHE.Patches; -using TOHE.Roles.Core; using TOHE.Roles.Crewmate; using TOHE.Roles.Core.AssignManager; using static TOHE.Translator; diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 00238ecb2f..8b4ce687c6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -6,9 +6,6 @@ using TOHE.Patches; using TOHE.Modules; using TOHE.Modules.ChatManager; -using TOHE.Roles.AddOns.Common; -using TOHE.Roles.AddOns.Crewmate; -using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using BepInEx.Unity.IL2CPP.Utils.Collections; diff --git a/Roles/(Ghosts)/Crewmate/Ghastly.cs b/Roles/(Ghosts)/Crewmate/Ghastly.cs index 4bd7741d74..cadfe072f3 100644 --- a/Roles/(Ghosts)/Crewmate/Ghastly.cs +++ b/Roles/(Ghosts)/Crewmate/Ghastly.cs @@ -126,7 +126,7 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } public void OnFixUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) { - if (killertarget.Item1 == player.PlayerId + if (!lowLoad && killertarget.Item1 == player.PlayerId && LastTime.TryGetValue(player.PlayerId, out var now) && now + PossessDur.GetInt() <= nowTime) { _Player?.Notify(string.Format($"\n{ GetString("GhastlyExpired")}\n", player.GetRealName())); diff --git a/Roles/(Ghosts)/Impostor/Possessor.cs b/Roles/(Ghosts)/Impostor/Possessor.cs index 5febb5e318..0faf30a4dc 100644 --- a/Roles/(Ghosts)/Impostor/Possessor.cs +++ b/Roles/(Ghosts)/Impostor/Possessor.cs @@ -3,7 +3,6 @@ using UnityEngine; using static TOHE.Options; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles._Ghosts_.Impostor; diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index c9278853a8..d55c3a9f17 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -218,7 +218,7 @@ private static void OnFixedUpdatesBloodlus(PlayerControl player, bool lowLoad, l } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!lowLoad || !IsInvis(player.PlayerId)) return; + if (lowLoad || !IsInvis(player.PlayerId)) return; var needSync = false; foreach (var AlchemistInfo in InvisTime) diff --git a/Roles/Crewmate/Lighter.cs b/Roles/Crewmate/Lighter.cs index ea2ba8ca54..2ef916fe3f 100644 --- a/Roles/Crewmate/Lighter.cs +++ b/Roles/Crewmate/Lighter.cs @@ -62,8 +62,7 @@ public override void Remove(byte playerId) } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (lowLoad) return; - if (Timer.TryGetValue(player.PlayerId, out var ltime) && ltime + LighterSkillDuration.GetInt() < nowTime) + if (!lowLoad && Timer.TryGetValue(player.PlayerId, out var ltime) && ltime + LighterSkillDuration.GetInt() < nowTime) { Timer.Remove(player.PlayerId); if (!Options.DisableShieldAnimations.GetBool()) diff --git a/Roles/Crewmate/Spy.cs b/Roles/Crewmate/Spy.cs index f82509341c..24585eee66 100644 --- a/Roles/Crewmate/Spy.cs +++ b/Roles/Crewmate/Spy.cs @@ -102,10 +102,9 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (lowLoad) return; - if (!SpyRedNameList.Any()) return; + if (lowLoad || !SpyRedNameList.Any()) return; + change = false; - foreach (var x in SpyRedNameList) { if (x.Value + SpyRedNameDur.GetInt() < nowTime) diff --git a/Roles/Crewmate/Telecommunication.cs b/Roles/Crewmate/Telecommunication.cs index 7962128d06..e2aa49362f 100644 --- a/Roles/Crewmate/Telecommunication.cs +++ b/Roles/Crewmate/Telecommunication.cs @@ -1,6 +1,5 @@ using System; using System.Text; -using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Utils; using static TOHE.Translator; diff --git a/Roles/Impostor/AntiAdminer.cs b/Roles/Impostor/AntiAdminer.cs index 6f3c0dbb67..21ddf7f608 100644 --- a/Roles/Impostor/AntiAdminer.cs +++ b/Roles/Impostor/AntiAdminer.cs @@ -1,6 +1,5 @@ using System; using System.Text; -using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Utils; using static TOHE.Translator; diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index be472ee4a5..269d0bf421 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -68,8 +68,7 @@ public static void ResetBomb() } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!lowLoad) return; - if (CurrentBombedPlayer == 254) + if (!lowLoad && CurrentBombedPlayer == 254) { SendRPC(CurrentBombedPlayer, LastBombedPlayer); CurrentBombedPlayer = byte.MaxValue; diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index e50f6659f8..56157d7f3c 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -256,7 +256,7 @@ public override void AfterMeetingTasks() } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!lowLoad || !AllHasBread(player) || player.Is(CustomRoles.Famine)) return; + if (lowLoad || !AllHasBread(player) || player.Is(CustomRoles.Famine)) return; player.RpcSetCustomRole(CustomRoles.Famine); player.Notify(GetString("BakerToFamine")); diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs index 76ae58a64c..3d5267119a 100644 --- a/Roles/Neutral/Bandit.cs +++ b/Roles/Neutral/Bandit.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; -using TOHE.Roles.AddOns.Common; using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; diff --git a/Roles/Neutral/BloodKnight.cs b/Roles/Neutral/BloodKnight.cs index 3feda46ed1..4bf2694fae 100644 --- a/Roles/Neutral/BloodKnight.cs +++ b/Roles/Neutral/BloodKnight.cs @@ -83,7 +83,7 @@ public override void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (lowLoad && TimeStamp < nowTime && TimeStamp != 0) + if (!lowLoad && TimeStamp < nowTime && TimeStamp != 0) { TimeStamp = 0; player.Notify(GetString("BKProtectOut"), sendInLog: false); From 5ba0b10d0943e44645f0886af3376536f4241e71 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 01:23:07 +0800 Subject: [PATCH 567/778] Remove unused RPC --- Modules/RPC.cs | 28 ++------------ Roles/Crewmate/Captain.cs | 77 --------------------------------------- 2 files changed, 4 insertions(+), 101 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 95de319f93..c9f2892bb4 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -13,7 +13,7 @@ namespace TOHE; -enum CustomRPC : byte // 192/255 USED +enum CustomRPC : byte // 187/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 @@ -60,11 +60,6 @@ enum CustomRPC : byte // 192/255 USED SetKillOrSpell, SetKillOrHex, SetKillOrCurse, - SetCaptainTargetSpeed, - RevertCaptainTargetSpeed, - RevertCaptainAllTargetSpeed, - SetCaptainVotedTarget, - RevertCaptainVoteRemove, SetDousedPlayer, DoSpell, DoHex, @@ -72,13 +67,13 @@ enum CustomRPC : byte // 192/255 USED SniperSync, SetLoversPlayers, SetExecutionerTarget, - RemoveExecutionerTarget = 149, - SendFireworkerState = 151, // BetterCheck used 150 + RemoveExecutionerTarget, + SendFireworkerState, SetCurrentDousingTarget, SetEvilTrackerTarget, SetDrawPlayer, SetCrewpostorTasksDone, - SetCurrentDrawTarget, + SetCurrentDrawTarget = 151, // BetterCheck used 150 RpcPassBomb, SyncRomanticTarget, SyncVengefulRomanticTarget, @@ -446,21 +441,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c HudManager.Instance.Chat.SetVisible(show); } break; - case CustomRPC.SetCaptainTargetSpeed: - Captain.ReceiveRPCSetSpeed(reader); - break; - case CustomRPC.RevertCaptainTargetSpeed: - Captain.ReceiveRPCRevertSpeed(reader); - break; - case CustomRPC.RevertCaptainAllTargetSpeed: - Captain.ReceiveRPCRevertAllSpeed(); - break; - case CustomRPC.SetCaptainVotedTarget: - Captain.ReceiveRPCVoteAdd(reader); - break; - case CustomRPC.RevertCaptainVoteRemove: - Captain.ReceiveRPCVoteRemove(reader); - break; case CustomRPC.SetDrawPlayer: Revolutionist.ReceiveDrawPlayerRPC(reader); break; diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index 038dd17c9e..90eec0de86 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -62,82 +62,11 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); } - private static void SendRPCSetSpeed(byte targetId) - { - MessageWriter writer; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCaptainTargetSpeed, SendOption.Reliable, -1); - writer.Write(targetId); - writer.Write(OriginalSpeed[targetId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - return; - } - public static void ReceiveRPCSetSpeed(MessageReader reader) - { - byte targetId = reader.ReadByte(); - float speed = reader.ReadSingle(); - OriginalSpeed[targetId] = speed; - } - private static void SendRPCRevertSpeed(byte targetId) - { - MessageWriter writer; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RevertCaptainTargetSpeed, SendOption.Reliable, -1); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - return; - } - public static void ReceiveRPCRevertSpeed(MessageReader reader) - { - byte targetId = reader.ReadByte(); - if (OriginalSpeed.ContainsKey(targetId)) OriginalSpeed.Remove(targetId); - } - private static void SendRPCRevertAllSpeed() - { - MessageWriter writer; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RevertCaptainAllTargetSpeed, SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - return; - } public static void ReceiveRPCRevertAllSpeed() { OriginalSpeed.Clear(); } - public static void SendRPCVoteAdd(byte playerId, byte targetId) - { - MessageWriter writer; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCaptainVotedTarget, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - return; - } - public static void ReceiveRPCVoteAdd(MessageReader reader) - { - byte playerId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - if (!CaptainVoteTargets.ContainsKey(playerId)) CaptainVoteTargets[playerId] = []; - CaptainVoteTargets[playerId].Add(targetId); - } - private static void SendRPCVoteRemove(byte captainTarget = byte.MaxValue, CustomRoles? SelectedAddon = null) - { - MessageWriter writer; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RevertCaptainVoteRemove, SendOption.Reliable, -1); - writer.Write(captainTarget); - if (captainTarget != byte.MaxValue) writer.Write((int)SelectedAddon); - AmongUsClient.Instance.FinishRpcImmediately(writer); - return; - } - public static void ReceiveRPCVoteRemove(MessageReader reader) - { - byte captainTarget = reader.ReadByte(); - if (captainTarget != byte.MaxValue) - { - int? SelectedAddon = reader.ReadInt32(); - if (SelectedAddon != null) Main.PlayerStates[captainTarget].SubRoles.Remove((CustomRoles)SelectedAddon); - } - else CaptainVoteTargets.Clear(); - } - public static bool CrewCanFindCaptain() => OptionCrewCanFindCaptain.GetBool(); public override bool OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) @@ -159,7 +88,6 @@ public override bool OnTaskComplete(PlayerControl pc, int completedTaskCount, in var targetPC = allTargets.RandomElement(); var target = targetPC.PlayerId; OriginalSpeed[target] = Main.AllPlayerSpeed[target]; - SendRPCSetSpeed(target); Logger.Info($"{targetPC.GetNameWithRole().RemoveHtmlTags()} is chosen as the captain's target", "Captain Target"); Main.AllPlayerSpeed[target] = OptionReducedSpeed.GetFloat(); targetPC.SyncSettings(); @@ -170,7 +98,6 @@ public override bool OnTaskComplete(PlayerControl pc, int completedTaskCount, in Main.AllPlayerSpeed[target] = OriginalSpeed[target]; targetPC.SyncSettings(); OriginalSpeed.Remove(target); - SendRPCRevertSpeed(target); }, OptionReducedSpeedTime.GetFloat(), "Captain Revert Speed"); return true; @@ -215,10 +142,8 @@ public override void OnPlayerExiled(PlayerControl captain, NetworkedPlayerInfo e if (SelectedAddOn == null) continue; Main.PlayerStates[captainTarget].RemoveSubRole((CustomRoles)SelectedAddOn); Logger.Info($"Successfully removed {SelectedAddOn} addon from {GetPlayerById(captainTarget).GetNameWithRole()}", "Captain"); - SendRPCVoteRemove(captainTarget: captainTarget, SelectedAddOn) ; } CaptainVoteTargets.Clear(); - SendRPCVoteRemove(); } public override void OnReportDeadBody(PlayerControl y, NetworkedPlayerInfo x) { @@ -231,7 +156,6 @@ public override void OnReportDeadBody(PlayerControl y, NetworkedPlayerInfo x) } OriginalSpeed.Clear(); - SendRPCRevertAllSpeed(); } public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) @@ -253,7 +177,6 @@ public override void OnVoted(PlayerControl votedPlayer, PlayerControl votedTarge if (!CaptainVoteTargets[votedPlayer.PlayerId].Contains(votedTarget.PlayerId)) { CaptainVoteTargets[votedPlayer.PlayerId].Add(votedTarget.PlayerId); - SendRPCVoteAdd(votedPlayer.PlayerId, votedTarget.PlayerId); } } } From 67befd3223d1faed561f765277247662a23b9725 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 18:57:25 +0800 Subject: [PATCH 568/778] Check Invalid Color only host & Fix Hide & Seek assign & Double complete tasks --- Modules/Utils.cs | 4 ++-- Patches/PlayerControlPatch.cs | 14 +++++--------- Patches/onGameStartedPatch.cs | 25 ++++++++++++++++--------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index d67ade4736..b77a18d84e 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -69,9 +69,9 @@ public static void ErrorEnd(string text) } else { - MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AntiBlackout, SendOption.Reliable); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AntiBlackout, SendOption.Reliable); writer.Write(text); - writer.EndMessage(); + AmongUsClient.Instance.FinishRpcImmediately(writer); Logger.Fatal($"Error: {text} - I'm triggering critical error", "Anti-black"); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 605a750443..93445baa84 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1579,7 +1579,7 @@ public static void Postfix(PlayerPhysics __instance, [HarmonyArgument(0)] int id [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] class PlayerControlCompleteTaskPatch { - public static bool Prefix(PlayerControl __instance, object[] __args) + public static bool Prefix(PlayerControl __instance, uint idx) { if (GameStates.IsHideNSeek) return true; @@ -1601,14 +1601,10 @@ public static bool Prefix(PlayerControl __instance, object[] __args) } // Check others complete task - if (player != null && __args != null && __args.Any()) - { - int taskIndex = Convert.ToInt32(__args.First()); - var playerTask = player.myTasks.ToArray().FirstOrDefault(task => (int)task.Id == taskIndex); + var playerTask = player.myTasks.ToArray().FirstOrDefault(task => task.Id == idx); - if (playerTask != null) - CustomRoleManager.OthersCompleteThisTask(player, playerTask); - } + if (playerTask != null) + CustomRoleManager.OthersCompleteThisTask(player, playerTask); var playerSubRoles = player.GetCustomSubRoles(); @@ -1819,7 +1815,7 @@ class PlayerControlSetRolePatch public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) { // Skip first assign - if (RpcSetRoleReplacer.BlockSetRole) return true; + if (RpcSetRoleReplacer.BlockSetRole || GameStates.IsHideNSeek) return true; canOverrideRole = true; if (GameStates.IsHideNSeek || __instance == null) return true; diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 8b4ce687c6..b17be97972 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -2,6 +2,7 @@ using Hazel; using System; using InnerNet; +using System.Text; using UnityEngine; using TOHE.Patches; using TOHE.Modules; @@ -123,14 +124,20 @@ public static void Postfix(AmongUsClient __instance) Camouflage.Init(); CustomRoleManager.Initialize(); - var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId); - if (invalidColor.Any()) + if (AmongUsClient.Instance.AmHost) { - var msg = GetString("Error.InvalidColor"); - Logger.SendInGame(msg); - msg += " " + string.Join(", ", invalidColor.Select(p => $"{p.Data.PlayerName}")); - Utils.SendMessage(msg); - Logger.Error(msg, "CoStartGame"); + var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId); + if (invalidColor.Any()) + { + StringBuilder sb = new(); + sb.Append(GetString("Error.InvalidColor")); + Logger.SendInGame(sb.ToString()); + sb.Append($" {string.Join(", ", invalidColor.Where(pc => pc != null).Select(p => $"{Main.AllPlayerNames.GetValueOrDefault(p.PlayerId, "PlayerNotFound")}"))}"); + var msg = sb.ToString(); + Utils.SendMessage(msg); + Utils.ErrorEnd("Player Have Invalid Color"); + Logger.Error(msg, "CoStartGame"); + } } foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) @@ -157,9 +164,9 @@ public static void Postfix(AmongUsClient __instance) } else { - string realName = Main.AllPlayerNames.TryGetValue(pc.PlayerId, out var name) ? name : ""; + string realName = Main.AllPlayerNames.GetValueOrDefault(pc.PlayerId, string.Empty); //Logger.Info($"player id: {pc.PlayerId} {realName}", "FinallyBegin"); - if (realName == "") continue; + if (realName == string.Empty) continue; currentName = realName; pc.RpcSetName(realName); From f8fa97dba280b26a0d9c5440104c5708ef9636a1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 20:04:47 +0800 Subject: [PATCH 569/778] Recode Medic --- Modules/GuessManager.cs | 2 +- Modules/RPC.cs | 4 - Roles/AddOns/Common/Avanger.cs | 2 +- Roles/Crewmate/CopyCat.cs | 3 +- Roles/Crewmate/Judge.cs | 2 +- Roles/Crewmate/Medic.cs | 144 +++++++++++++++++++-------------- Roles/Impostor/Bomber.cs | 2 +- Roles/Impostor/Councillor.cs | 2 +- Roles/Impostor/Puppeteer.cs | 3 +- Roles/Impostor/Warlock.cs | 2 +- Roles/Neutral/HexMaster.cs | 1 - Roles/Neutral/Pelican.cs | 2 +- 12 files changed, 92 insertions(+), 77 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 1cbdb429cf..0412acad9d 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -214,7 +214,7 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("GuessedAsMundane")); return true; } - if (Medic.ProtectList.Contains(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + if (Medic.InProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) { pc.ShowInfoMessage(isUI, GetString("GuessShielded")); return true; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index c9f2892bb4..c267be803b 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -92,7 +92,6 @@ enum CustomRPC : byte // 187/255 USED SetSwapperVotes, SetMarkedPlayer, SetConcealerTimer, - SetMedicalerProtectList, PresidentEnd, PresidentReveal, SetBKTimer, @@ -563,9 +562,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetMarkedPlayer: Ninja.ReceiveRPC(reader); break; - case CustomRPC.SetMedicalerProtectList: - Medic.ReceiveRPCForProtectList(reader); - break; case CustomRPC.SyncGeneralOptions: byte paciefID = reader.ReadByte(); //playerstate: diff --git a/Roles/AddOns/Common/Avanger.cs b/Roles/AddOns/Common/Avanger.cs index 83af4214ad..4c59ec1077 100644 --- a/Roles/AddOns/Common/Avanger.cs +++ b/Roles/AddOns/Common/Avanger.cs @@ -24,7 +24,7 @@ public void Remove(byte playerId) public static void OnMurderPlayer(PlayerControl target) { - var pcList = Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != target.PlayerId && !Pelican.IsEaten(pc.PlayerId) && !Medic.ProtectList.Contains(pc.PlayerId) + var pcList = Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != target.PlayerId && !Pelican.IsEaten(pc.PlayerId) && !Medic.InProtected(pc.PlayerId) && !pc.Is(CustomRoles.Pestilence) && !pc.Is(CustomRoles.Necromancer) && !pc.Is(CustomRoles.PunchingBag) && !pc.Is(CustomRoles.Solsticer) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18)).ToList(); if (pcList.Any()) diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 912c53a008..4a07b4b5e2 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -88,7 +88,6 @@ CustomRoles.Doomsayer or // CopyCat cannot guessed roles because he can be know CustomRoles.EvilGuesser or CustomRoles.NiceGuesser or CustomRoles.Captain or - CustomRoles.Medic or // Bcz the medic is limited to only one player CustomRoles.TimeMaster or CustomRoles.Mole; //bcoz of single role @@ -161,7 +160,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr role = CustomRoles.Deputy; break; case 2: - role = CustomRoles.Crusader; // medic would make more sense but medic cant be used + role = CustomRoles.Medic; break; } break; diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index f19cfb33b8..48d5b8806b 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -162,7 +162,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) } else if (target.IsTransformedNeutralApocalypse()) judgeSuicide = true; else if (target.Is(CustomRoles.Trickster)) judgeSuicide = true; - else if (Medic.ProtectList.Contains(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + else if (Medic.InProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) { pc.ShowInfoMessage(isUI, GetString("GuessShielded")); return true; diff --git a/Roles/Crewmate/Medic.cs b/Roles/Crewmate/Medic.cs index 8aa9e48fa3..24d5164761 100644 --- a/Roles/Crewmate/Medic.cs +++ b/Roles/Crewmate/Medic.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using Hazel; +using InnerNet; using UnityEngine; using TOHE.Modules; using TOHE.Roles.Core; @@ -26,9 +27,11 @@ internal class Medic : RoleBase private static OptionItem ResetCooldown; public static OptionItem GuesserIgnoreShield; - public static readonly List ProtectList = []; + public static readonly HashSet GlobalProtectedList = []; + public static readonly Dictionary> ProtectedPlayers = []; - private static byte TempMarkProtected; + private readonly HashSet ProtectedList = []; + private readonly HashSet TempMarkProtected = []; private enum SelectOptionsList { @@ -64,53 +67,71 @@ public override void SetupCustomOption() } public override void Init() { - ProtectList.Clear(); - TempMarkProtected = byte.MaxValue; + GlobalProtectedList.Clear(); + ProtectedPlayers.Clear(); + ProtectedList.Clear(); + TempMarkProtected.Clear(); } public override void Add(byte playerId) { AbilityLimit = 1; + ProtectedPlayers[playerId] = []; } - private static void SendRPCForProtectList() + private void SendRPC() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetMedicalerProtectList, SendOption.Reliable, -1); - writer.Write(TempMarkProtected); - writer.Write(ProtectList.Count); - for (int i = 0; i < ProtectList.Count; i++) - writer.Write(ProtectList[i]); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(AbilityLimit); + writer.Write(TempMarkProtected.Count); + foreach (var markProtected in TempMarkProtected) + { + writer.Write(markProtected); + } + writer.Write(ProtectedList.Count); + foreach (var protect in ProtectedList) + { + writer.Write(protect); + } AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static void ReceiveRPCForProtectList(MessageReader reader) + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { - TempMarkProtected = reader.ReadByte(); - int count = reader.ReadInt32(); - ProtectList.Clear(); - for (int i = 0; i < count; i++) - ProtectList.Add(reader.ReadByte()); + float Limit = reader.ReadSingle(); + AbilityLimit = Limit; + + int countMarkProtected = reader.ReadInt32(); + TempMarkProtected.Clear(); + for (int i = 0; i < countMarkProtected; i++) + TempMarkProtected.Add(reader.ReadByte()); + + int countProtected = reader.ReadInt32(); + ProtectedList.Clear(); + for (int i = 0; i < countProtected; i++) + ProtectedList.Add(reader.ReadByte()); } - public static bool InProtect(byte id) - => ProtectList.Contains(id) && Main.PlayerStates.TryGetValue(id, out var ps) && !ps.IsDead; + public static bool InProtected(byte id) + => GlobalProtectedList.Contains(id) && Main.PlayerStates.TryGetValue(id, out var ps) && !ps.IsDead; - public bool CheckKillButton(byte playerId) - => !Main.PlayerStates[playerId].IsDead - && AbilityLimit > 0; + private bool InProtect(byte id) + => ProtectedList.Contains(id) && Main.PlayerStates.TryGetValue(id, out var ps) && !ps.IsDead; - public override bool CanUseKillButton(PlayerControl pc) => CheckKillButton(pc.PlayerId); - public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = CheckKillButton(id) ? 5f : 300f; - public override string GetProgressText(byte playerId, bool comms) => ColorString(CheckKillButton(playerId) ? GetRoleColor(CustomRoles.Medic).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public bool CheckKillButton() => AbilityLimit > 0; + + public override bool CanUseKillButton(PlayerControl pc) => CheckKillButton(); + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = CheckKillButton() ? 5f : 300f; + public override string GetProgressText(byte playerId, bool comms) => ColorString(CheckKillButton() ? GetRoleColor(CustomRoles.Medic).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (killer == null || target == null) return false; - if (!CheckKillButton(killer.PlayerId)) return false; - if (ProtectList.Contains(target.PlayerId)) return false; + if (!CheckKillButton() || ProtectedList.Contains(target.PlayerId)) return false; AbilityLimit--; - SendSkillRPC(); - ProtectList.Add(target.PlayerId); - TempMarkProtected = target.PlayerId; - SendRPCForProtectList(); + ProtectedPlayers[killer.PlayerId].Add(target.PlayerId); + GlobalProtectedList.Add(target.PlayerId); + ProtectedList.Add(target.PlayerId); + TempMarkProtected.Add(target.PlayerId); + SendRPC(); if (!Options.DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(); @@ -138,12 +159,11 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { + if (killer == null || target == null || _Player == null) return true; + if (!ProtectedList.Contains(target.PlayerId)) return false; - var Medics = Utils.GetPlayerListByRole(CustomRoles.Medic); - if (killer == null || target == null || Medics == null || !Medics.Any()) return true; - if (!ProtectList.Contains(target.PlayerId)) return false; - - SendRPCForProtectList(); + var medic = _Player; + SendRPC(); killer.RpcGuardAndKill(target); killer.SetKillCooldown(ResetCooldown.GetFloat()); @@ -154,22 +174,12 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr switch (KnowShieldBrokenOpt.GetValue()) { case 0: - foreach (var medic in Medics) - { - if (medic == null || !medic.IsAlive()) continue; - - medic.Notify(GetString("MedicKillerTryBrokenShieldTargetForMedic")); - } + medic?.Notify(GetString("MedicKillerTryBrokenShieldTargetForMedic")); target.RpcGuardAndKill(target); target.Notify(GetString("MedicKillerTryBrokenShieldTargetForTarget")); break; case 1: - foreach (var medic in Medics) - { - if (medic == null || !medic.IsAlive()) continue; - - medic.Notify(GetString("MedicKillerTryBrokenShieldTargetForMedic")); - } + medic?.Notify(GetString("MedicKillerTryBrokenShieldTargetForMedic")); break; case 2: target.RpcGuardAndKill(target); @@ -184,31 +194,43 @@ public override void AfterMeetingTasks() { if (!ShieldDeactivatesWhenMedicDies.GetBool()) return; - if (ShieldDeactivationIsVisibleOpt.GetInt() == 1) + if (ShieldDeactivationIsVisibleOpt.GetValue() is 1) { - TempMarkProtected = byte.MaxValue; - SendRPCForProtectList(); + TempMarkProtected.Clear(); + SendRPC(); NotifyRoles(); } } - private static void IsDead(PlayerControl target) + private void AfterMedicDeadTask(PlayerControl target) { if (!target.Is(CustomRoles.Medic)) return; if (!ShieldDeactivatesWhenMedicDies.GetBool()) return; - ProtectList.Clear(); + if (ProtectedPlayers.TryGetValue(target.PlayerId, out var protectedList)) + { + foreach (var protectedId in protectedList) + { + ProtectedPlayers[target.PlayerId].Remove(protectedId); + GlobalProtectedList.Remove(protectedId); + } + } + + ProtectedList.Clear(); Logger.Info($"{target.GetNameWithRole()} : Medic is dead", "Medic"); - if (ShieldDeactivationIsVisibleOpt.GetInt() == 0) + if (ShieldDeactivationIsVisibleOpt.GetValue() is 0) + { + TempMarkProtected.Clear(); + } + if (!target.IsDisconnected()) { - TempMarkProtected = byte.MaxValue; + SendRPC(); } - SendRPCForProtectList(); NotifyRoles(ForceLoop: true); } public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { - IsDead(target); + AfterMedicDeadTask(target); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); @@ -217,11 +239,11 @@ public override string GetMark(PlayerControl seer, PlayerControl target = null, { if (WhoCanSeeProtectOpt.GetInt() is 0 or 1) { - if (seer.PlayerId == target.PlayerId && (InProtect(seer.PlayerId) || TempMarkProtected == seer.PlayerId)) + if (seer.PlayerId == target.PlayerId && (InProtect(seer.PlayerId) || TempMarkProtected.Contains(seer.PlayerId))) { return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); } - else if (seer.PlayerId != target.PlayerId && (InProtect(target.PlayerId) || TempMarkProtected == target.PlayerId)) + else if (seer.PlayerId != target.PlayerId && (InProtect(target.PlayerId) || TempMarkProtected.Contains(target.PlayerId))) { return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); } @@ -234,11 +256,11 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b if (!seer.Is(CustomRoles.Medic)) { // The seer sees protect on himself - if (seer.PlayerId == target.PlayerId && (InProtect(seer.PlayerId) || TempMarkProtected == seer.PlayerId) && (WhoCanSeeProtectOpt.GetInt() is 0 or 2)) + if (seer.PlayerId == target.PlayerId && (InProtect(seer.PlayerId) || TempMarkProtected.Contains(seer.PlayerId)) && (WhoCanSeeProtectOpt.GetInt() is 0 or 2)) { return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); } - else if (seer.PlayerId != target.PlayerId && !seer.IsAlive() && (InProtect(target.PlayerId) || TempMarkProtected == target.PlayerId)) + else if (seer.PlayerId != target.PlayerId && !seer.IsAlive() && (InProtect(target.PlayerId) || TempMarkProtected.Contains(target.PlayerId))) { // Dead players see protect return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index d4cb27741c..1af494f5f4 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -76,7 +76,7 @@ public override void UnShapeShiftButton(PlayerControl shapeshifter) if (!target.IsModded()) target.KillFlash(); if (target.PlayerId == shapeshifter.PlayerId) continue; - if (!target.IsAlive() || Medic.ProtectList.Contains(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; + if (!target.IsAlive() || Medic.InProtected(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; var pos = shapeshifter.transform.position; var dis = Utils.GetDistance(pos, target.transform.position); diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index 61d2ffffc3..23b05ae823 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -162,7 +162,7 @@ public bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("ApocalypseImmune")); return true; } - else if (Medic.ProtectList.Contains(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + else if (Medic.InProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) { pc.ShowInfoMessage(isUI, GetString("GuessShielded")); return true; diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index 1a8ade36cf..5c0d83c864 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -85,8 +85,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (target.Is(CustomRoles.LazyGuy) || target.Is(CustomRoles.Lazy) - || target.Is(CustomRoles.NiceMini) && Mini.Age < 18 - || Medic.ProtectList.Contains(target.PlayerId)) + || target.Is(CustomRoles.NiceMini) && Mini.Age < 18) return false; return killer.CheckDoubleTrigger(target, () => diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index df0c0c49f0..e17aabd073 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -105,7 +105,7 @@ public override void OnShapeshift(PlayerControl shapeshifter, PlayerControl targ if (p.PlayerId == cp.PlayerId) continue; if (!WarlockCanKillSelf.GetBool() && p.PlayerId == shapeshifter.PlayerId) continue; if (!WarlockCanKillAllies.GetBool() && p.GetCustomRole().IsImpostor()) continue; - if (Pelican.IsEaten(p.PlayerId) || Medic.ProtectList.Contains(p.PlayerId)) continue; + if (Pelican.IsEaten(p.PlayerId) || Medic.InProtected(p.PlayerId)) continue; if (p.Is(CustomRoles.Glitch) || p.Is(CustomRoles.Pestilence)) continue; dis = Utils.GetDistance(cppos, p.transform.position); diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index e74069191c..c1131295c8 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -158,7 +158,6 @@ public override void AfterMeetingTasks() } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (Medic.ProtectList.Contains(target.PlayerId)) return false; if (target.IsTransformedNeutralApocalypse()) return false; if (NowSwitchTrigger == SwitchTriggerList.TriggerDouble) diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index 44802e8ff9..6a28f11749 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -114,7 +114,7 @@ public static bool CanEat(PlayerControl pc, byte id) } } - return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); + return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.InProtected(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); } public static Vector2 GetBlackRoomPSForPelican() { From 4035a8438188582689d9f627cad6e8d9d43e273b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 20:08:19 +0800 Subject: [PATCH 570/778] Rename --- Modules/GuessManager.cs | 2 +- Roles/AddOns/Common/Avanger.cs | 2 +- Roles/Crewmate/Judge.cs | 2 +- Roles/Crewmate/Medic.cs | 18 +++++++++--------- Roles/Impostor/Bomber.cs | 2 +- Roles/Impostor/Councillor.cs | 2 +- Roles/Impostor/Warlock.cs | 4 ++-- Roles/Neutral/Pelican.cs | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 0412acad9d..ade70a8dc3 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -214,7 +214,7 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("GuessedAsMundane")); return true; } - if (Medic.InProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + if (Medic.IsProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) { pc.ShowInfoMessage(isUI, GetString("GuessShielded")); return true; diff --git a/Roles/AddOns/Common/Avanger.cs b/Roles/AddOns/Common/Avanger.cs index 4c59ec1077..d121f011b6 100644 --- a/Roles/AddOns/Common/Avanger.cs +++ b/Roles/AddOns/Common/Avanger.cs @@ -24,7 +24,7 @@ public void Remove(byte playerId) public static void OnMurderPlayer(PlayerControl target) { - var pcList = Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != target.PlayerId && !Pelican.IsEaten(pc.PlayerId) && !Medic.InProtected(pc.PlayerId) + var pcList = Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != target.PlayerId && !Pelican.IsEaten(pc.PlayerId) && !Medic.IsProtected(pc.PlayerId) && !pc.Is(CustomRoles.Pestilence) && !pc.Is(CustomRoles.Necromancer) && !pc.Is(CustomRoles.PunchingBag) && !pc.Is(CustomRoles.Solsticer) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18)).ToList(); if (pcList.Any()) diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 48d5b8806b..4e00af3a3e 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -162,7 +162,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) } else if (target.IsTransformedNeutralApocalypse()) judgeSuicide = true; else if (target.Is(CustomRoles.Trickster)) judgeSuicide = true; - else if (Medic.InProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + else if (Medic.IsProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) { pc.ShowInfoMessage(isUI, GetString("GuessShielded")); return true; diff --git a/Roles/Crewmate/Medic.cs b/Roles/Crewmate/Medic.cs index 24d5164761..8d4dd7c84f 100644 --- a/Roles/Crewmate/Medic.cs +++ b/Roles/Crewmate/Medic.cs @@ -27,8 +27,8 @@ internal class Medic : RoleBase private static OptionItem ResetCooldown; public static OptionItem GuesserIgnoreShield; - public static readonly HashSet GlobalProtectedList = []; - public static readonly Dictionary> ProtectedPlayers = []; + private static readonly HashSet GlobalProtectedList = []; + private static readonly Dictionary> ProtectedPlayers = []; private readonly HashSet ProtectedList = []; private readonly HashSet TempMarkProtected = []; @@ -110,10 +110,10 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) ProtectedList.Add(reader.ReadByte()); } - public static bool InProtected(byte id) + public static bool IsProtected(byte id) => GlobalProtectedList.Contains(id) && Main.PlayerStates.TryGetValue(id, out var ps) && !ps.IsDead; - private bool InProtect(byte id) + private bool IsProtect(byte id) => ProtectedList.Contains(id) && Main.PlayerStates.TryGetValue(id, out var ps) && !ps.IsDead; public bool CheckKillButton() => AbilityLimit > 0; @@ -160,7 +160,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { if (killer == null || target == null || _Player == null) return true; - if (!ProtectedList.Contains(target.PlayerId)) return false; + if (!IsProtect(target.PlayerId)) return false; var medic = _Player; SendRPC(); @@ -239,11 +239,11 @@ public override string GetMark(PlayerControl seer, PlayerControl target = null, { if (WhoCanSeeProtectOpt.GetInt() is 0 or 1) { - if (seer.PlayerId == target.PlayerId && (InProtect(seer.PlayerId) || TempMarkProtected.Contains(seer.PlayerId))) + if (seer.PlayerId == target.PlayerId && (IsProtect(seer.PlayerId) || TempMarkProtected.Contains(seer.PlayerId))) { return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); } - else if (seer.PlayerId != target.PlayerId && (InProtect(target.PlayerId) || TempMarkProtected.Contains(target.PlayerId))) + else if (seer.PlayerId != target.PlayerId && (IsProtect(target.PlayerId) || TempMarkProtected.Contains(target.PlayerId))) { return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); } @@ -256,11 +256,11 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b if (!seer.Is(CustomRoles.Medic)) { // The seer sees protect on himself - if (seer.PlayerId == target.PlayerId && (InProtect(seer.PlayerId) || TempMarkProtected.Contains(seer.PlayerId)) && (WhoCanSeeProtectOpt.GetInt() is 0 or 2)) + if (seer.PlayerId == target.PlayerId && (IsProtect(seer.PlayerId) || TempMarkProtected.Contains(seer.PlayerId)) && (WhoCanSeeProtectOpt.GetInt() is 0 or 2)) { return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); } - else if (seer.PlayerId != target.PlayerId && !seer.IsAlive() && (InProtect(target.PlayerId) || TempMarkProtected.Contains(target.PlayerId))) + else if (seer.PlayerId != target.PlayerId && !seer.IsAlive() && (IsProtect(target.PlayerId) || TempMarkProtected.Contains(target.PlayerId))) { // Dead players see protect return ColorString(GetRoleColor(CustomRoles.Medic), "✚"); diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index 1af494f5f4..5a3e0cced8 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -76,7 +76,7 @@ public override void UnShapeShiftButton(PlayerControl shapeshifter) if (!target.IsModded()) target.KillFlash(); if (target.PlayerId == shapeshifter.PlayerId) continue; - if (!target.IsAlive() || Medic.InProtected(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; + if (!target.IsAlive() || Medic.IsProtected(target.PlayerId) || (target.Is(Custom_Team.Impostor) && ImpostorsSurviveBombs.GetBool()) || target.inVent || target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) continue; var pos = shapeshifter.transform.position; var dis = Utils.GetDistance(pos, target.transform.position); diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index 23b05ae823..6d3feeab70 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -162,7 +162,7 @@ public bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("ApocalypseImmune")); return true; } - else if (Medic.InProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + else if (Medic.IsProtected(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) { pc.ShowInfoMessage(isUI, GetString("GuessShielded")); return true; diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index e17aabd073..55c93d0097 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -104,8 +104,8 @@ public override void OnShapeshift(PlayerControl shapeshifter, PlayerControl targ { if (p.PlayerId == cp.PlayerId) continue; if (!WarlockCanKillSelf.GetBool() && p.PlayerId == shapeshifter.PlayerId) continue; - if (!WarlockCanKillAllies.GetBool() && p.GetCustomRole().IsImpostor()) continue; - if (Pelican.IsEaten(p.PlayerId) || Medic.InProtected(p.PlayerId)) continue; + if (!WarlockCanKillAllies.GetBool() && p.Is(Custom_Team.Impostor)) continue; + if (Pelican.IsEaten(p.PlayerId) || Medic.IsProtected(p.PlayerId)) continue; if (p.Is(CustomRoles.Glitch) || p.Is(CustomRoles.Pestilence)) continue; dis = Utils.GetDistance(cppos, p.transform.position); diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index 6a28f11749..a94ccb728c 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -114,7 +114,7 @@ public static bool CanEat(PlayerControl pc, byte id) } } - return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.InProtected(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); + return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.IsProtected(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); } public static Vector2 GetBlackRoomPSForPelican() { From 21b351a8c6120167cfb256320c9465ecb95790df Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 21:26:42 +0800 Subject: [PATCH 571/778] CopyCat Support Change Role Basis --- Modules/ExtendedPlayerControl.cs | 19 +++++++++----- Modules/Utils.cs | 2 +- Patches/ExilePatch.cs | 4 +-- Roles/Crewmate/CopyCat.cs | 45 ++++++++++++++------------------ 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 89b8ccca4a..bcafd754a8 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -117,9 +117,10 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new { var seerClientId = seer.GetClientId(); if (seerClientId == -1) continue; + var seerIsHost = seer.IsHost(); var self = player.PlayerId == seer.PlayerId; - if (!self && seer.HasDesyncRole() && !seer.IsHost()) + if (!self && seer.HasDesyncRole() && !seerIsHost) remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; else remeberRoleType = newRoleType; @@ -133,7 +134,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new if (seer.IsAlive()) { if (seerCustomRole.IsDesyncRole()) - remeberRoleType = RoleTypes.Scientist; + remeberRoleType = seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist; else remeberRoleType = seerRoleType; } @@ -144,7 +145,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new remeberRoleType = RoleTypes.CrewmateGhost; if (!playerIsKiller && seer.Is(Custom_Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; - RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? RoleTypes.Scientist : seerRoleType, seerCustomRole); + RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist : seerRoleType, seerCustomRole); seer.RpcSetRoleDesync(remeberRoleType, playerClientId); continue; } @@ -175,7 +176,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new } else { - if (!player.IsHost() && seer.HasDesyncRole()) remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + if (newRoleIsDesync) remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; else remeberRoleType = newRoleType; } @@ -233,9 +234,15 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new if (loggerRoleMap) { - foreach (var ((seerId, targetId), (roleType, customRole)) in RpcSetRoleReplacer.RoleMap) + foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - Logger.Info($"seer {Utils.GetPlayerInfoById(seerId)?.PlayerName}-{seerId}, target {Utils.GetPlayerInfoById(targetId)?.PlayerName}-{targetId} => {roleType}, {customRole}", "Role Map"); + var seerData = seer.Data; + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + var targetData = target.Data; + var (roleType, customRole) = seer.GetRoleMap(targetData.PlayerId); + Logger.Info($"seer {seerData?.PlayerName}-{seerData?.PlayerId}, target {targetData?.PlayerName}-{targetData?.PlayerId} => {roleType}, {customRole}", "Role Map"); + } } } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index b77a18d84e..ea140e24d5 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -636,7 +636,7 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = } - if (CopyCat.NoHaveTask(playerData.PlayerId)) hasTasks = false; + if (CopyCat.NoHaveTask(playerData.PlayerId, ForRecompute)) hasTasks = false; if (Main.TasklessCrewmate.Contains(playerData.PlayerId)) hasTasks = false; return hasTasks; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index f238570eee..00eb281c92 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -104,9 +104,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) FallFromLadder.Reset(); Utils.CountAlivePlayers(sendLog: true, checkGameEnd: Options.CurrentGameMode is CustomGameMode.Standard); - Utils.AfterMeetingTasks(); Utils.SyncAllSettings(); - Utils.NotifyRoles(NoCache: true); bool shouldPerformVentInteractions = false; foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) @@ -181,6 +179,8 @@ static void WrapUpFinalizer(NetworkedPlayerInfo exiled) Main.AfterMeetingDeathPlayers.Clear(); AntiBlackout.ResetAfterMeeting(); + Utils.AfterMeetingTasks(); + Utils.NotifyRoles(NoCache: true); }, 1.2f, "AfterMeetingDeathPlayers Task"); } //This should happen shortly after the Exile Controller wrap up finished for clients diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 4a07b4b5e2..94f5671d28 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -47,16 +47,23 @@ public override void Remove(byte playerId) //only to be used when copycat's role playerIdList.Remove(playerId); } public static bool CanCopyTeamChangingAddon() => CopyTeamChangingAddon.GetBool(); - public static bool NoHaveTask(byte playerId) => playerIdList.Contains(playerId); + public static bool NoHaveTask(byte playerId, bool ForRecompute) => playerIdList.Contains(playerId) && (playerId.GetPlayer().GetCustomRole().IsDesyncRole() || ForRecompute); public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => playerIdList.Contains(pc.PlayerId); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).IsAlive() ? CurrentKillCooldown : 300f; public static void UnAfterMeetingTasks() { - foreach (var player in playerIdList.ToArray()) + foreach (var playerId in playerIdList.ToArray()) { - var pc = Utils.GetPlayerById(player); - if (!pc.IsAlive()) continue; + var pc = playerId.GetPlayer(); + if (!pc.IsAlive()) + { + if (!pc.HasGhostRole()) + { + pc.RpcSetCustomRole(CustomRoles.CopyCat); + } + continue; + } //////////// /*remove the settings for current role*/ ///////////////////// var pcRole = pc.GetCustomRole(); @@ -67,6 +74,7 @@ public static void UnAfterMeetingTasks() pc.GetRoleClass()?.OnRemove(pc.PlayerId); } pc.RpcSetCustomRole(CustomRoles.CopyCat); + pc.RpcChangeRoleBasis(CustomRoles.CopyCat, loggerRoleMap: true); } pc.ResetKillCooldown(); } @@ -75,23 +83,9 @@ public static void UnAfterMeetingTasks() private static bool BlackList(CustomRoles role) { return role is CustomRoles.CopyCat or - //bcoz of vent cd - CustomRoles.Grenadier or - CustomRoles.Lighter or - CustomRoles.Pacifist or - CustomRoles.Veteran or - CustomRoles.Bastion or - CustomRoles.Addict or - CustomRoles.Chameleon or - CustomRoles.Alchemist or CustomRoles.Doomsayer or // CopyCat cannot guessed roles because he can be know others roles players CustomRoles.EvilGuesser or - CustomRoles.NiceGuesser or - CustomRoles.Captain or - CustomRoles.TimeMaster or - CustomRoles.Mole; - //bcoz of single role - // Other + CustomRoles.NiceGuesser; } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) @@ -134,16 +128,12 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr case CustomRoles.Berserker: role = CustomRoles.Reverie; break; - //case CustomRoles.EvilGuesser: - //case CustomRoles.Doomsayer: - // role = CustomRoles.NiceGuesser; - // break; case CustomRoles.Taskinator: role = CustomRoles.Benefactor; break; - //case CustomRoles.EvilTracker: - // role = CustomRoles.Tracker; - // break; + case CustomRoles.EvilTracker: + role = CustomRoles.TrackerTOHE; + break; case CustomRoles.AntiAdminer: role = CustomRoles.Telecommunication; break; @@ -171,7 +161,10 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr if (role != CustomRoles.CopyCat) { killer.RpcSetCustomRole(role); + killer.RpcChangeRoleBasis(role, loggerRoleMap: true); killer.GetRoleClass()?.OnAdd(killer.PlayerId); + killer.SyncSettings(); + Main.PlayerStates[killer.PlayerId].InitTask(killer); } if (CopyTeamChangingAddon.GetBool()) { From 947a736a70772987e819d256c0d8cdb4c67e8f9c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 21:28:57 +0800 Subject: [PATCH 572/778] Remove --- Roles/Crewmate/CopyCat.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 94f5671d28..ee2007d8a6 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -74,7 +74,7 @@ public static void UnAfterMeetingTasks() pc.GetRoleClass()?.OnRemove(pc.PlayerId); } pc.RpcSetCustomRole(CustomRoles.CopyCat); - pc.RpcChangeRoleBasis(CustomRoles.CopyCat, loggerRoleMap: true); + pc.RpcChangeRoleBasis(CustomRoles.CopyCat); } pc.ResetKillCooldown(); } @@ -161,7 +161,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr if (role != CustomRoles.CopyCat) { killer.RpcSetCustomRole(role); - killer.RpcChangeRoleBasis(role, loggerRoleMap: true); + killer.RpcChangeRoleBasis(role); killer.GetRoleClass()?.OnAdd(killer.PlayerId); killer.SyncSettings(); Main.PlayerStates[killer.PlayerId].InitTask(killer); From efcf5db457a97385468f751c605c43bd2abc8d1b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 22:31:13 +0800 Subject: [PATCH 573/778] Recode SoulCollector & Some changes --- Patches/IntroPatch.cs | 2 +- Patches/ShipStatusPatch.cs | 19 +++-- Roles/Neutral/SoulCollector.cs | 140 ++++++++++++++------------------- main.cs | 6 +- 4 files changed, 76 insertions(+), 91 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 4524c278bf..6d8b9e5915 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -61,7 +61,7 @@ public static void Prefix() { Logger.Warn($"Game ended? {AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd}", "ShipStatus.Begin"); } - }, 5f, "Assing Task For All"); + }, 4f, "Assing Task For All"); } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 8ac2307f37..fc0147c87e 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -247,18 +247,21 @@ public static void Postfix() { Logger.CurrentMethod(); - if (RolesIsAssigned && GameStates.IsNormalGame) + _ = new LateTask(() => { - foreach (var player in Main.AllPlayerControls) + if (RolesIsAssigned && GameStates.IsNormalGame) { - Main.PlayerStates[player.PlayerId].InitTask(player); - } + foreach (var player in Main.AllPlayerControls) + { + Main.PlayerStates[player.PlayerId].InitTask(player); + } - GameData.Instance.RecomputeTaskCounts(); - TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); - } + Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); + } + }, 1f, "Assign Custom Tasks"); } } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index ce706ae1b3..422a8ccbc7 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -6,12 +6,12 @@ using static TOHE.Translator; namespace TOHE.Roles.Neutral; + internal class SoulCollector : RoleBase { //===========================SETUP================================\\ private const int Id = 15300; - public static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Death); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; @@ -22,8 +22,7 @@ internal class SoulCollector : RoleBase public static OptionItem SoulCollectorCanVent; public static OptionItem DeathMeetingTimeIncrease; - private static readonly Dictionary SoulCollectorTarget = []; - private static readonly Dictionary SoulCollectorPoints = []; + private byte TargetId; public override void SetupCustomOption() { @@ -37,56 +36,45 @@ public override void SetupCustomOption() } public override void Init() { - playerIdList.Clear(); - SoulCollectorTarget.Clear(); - SoulCollectorPoints.Clear(); + TargetId = byte.MaxValue; } public override void Add(byte playerId) { - playerIdList.Add(playerId); - SoulCollectorTarget.TryAdd(playerId, byte.MaxValue); - SoulCollectorPoints.TryAdd(playerId, 0); + TargetId = byte.MaxValue; + AbilityLimit = 0; CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } - public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), SoulCollectorPoints.TryGetValue(playerId, out var x) ? $"({x}/{SoulCollectorPointsOpt.GetInt()})" : "Invalid"); + public override string GetProgressText(byte playerId, bool cvooms) => Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector).ShadeColor(0.25f), $"({AbilityLimit}/{SoulCollectorPointsOpt.GetInt()})"); public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("SoulCollectorKillButtonText")); - private void SendRPC(byte playerId) + private void SendRPC() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(_Player); //SetSoulCollectorLimit - writer.Write(playerId); - writer.Write(SoulCollectorPoints[playerId]); - writer.Write(SoulCollectorTarget[playerId]); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(_Player); + writer.Write(AbilityLimit); + writer.Write(TargetId); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - byte SoulCollectorId = reader.ReadByte(); - int Limit = reader.ReadInt32(); + var limit = reader.ReadSingle(); byte target = reader.ReadByte(); - if (SoulCollectorPoints.ContainsKey(SoulCollectorId)) - SoulCollectorPoints[SoulCollectorId] = Limit; - else - SoulCollectorPoints.Add(SoulCollectorId, 0); - - if (SoulCollectorTarget.ContainsKey(SoulCollectorId)) - SoulCollectorTarget[SoulCollectorId] = target; - else - SoulCollectorTarget.Add(SoulCollectorId, byte.MaxValue); + AbilityLimit = limit; + TargetId = target; } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => SoulCollectorTarget[seer.PlayerId] == seen.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠") : string.Empty; + => TargetId == seen.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (playerIdList.Any() && SoulCollectorTarget[playerIdList.First()] == target.PlayerId && seer.IsNeutralApocalypse() && seer.PlayerId != playerIdList.First()) + if (TargetId == target.PlayerId && seer.IsNeutralApocalypse() && seer.PlayerId != _Player.PlayerId) { return Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠"); } @@ -97,84 +85,78 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - if (SoulCollectorTarget[killer.PlayerId] != byte.MaxValue) + if (TargetId != byte.MaxValue) { killer.Notify(GetString("SoulCollectorTargetUsed")); return false; } - SoulCollectorTarget.Remove(killer.PlayerId); - SoulCollectorTarget.TryAdd(killer.PlayerId, target.PlayerId); + TargetId = target.PlayerId; Logger.Info($"{killer.GetNameWithRole()} predicted the death of {target.GetNameWithRole()}", "SoulCollector"); killer.Notify(string.Format(GetString("SoulCollectorTarget"), target.GetRealName())); return false; } public override void OnReportDeadBody(PlayerControl ryuak, NetworkedPlayerInfo iscute) { - PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); - foreach (var playerId in SoulCollectorTarget.Keys) + if (_Player == null) return; + PlayerControl sc = _Player; + + if (GetPassiveSouls.GetBool() && sc.IsAlive()) { - if (GetPassiveSouls.GetBool() && sc.IsAlive()) + AbilityLimit++; + _ = new LateTask(() => { - SoulCollectorPoints[playerId]++; - _ = new LateTask(() => - { - Utils.SendMessage(GetString("PassiveSoulGained"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + Utils.SendMessage(GetString("PassiveSoulGained"), sc.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - }, 3f, "Set Chat Visible for Everyone"); - } + }, 3f, "Passive Soul Gained"); + SendRPC(); } } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { - foreach (var (playerId, targetId) in SoulCollectorTarget) - { - if (targetId == byte.MaxValue) continue; + if (_Player == null) return; + if (TargetId == byte.MaxValue) return; - Main.PlayerStates.TryGetValue(targetId, out var playerState); - if (targetId == deadPlayer.PlayerId && playerState.IsDead && !playerState.Disconnected) + var playerId = _Player.PlayerId; + Main.PlayerStates.TryGetValue(TargetId, out var playerState); + if (TargetId == deadPlayer.PlayerId && playerState.IsDead && !playerState.Disconnected) + { + TargetId = byte.MaxValue; + AbilityLimit++; + if (GameStates.IsMeeting) { - SoulCollectorTarget[playerId] = byte.MaxValue; - SoulCollectorPoints[playerId]++; - if (GameStates.IsMeeting) _ = new LateTask(() => + _ = new LateTask(() => { Utils.SendMessage(GetString("SoulCollectorMeetingDeath"), playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - }, 3f, "Set Chat Visible for Everyone"); - Utils.GetPlayerById(playerId).Notify(GetString("SoulCollectorSoulGained")); - SendRPC(playerId); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(playerId), ForceLoop: false); + }, 3f, "Soul Collector Meeting Death"); } - if (SoulCollectorPoints[playerId] >= SoulCollectorPointsOpt.GetInt()) - { - SoulCollectorPoints[playerId] = SoulCollectorPointsOpt.GetInt(); - if (!GameStates.IsMeeting) { - PlayerControl sc = Utils.GetPlayerById(playerId); - sc.RpcSetCustomRole(CustomRoles.Death); - sc.Notify(GetString("SoulCollectorToDeath")); - sc.RpcGuardAndKill(sc); - } - } - } - } - public override void AfterMeetingTasks() - { - foreach (var playerId in SoulCollectorTarget.Keys) - { - SoulCollectorTarget[playerId] = byte.MaxValue; + + SendRPC(); + _Player.Notify(GetString("SoulCollectorSoulGained")); } - if (playerIdList.Any()) + if (AbilityLimit >= SoulCollectorPointsOpt.GetInt()) { - PlayerControl sc = Utils.GetPlayerById(playerIdList.First()); - if (sc == null) return; - - if (SoulCollectorPoints[sc.PlayerId] >= SoulCollectorPointsOpt.GetInt() && !sc.Is(CustomRoles.Death)) + if (!GameStates.IsMeeting) { + PlayerControl sc = _Player; sc.RpcSetCustomRole(CustomRoles.Death); sc.Notify(GetString("SoulCollectorToDeath")); sc.RpcGuardAndKill(sc); } } } + public override void AfterMeetingTasks() + { + if (_Player == null) return; + TargetId = byte.MaxValue; + + if (AbilityLimit >= SoulCollectorPointsOpt.GetInt() && !_Player.Is(CustomRoles.Death)) + { + _Player.RpcSetCustomRole(CustomRoles.Death); + _Player.Notify(GetString("SoulCollectorToDeath")); + _Player.RpcGuardAndKill(_Player); + } + } public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -188,7 +170,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl internal class Death : RoleBase { //===========================SETUP================================\\ - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.SoulCollector); + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Death); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; @@ -196,7 +178,7 @@ internal class Death : RoleBase public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) - => (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()); + => target.IsNeutralApocalypse() && seer.IsNeutralApocalypse(); public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(true); public override bool CanUseImpostorVentButton(PlayerControl pc) => SoulCollector.SoulCollectorCanVent.GetBool(); public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; diff --git a/main.cs b/main.cs index 4fd091d502..9c9be4a0fc 100644 --- a/main.cs +++ b/main.cs @@ -42,12 +42,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0919.210.00121"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 12 Hotfix 1"; + public const string PluginVersion = "2024.0921.210.00130"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 13"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 12 Hotfix 1 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 13 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 7abb0bd01339837bdab226895d6fd279d9140d7a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 22:31:56 +0800 Subject: [PATCH 574/778] Fix --- Roles/Neutral/SoulCollector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 422a8ccbc7..e10ee693b8 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -11,7 +11,7 @@ internal class SoulCollector : RoleBase { //===========================SETUP================================\\ private const int Id = 15300; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Death); + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.SoulCollector); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; From 595dc639acd4cc820120e0375cd8b5db11e63932 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 22:34:23 +0800 Subject: [PATCH 575/778] Some changes --- Roles/Neutral/SoulCollector.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index e10ee693b8..840f049b6f 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -97,23 +97,19 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override void OnReportDeadBody(PlayerControl ryuak, NetworkedPlayerInfo iscute) { - if (_Player == null) return; - PlayerControl sc = _Player; - - if (GetPassiveSouls.GetBool() && sc.IsAlive()) + if (!_Player.IsAlive() || !GetPassiveSouls.GetBool()) return; + + AbilityLimit++; + _ = new LateTask(() => { - AbilityLimit++; - _ = new LateTask(() => - { - Utils.SendMessage(GetString("PassiveSoulGained"), sc.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + Utils.SendMessage(GetString("PassiveSoulGained"), _Player.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - }, 3f, "Passive Soul Gained"); - SendRPC(); - } + }, 3f, "Passive Soul Gained"); + SendRPC(); } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { - if (_Player == null) return; + if (!_Player.IsAlive()) return; if (TargetId == byte.MaxValue) return; var playerId = _Player.PlayerId; @@ -147,7 +143,7 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } public override void AfterMeetingTasks() { - if (_Player == null) return; + if (!_Player.IsAlive()) return; TargetId = byte.MaxValue; if (AbilityLimit >= SoulCollectorPointsOpt.GetInt() && !_Player.Is(CustomRoles.Death)) From 8b525cd4ee3299d0f6b2a381f22140486f4936c6 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 22:52:26 +0800 Subject: [PATCH 576/778] Gangster and Admirer can't get Egoist --- Modules/CustomRolesHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 93f4ee6dc6..d13be9fdae 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -770,6 +770,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if (pc.Is(CustomRoles.Sidekick) || pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.Hurried) + || pc.Is(CustomRoles.Gangster) + || pc.Is(CustomRoles.Admirer) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; if (pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsMadmate() || pc.IsAnySubRole(sub => sub.IsConverted())) From 94c12058c26edb6fb9bbe0bc2a5a4d0e729e7385 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 22:56:03 +0800 Subject: [PATCH 577/778] Necroview check Admired and Madmate --- Roles/AddOns/Common/Necroview.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/Necroview.cs b/Roles/AddOns/Common/Necroview.cs index 759cb96dd8..3a4b3b602f 100644 --- a/Roles/AddOns/Common/Necroview.cs +++ b/Roles/AddOns/Common/Necroview.cs @@ -33,7 +33,7 @@ or CustomRoles.Recruit return Main.roleColors[CustomRoles.Knight]; } - if (customRole.IsImpostorTeamV2() || customRole.IsMadmate()) + if ((customRole.IsImpostorTeamV2() || customRole.IsMadmate() || target.Is(CustomRoles.Madmate)) && !target.Is(CustomRoles.Admired)) { return Main.roleColors[CustomRoles.Impostor]; } From 9c0dcb9be2f8c684ff82f640cc359babe4ffc35a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 21 Sep 2024 22:59:15 +0800 Subject: [PATCH 578/778] Remove --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7969586038..016fcc747a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -767,7 +767,7 @@ "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", - "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.\nNote: the Disperser itself will not teleport after they shapeshift and players who are in the vent will not teleport.", + "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.", "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Lights or Reactor), you cannot kill.", "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Comms or O2), then you can kill.", "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nDepending on the settings, Councillor will suicide when he judge his teammates.\nConverted Councillor can judge freely.", From 672539a8d2fe69c6d4f94cd4f62a162116adc14b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 03:07:25 +0800 Subject: [PATCH 579/778] Some fix & changes --- Modules/ExtendedPlayerControl.cs | 20 ++++++++------------ Modules/Utils.cs | 18 ++++++++++++++++++ Patches/ExilePatch.cs | 15 +-------------- Patches/IntroPatch.cs | 15 +-------------- 4 files changed, 28 insertions(+), 40 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index bcafd754a8..6efcbffe2f 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -108,6 +108,9 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var oldRoleIsDesync = playerRole.IsDesyncRole(); var newRoleIsDesync = newCustomRole.IsDesyncRole(); + var newVanillaRole = newCustomRole.GetVNRole(); + var newDesyncRole = newCustomRole.GetDYRole(); + switch (oldRoleIsDesync, newRoleIsDesync) { // Desync role to normal role @@ -121,7 +124,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var self = player.PlayerId == seer.PlayerId; if (!self && seer.HasDesyncRole() && !seerIsHost) - remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + remeberRoleType = newVanillaRole is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; else remeberRoleType = newRoleType; // Set role type for seer @@ -140,10 +143,8 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new } else { - var playerIsKiller = playerRole.IsImpostor() || newRoleIsDesync; - remeberRoleType = RoleTypes.CrewmateGhost; - if (!playerIsKiller && seer.Is(Custom_Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; + if (!newCustomRole.IsImpostor() && seer.Is(Custom_Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist : seerRoleType, seerCustomRole); seer.RpcSetRoleDesync(remeberRoleType, playerClientId); @@ -171,12 +172,12 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new remeberRoleType = player.IsHost() ? RoleTypes.Crewmate : RoleTypes.Impostor; // For Desync Shapeshifter - if (newCustomRole.GetDYRole() is RoleTypes.Shapeshifter) + if (newDesyncRole is RoleTypes.Shapeshifter) remeberRoleType = RoleTypes.Shapeshifter; } else { - if (newRoleIsDesync) remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + if (newRoleIsDesync) remeberRoleType = newVanillaRole is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; else remeberRoleType = newRoleType; } @@ -188,16 +189,11 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var seerCustomRole = seer.GetRoleMap().CustomRole; if (seer.IsAlive()) { - if (newCustomRole.IsDesyncRole()) - remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + remeberRoleType = newVanillaRole is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; } else { - var playerIsKiller = playerRole.IsImpostor() || newRoleIsDesync; - remeberRoleType = RoleTypes.CrewmateGhost; - if (!playerIsKiller && seer.Is(Custom_Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; - RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist, seerCustomRole); seer.RpcSetRoleDesync(remeberRoleType, playerClientId); continue; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ea140e24d5..f5bd38cd2e 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2221,6 +2221,24 @@ public static void SetAllVentInteractions() { VentSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast()); } + public static void CheckAndSetVentInteractions() + { + bool shouldPerformVentInteractions = false; + + foreach (var pc in Main.AllPlayerControls) + { + if (VentSystemDeterioratePatch.BlockVentInteraction(pc)) + { + VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + shouldPerformVentInteractions = true; + } + } + + if (shouldPerformVentInteractions) + { + SetAllVentInteractions(); + } + } public static bool DeathReasonIsEnable(this PlayerState.DeathReason reason, bool checkbanned = false) { static bool BannedReason(PlayerState.DeathReason rso) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 00eb281c92..4f9874273a 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -105,20 +105,7 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) FallFromLadder.Reset(); Utils.CountAlivePlayers(sendLog: true, checkGameEnd: Options.CurrentGameMode is CustomGameMode.Standard); Utils.SyncAllSettings(); - - bool shouldPerformVentInteractions = false; - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - if (pc.BlockVentInteraction()) - { - VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - shouldPerformVentInteractions = true; - } - } - if (shouldPerformVentInteractions) - { - Utils.SetAllVentInteractions(); - } + Utils.CheckAndSetVentInteractions(); if (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA) { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 6d8b9e5915..6157d15774 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -685,20 +685,7 @@ public static void Postfix() Logger.Error($"Error: {error}", "FFA chat visible"); } - bool shouldPerformVentInteractions = false; - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - if (pc.BlockVentInteraction()) - { - VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - shouldPerformVentInteractions = true; - } - } - - if (shouldPerformVentInteractions) - { - Utils.SetAllVentInteractions(); - } + Utils.CheckAndSetVentInteractions(); } Logger.Info("OnDestroy", "IntroCutscene"); From f778a71d3c2ce4f614358261bad59e4d22593b2d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 03:58:36 +0800 Subject: [PATCH 580/778] Add roles in CopyCrewVar & Can Random Spawn In First Round --- Modules/Utils.cs | 5 ++- Patches/ExilePatch.cs | 48 ++++++++++++---------- Patches/IntroPatch.cs | 51 ++++++----------------- Patches/RandomSpawnPatch.cs | 5 +++ Resources/Lang/en_US.json | 1 + Roles/Crewmate/Captain.cs | 3 +- Roles/Crewmate/CopyCat.cs | 81 ++++++++++++------------------------- Roles/Impostor/Puppeteer.cs | 1 - Roles/Neutral/HexMaster.cs | 1 - 9 files changed, 75 insertions(+), 121 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f5bd38cd2e..a70df6bbf5 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1839,7 +1839,7 @@ public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => private static readonly StringBuilder TargetMark = new(20); public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || OnPlayerLeftPatch.StartingProcessing) return; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro || OnPlayerLeftPatch.StartingProcessing) return; if (Main.MeetingIsStarted && !isForMeeting) return; if (Main.AllPlayerControls == null) return; @@ -1856,7 +1856,7 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon } public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || OnPlayerLeftPatch.StartingProcessing) return Task.CompletedTask; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro || OnPlayerLeftPatch.StartingProcessing) return Task.CompletedTask; if (Main.MeetingIsStarted && !isForMeeting) return Task.CompletedTask; if (Main.AllPlayerControls == null) return Task.CompletedTask; @@ -2307,6 +2307,7 @@ var Breason when BannedReason(Breason) => false, public static void AfterMeetingTasks() { ChatManager.ClearLastSysMsg(); + FallFromLadder.Reset(); if (Diseased.IsEnable) Diseased.AfterMeetingTasks(); if (Antidote.IsEnable) Antidote.AfterMeetingTasks(); diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 4f9874273a..68d2e0e459 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -1,6 +1,5 @@ using AmongUs.Data; using System; -using TOHE.Patches; using TOHE.Roles.Core; using TOHE.Roles.Neutral; @@ -12,6 +11,10 @@ class ExileControllerWrapUpPatch [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { + public static void Prefix() + { + CheckAndDoRandomSpawn(); + } public static void Postfix(ExileController __instance) { try @@ -32,6 +35,10 @@ public static void Postfix(ExileController __instance) [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))] class AirshipExileControllerPatch { + public static void Prefix() + { + CheckAndDoRandomSpawn(); + } public static void Postfix(AirshipExileController __instance) { try @@ -48,7 +55,23 @@ public static void Postfix(AirshipExileController __instance) } } } - static void WrapUpPostfix(NetworkedPlayerInfo exiled) + private static void CheckAndDoRandomSpawn() + { + if (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA) + { + RandomSpawn.SpawnMap spawnMap = Utils.GetActiveMapName() switch + { + MapNames.Skeld => new RandomSpawn.SkeldSpawnMap(), + MapNames.Mira => new RandomSpawn.MiraHQSpawnMap(), + MapNames.Polus => new RandomSpawn.PolusSpawnMap(), + MapNames.Dleks => new RandomSpawn.DleksSpawnMap(), + MapNames.Fungle => new RandomSpawn.FungleSpawnMap(), + _ => null, + }; + if (spawnMap != null) Main.AllPlayerControls.Do(spawnMap.RandomTeleport); + } + } + private static void WrapUpPostfix(NetworkedPlayerInfo exiled) { if (AntiBlackout.BlackOutIsActive) exiled = AntiBlackout_LastExiled; @@ -102,31 +125,12 @@ static void WrapUpPostfix(NetworkedPlayerInfo exiled) Main.MeetingIsStarted = false; Main.MeetingsPassed++; - FallFromLadder.Reset(); Utils.CountAlivePlayers(sendLog: true, checkGameEnd: Options.CurrentGameMode is CustomGameMode.Standard); Utils.SyncAllSettings(); Utils.CheckAndSetVentInteractions(); - - if (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA) - { - _ = new LateTask(() => - { - RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null, - }; - if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); - - }, 0.8f, "Random Spawn After Meeting"); - } } - static void WrapUpFinalizer(NetworkedPlayerInfo exiled) + private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { // Even if an exception occurs in WrapUpPostfix, this is the only part that will be executed reliably. if (AmongUsClient.Instance.AmHost) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 6157d15774..d1f40105fd 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading.Tasks; using TOHE.Modules; -using TOHE.Patches; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; using TOHE.Roles.Neutral; @@ -80,15 +79,21 @@ public static void Postfix(IntroCutscene __instance) Utils.DoNotifyRoles(NoCache: true); } - // Show role map - /*foreach (var seer in Main.AllPlayerControls) + var mapName = Utils.GetActiveMapName(); + Logger.Msg($"{mapName}", "Map"); + if (AmongUsClient.Instance.AmHost && RandomSpawn.IsRandomSpawn() && RandomSpawn.CanSpawnInFirstRound()) { - foreach (var target in Main.AllPlayerControls) + RandomSpawn.SpawnMap spawnMap = mapName switch { - RpcSetRoleReplacer.RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map); - Logger.Info($"seer {seer?.Data?.PlayerName}-{seer.PlayerId}, target {target?.Data?.PlayerName}-{target.PlayerId} => {map.roleType}, {map.customRole}", "Role Map"); - } - }*/ + MapNames.Skeld => new RandomSpawn.SkeldSpawnMap(), + MapNames.Mira => new RandomSpawn.MiraHQSpawnMap(), + MapNames.Polus => new RandomSpawn.PolusSpawnMap(), + MapNames.Dleks => new RandomSpawn.DleksSpawnMap(), + MapNames.Fungle => new RandomSpawn.FungleSpawnMap(), + _ => null, + }; + if (spawnMap != null) Main.AllPlayerControls.Do(spawnMap.RandomTeleport); + } _ = new LateTask(() => { @@ -266,20 +271,6 @@ public static void Prefix() RPC.RpcVersionCheck(); FFAManager.SetData(); - - if (AmongUsClient.Instance.AmHost && GameStates.IsHideNSeek && RandomSpawn.IsRandomSpawn()) - { - RandomSpawn.SpawnMap map = Utils.GetActiveMapId() switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null, - }; - if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); - } } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] @@ -587,22 +578,6 @@ public static void Prefix() } Utils.DoNotifyRoles(NoCache: true); - - if (GameStates.IsNormalGame && (RandomSpawn.IsRandomSpawn() || Options.CurrentGameMode == CustomGameMode.FFA)) - { - var mapId = Utils.GetActiveMapId(); - Logger.Msg($"Check map {mapId}", "Map"); - RandomSpawn.SpawnMap map = mapId switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null, - }; - if (map != null) Main.AllPlayerControls.Do(map.RandomTeleport); - } } } public static void Postfix() diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs index d1d44d7560..442b5d70e7 100644 --- a/Patches/RandomSpawnPatch.cs +++ b/Patches/RandomSpawnPatch.cs @@ -146,16 +146,19 @@ public static void AirshipSpawn(PlayerControl player) Main.PlayerStates[player.PlayerId].HasSpawned = true; } public static bool IsRandomSpawn() => RandomSpawnMode.GetBool(); + public static bool CanSpawnInFirstRound() => SpawnInFirstRound.GetBool(); private enum RandomSpawnOpt { RandomSpawnMode, + RandomSpawn_SpawnInFirstRound, RandomSpawn_SpawnRandomLocation, RandomSpawn_AirshipAdditionalSpawn, RandomSpawn_SpawnRandomVents } private static OptionItem RandomSpawnMode; + private static OptionItem SpawnInFirstRound; private static OptionItem SpawnRandomLocation; private static OptionItem AirshipAdditionalSpawn; private static OptionItem SpawnRandomVents; @@ -165,6 +168,8 @@ public static void SetupCustomOption() RandomSpawnMode = BooleanOptionItem.Create(60470, RandomSpawnOpt.RandomSpawnMode, false, TabGroup.ModSettings, false) .HideInFFA() .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + SpawnInFirstRound = BooleanOptionItem.Create(60476, RandomSpawnOpt.RandomSpawn_SpawnInFirstRound, true, TabGroup.ModSettings, false) + .SetParent(RandomSpawnMode); SpawnRandomLocation = BooleanOptionItem.Create(60471, RandomSpawnOpt.RandomSpawn_SpawnRandomLocation, true, TabGroup.ModSettings, false) .SetParent(RandomSpawnMode); AirshipAdditionalSpawn = BooleanOptionItem.Create(60472, RandomSpawnOpt.RandomSpawn_AirshipAdditionalSpawn, true, TabGroup.ModSettings, false) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 016fcc747a..b1904302ef 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1276,6 +1276,7 @@ "DisableAirshipGapRoomLightsPanel": "Disable Gap Room Lights Panel (Airship)", "DisableAirshipCargoLightsPanel": "Disable Cargo Lights Panel (Airship)", "RandomSpawnMode": "Random Spawns Mode", + "RandomSpawn_SpawnInFirstRound": "Active On Round One", "RandomSpawn_SpawnRandomLocation": "Random Spawns In Locations", "RandomSpawn_AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", "RandomSpawn_SpawnRandomVents": "Random Spawns On Vents", diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index 90eec0de86..e3480bcb8b 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -1,5 +1,4 @@ -using Hazel; -using static TOHE.Options; +using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index ee2007d8a6..1759c97c31 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -99,62 +99,33 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } if (CopyCrewVar.GetBool()) { - switch (role) + role = role switch { - case CustomRoles.Eraser: - role = CustomRoles.Cleanser; - break; - case CustomRoles.Nemesis: - role = CustomRoles.Retributionist; - break; - case CustomRoles.Visionary: - role = CustomRoles.Oracle; - break; - case CustomRoles.Workaholic: - role = CustomRoles.Snitch; - break; - case CustomRoles.Sunnyboy: - role = CustomRoles.Doctor; - break; - case CustomRoles.Vindicator: - case CustomRoles.Pickpocket: - role = CustomRoles.Mayor; - break; - case CustomRoles.Councillor: - role = CustomRoles.Judge; - break; - case CustomRoles.Arrogance: - case CustomRoles.Juggernaut: - case CustomRoles.Berserker: - role = CustomRoles.Reverie; - break; - case CustomRoles.Taskinator: - role = CustomRoles.Benefactor; - break; - case CustomRoles.EvilTracker: - role = CustomRoles.TrackerTOHE; - break; - case CustomRoles.AntiAdminer: - role = CustomRoles.Telecommunication; - break; - case CustomRoles.Pursuer: - role = CustomRoles.Deceiver; - break; - case CustomRoles.Baker: - switch (Baker.CurrentBread()) - { - case 0: - role = CustomRoles.Overseer; - break; - case 1: - role = CustomRoles.Deputy; - break; - case 2: - role = CustomRoles.Medic; - break; - } - break; - } + CustomRoles.Swooper or CustomRoles.Wraith => CustomRoles.Chameleon, + CustomRoles.Stealth => CustomRoles.Grenadier, + CustomRoles.TimeThief => CustomRoles.TimeManager, + CustomRoles.Consigliere => CustomRoles.Overseer, + CustomRoles.CursedWolf or CustomRoles.Jinx => CustomRoles.Veteran, + CustomRoles.SerialKiller => CustomRoles.Addict, + CustomRoles.Miner => CustomRoles.Mole, + CustomRoles.Twister => CustomRoles.TimeMaster, + CustomRoles.Disperser => CustomRoles.Transporter, + CustomRoles.Eraser => CustomRoles.Cleanser, + CustomRoles.Visionary => CustomRoles.Oracle, + CustomRoles.Workaholic => CustomRoles.Snitch, + CustomRoles.Sunnyboy => CustomRoles.Doctor, + CustomRoles.Councillor => CustomRoles.Judge, + CustomRoles.Vindicator or CustomRoles.Pickpocket => CustomRoles.Mayor, + CustomRoles.Arrogance or CustomRoles.Juggernaut or CustomRoles.Berserker => CustomRoles.Reverie, + CustomRoles.Taskinator => CustomRoles.Benefactor, + CustomRoles.EvilTracker => CustomRoles.TrackerTOHE, + CustomRoles.AntiAdminer => CustomRoles.Telecommunication, + CustomRoles.Pursuer => CustomRoles.Deceiver, + CustomRoles.Baker when Baker.CurrentBread() is 0 => CustomRoles.Overseer, + CustomRoles.Baker when Baker.CurrentBread() is 1 => CustomRoles.Deputy, + CustomRoles.Baker when Baker.CurrentBread() is 2 => CustomRoles.Medic, + _ => role + }; } if (role.IsCrewmate()) { diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index 5c0d83c864..054a6da75e 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -2,7 +2,6 @@ using Hazel; using TOHE.Modules; using TOHE.Roles.Core; -using TOHE.Roles.Crewmate; using TOHE.Roles.Double; using TOHE.Roles.Neutral; using UnityEngine; diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index c1131295c8..46fcc55897 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -2,7 +2,6 @@ using Hazel; using UnityEngine; using System.Text; -using TOHE.Roles.Crewmate; using static TOHE.Options; using static TOHE.Translator; From bbb929afd2e4f9ab36b7d003e13db86549d3a726 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 04:03:12 +0800 Subject: [PATCH 581/778] Fix --- Patches/LadderPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/LadderPatch.cs b/Patches/LadderPatch.cs index 69e60aed00..a05fa2ce35 100644 --- a/Patches/LadderPatch.cs +++ b/Patches/LadderPatch.cs @@ -27,7 +27,7 @@ public static void OnClimbLadder(PlayerPhysics player, Ladder source) } public static void FixedUpdate(PlayerControl player) { - if (player.Data.Disconnected) return; + if (player.Data.Disconnected || Main.MeetingIsStarted) return; if (TargetLadderData.ContainsKey(player.PlayerId)) { if (Utils.GetDistance(TargetLadderData[player.PlayerId], player.transform.position) < 0.5f) From 49bf3650c4ef0b54aacd9ec0c2ce891aa9cc693c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 04:16:07 +0800 Subject: [PATCH 582/778] Add --- Roles/Crewmate/CopyCat.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 1759c97c31..09ebb1fac4 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -101,13 +101,12 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr { role = role switch { - CustomRoles.Swooper or CustomRoles.Wraith => CustomRoles.Chameleon, CustomRoles.Stealth => CustomRoles.Grenadier, CustomRoles.TimeThief => CustomRoles.TimeManager, CustomRoles.Consigliere => CustomRoles.Overseer, - CustomRoles.CursedWolf or CustomRoles.Jinx => CustomRoles.Veteran, CustomRoles.SerialKiller => CustomRoles.Addict, CustomRoles.Miner => CustomRoles.Mole, + CustomRoles.PotionMaster => CustomRoles.Overseer, CustomRoles.Twister => CustomRoles.TimeMaster, CustomRoles.Disperser => CustomRoles.Transporter, CustomRoles.Eraser => CustomRoles.Cleanser, @@ -115,12 +114,14 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr CustomRoles.Workaholic => CustomRoles.Snitch, CustomRoles.Sunnyboy => CustomRoles.Doctor, CustomRoles.Councillor => CustomRoles.Judge, - CustomRoles.Vindicator or CustomRoles.Pickpocket => CustomRoles.Mayor, - CustomRoles.Arrogance or CustomRoles.Juggernaut or CustomRoles.Berserker => CustomRoles.Reverie, CustomRoles.Taskinator => CustomRoles.Benefactor, CustomRoles.EvilTracker => CustomRoles.TrackerTOHE, CustomRoles.AntiAdminer => CustomRoles.Telecommunication, CustomRoles.Pursuer => CustomRoles.Deceiver, + CustomRoles.CursedWolf or CustomRoles.Jinx => CustomRoles.Veteran, + CustomRoles.Swooper or CustomRoles.Wraith => CustomRoles.Chameleon, + CustomRoles.Vindicator or CustomRoles.Pickpocket => CustomRoles.Mayor, + CustomRoles.Arrogance or CustomRoles.Juggernaut or CustomRoles.Berserker => CustomRoles.Reverie, CustomRoles.Baker when Baker.CurrentBread() is 0 => CustomRoles.Overseer, CustomRoles.Baker when Baker.CurrentBread() is 1 => CustomRoles.Deputy, CustomRoles.Baker when Baker.CurrentBread() is 2 => CustomRoles.Medic, From b48b1a0cd3f3bdf5b3a661495dbfb29f94147440 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sun, 22 Sep 2024 10:56:05 +0800 Subject: [PATCH 583/778] Fix Mod Client Blue name not showing --- Patches/PlayerControlPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 93445baa84..066cc3ae59 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1234,7 +1234,7 @@ public static Task DoPostfix(PlayerControl __instance) __instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"{__instance.name}" : $"{ver.tag}\n{__instance?.name}"; else __instance.cosmetics.nameText.text = $"v{ver.version}\n{__instance?.name}"; } - if (Main.BAUPlayers.TryGetValue(__instance.Data, out var puid)) // Set name color for BAU users + else if (Main.BAUPlayers.TryGetValue(__instance.Data, out var puid)) // Set name color for BAU users { if (puid == __instance.Data.Puid) { From 5f305c198d10654dce9c324a224df4be19a0c8f4 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sun, 22 Sep 2024 10:56:22 +0800 Subject: [PATCH 584/778] Perform Code Format --- Patches/PlayerControlPatch.cs | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 066cc3ae59..a19583733e 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -58,10 +58,10 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC if (angel.Is(CustomRoles.Sheriff) && angel.Data.IsDead) { - Logger.Info("Blocked protection", "CheckProtect"); - return false; // What is this for? sheriff dosen't become guardian angel lmao + Logger.Info("Blocked protection", "CheckProtect"); + return false; // What is this for? sheriff dosen't become guardian angel lmao } - + return true; } @@ -154,7 +154,7 @@ public static bool CheckForInvalidMurdering(PlayerControl killer, PlayerControl // Is the target in a killable state? if (target.Data == null // Check if PlayerData is not null - // Check target status + // Check target status || target.inVent || target.inMovingPlat // Moving Platform on Airhip and Zipline on Fungle || target.MyPhysics.Animations.IsPlayingEnterVentAnimation() @@ -364,13 +364,13 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC { Logger.Info("Murder triggered in lobby, so murder canceled", "MurderPlayer Prefix"); return false; - } + } var isProtectedByClient = resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.IsProtected(); var isProtectedByHost = resultFlags.HasFlag(MurderResultFlags.FailedProtected); var isFailed = resultFlags.HasFlag(MurderResultFlags.FailedError); var isSucceeded = __state = !isProtectedByClient && !isProtectedByHost && !isFailed; - + if (isProtectedByClient) { Logger.Info("The kill will fail because it has DecisonByHost and target is protected", "MurderPlayer Prefix"); @@ -493,7 +493,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player // When target death, activate ability for others roles AfterPlayerDeathTasks(killer, target, false); - + // Check Kill Flash Utils.TargetDies(__instance, target); @@ -577,7 +577,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC if (shapeshifterRoleClass.CanDesyncShapeshift) { shapeshifter.RpcSpecificRejectShapeshift(target, shouldAnimate); - + if (resetCooldown) shapeshifter.RpcResetAbilityCooldown(); } @@ -637,7 +637,7 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont instance.Notify(Utils.ColorString(Utils.GetRoleColor(instance.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); logger.Info($"Cancel shapeshifting because {target.GetRealName()} is protected by the game"); return false; - } + } if (Pelican.IsEaten(instance.PlayerId)) { logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); @@ -646,7 +646,7 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) { - if(!instance.IsMushroomMixupActive() && !GameStates.IsMeeting) instance.GetRoleClass().UnShapeShiftButton(instance); + if (!instance.IsMushroomMixupActive() && !GameStates.IsMeeting) instance.GetRoleClass().UnShapeShiftButton(instance); instance.RpcResetAbilityCooldown(); // Just incase logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); return false; @@ -774,7 +774,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] Network foreach (var player in Main.PlayerStates.Values.ToArray()) { var playerRoleClass = player.RoleClass; - if (player == null || playerRoleClass == null) continue; + if (player == null || playerRoleClass == null) continue; if (playerRoleClass.OnCheckReportDeadBody(__instance, target, killer) == false) { @@ -814,9 +814,9 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] Network if (__instance.Is(CustomRoles.Unlucky) && (target?.Object == null || !target.Object.Is(CustomRoles.Bait))) { - if (Unlucky.SuicideRand(__instance, Unlucky.StateSuicide.ReportDeadBody)) + if (Unlucky.SuicideRand(__instance, Unlucky.StateSuicide.ReportDeadBody)) return false; - + } } @@ -829,7 +829,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] Network return false; } else Options.UsedButtonCount++; - + if (Options.SyncedButtonCount.GetFloat() == Options.UsedButtonCount) { Logger.Info("The maximum number of meeting buttons has been reached", "ReportDeadBody"); @@ -1200,7 +1200,7 @@ public static Task DoPostfix(PlayerControl __instance) { if (pc.Is(CustomRoles.Vampire) || pc.Is(CustomRoles.Warlock) || pc.Is(CustomRoles.Ninja)) Main.AllPlayerKillCooldown[pc.PlayerId] = Options.DefaultKillCooldown * 2; - + if (pc.Is(CustomRoles.Poisoner)) Main.AllPlayerKillCooldown[pc.PlayerId] = Poisoner.KillCooldown.GetFloat() * 2; } @@ -1249,11 +1249,11 @@ public static Task DoPostfix(PlayerControl __instance) RoleText.text = RoleTextData.Item1; RoleText.color = RoleTextData.Item2; if (Options.CurrentGameMode == CustomGameMode.FFA) RoleText.text = string.Empty; - + if (__instance.AmOwner || Options.CurrentGameMode == CustomGameMode.FFA) RoleText.enabled = true; else if (ExtendedPlayerControl.KnowRoleTarget(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; else RoleText.enabled = false; - + if (!PlayerControl.LocalPlayer.Data.IsDead && Overseer.IsRevealedPlayer(PlayerControl.LocalPlayer, __instance) && __instance.Is(CustomRoles.Trickster)) { RoleText.text = Overseer.GetRandomRole(PlayerControl.LocalPlayer.PlayerId); // random role for revealed trickster @@ -1300,7 +1300,7 @@ public static Task DoPostfix(PlayerControl __instance) RealName = RealName.ApplyNameColorData(seer, target, false); var seerRole = seer.GetCustomRole(); - + // Add protected player icon from ShieldPersonDiedFirst if (target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) { @@ -1607,7 +1607,7 @@ public static bool Prefix(PlayerControl __instance, uint idx) CustomRoleManager.OthersCompleteThisTask(player, playerTask); var playerSubRoles = player.GetCustomSubRoles(); - + // Add-Ons if (playerSubRoles.Any()) { @@ -1829,7 +1829,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol { try { - GhostRoleAssign.GhostAssignPatch(__instance); // Sets customrole ghost if succeed + GhostRoleAssign.GhostAssignPatch(__instance); // Sets customrole ghost if succeed } catch (Exception error) { From cec4d44ac14f0b9aba27b903a5518a642dbf95e0 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sun, 22 Sep 2024 11:37:53 +0800 Subject: [PATCH 585/778] Update DelayedNetworkData to InnerSloth officials --- Modules/DelayNetworkedData.cs | 88 ++++++++++++++++------------------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index a00cab54fa..bb9e2f9c3c 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -49,33 +49,26 @@ public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId return false; } + // InnerSloth vanilla officials send PlayerInfo in spilt reliable packets private static void DelaySpawnPlayerInfo(InnerNetClient __instance, int clientId) { List players = GameData.Instance.AllPlayers.ToArray().ToList(); - // We send 5 players at a time to prevent too huge packet - while (players.Count > 0) + foreach (var player in players) { - var batch = players.Take(5).ToList(); + if (player != null && player.ClientId != clientId && !player.Disconnected) + { + MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); + messageWriter.StartMessage(6); + messageWriter.Write(__instance.GameId); + messageWriter.WritePacked(clientId); - MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); - messageWriter.StartMessage(6); - messageWriter.Write(__instance.GameId); - messageWriter.WritePacked(clientId); + __instance.WriteSpawnMessage(player, player.OwnerId, player.SpawnFlags, messageWriter); + messageWriter.EndMessage(); - foreach (var player in batch) - { - if (messageWriter.Length > 1600) break; - if (player != null && player.ClientId != clientId && !player.Disconnected) - { - __instance.WriteSpawnMessage(player, player.OwnerId, player.SpawnFlags, messageWriter); - } - players.Remove(player); + __instance.SendOrDisconnect(messageWriter); + messageWriter.Recycle(); } - messageWriter.EndMessage(); - // Logger.Info($"send delayed network data to {clientId} , size is {messageWriter.Length}", "SendInitialDataPrefix"); - __instance.SendOrDisconnect(messageWriter); - messageWriter.Recycle(); } } @@ -135,56 +128,54 @@ public static bool SendAllStreamedObjectsPrefix(InnerNetClient __instance, ref b return false; } - private static byte timer = 0; [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))] [HarmonyPostfix] public static void FixedUpdatePostfix(InnerNetClient __instance) { - // Send a networked data pre 2 fixed update should be a good practice? + // Just send with None calls. Who cares? if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (!__instance.AmHost || __instance.Streams == null) return; - if (timer == 0) - { - timer = 1; - return; - } - - var player = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(x => x.IsDirty); - if (player != null) + var players = GameData.Instance.AllPlayers.ToArray().Where(x => x.IsDirty).ToList(); + if (players != null) { - timer = 0; - MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); - messageWriter.StartMessage(5); - messageWriter.Write(__instance.GameId); - messageWriter.StartMessage(1); - messageWriter.WritePacked(player.NetId); - try + foreach (var player in players) { - if (player.Serialize(messageWriter, false)) + MessageWriter messageWriter = MessageWriter.Get(SendOption.None); + messageWriter.StartMessage(5); + messageWriter.Write(__instance.GameId); + messageWriter.StartMessage(1); + messageWriter.WritePacked(player.NetId); + try { + if (player.Serialize(messageWriter, false)) + { + messageWriter.EndMessage(); + } + else + { + messageWriter.CancelMessage(); + player.ClearDirtyBits(); + continue; + } messageWriter.EndMessage(); + __instance.SendOrDisconnect(messageWriter); + messageWriter.Recycle(); } - else + catch (Exception ex) { + Logger.Exception(ex, "FixedUpdatePostfix"); messageWriter.CancelMessage(); player.ClearDirtyBits(); - return; + continue; } - messageWriter.EndMessage(); - __instance.SendOrDisconnect(messageWriter); - messageWriter.Recycle(); - } - catch (Exception ex) - { - Logger.Exception(ex, "FixedUpdatePostfix"); - messageWriter.CancelMessage(); - player.ClearDirtyBits(); } } } } +// Seems like there is no need to patch this if we are always sending with None calls +/* [HarmonyPatch(typeof(GameData), nameof(GameData.DirtyAllData))] internal class DirtyAllDataPatch { @@ -197,3 +188,4 @@ public static bool Prefix() return false; } } +*/ From c23b2d72c6da6f281f33be07f83d5d83b675f41b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 13:17:12 +0800 Subject: [PATCH 586/778] Lot of fix & Recode Executioner and Lawyer & support change role basic after target dead --- GameModes/FFAManager.cs | 11 -- Modules/Camouflague.cs | 2 +- Modules/ChatManager.cs | 8 +- Modules/CustomWinnerHolder.cs | 18 ++-- Modules/GuessManager.cs | 2 +- Modules/RPC.cs | 18 ++-- Modules/Utils.cs | 23 ++-- Patches/CheckGameEndPatch.cs | 14 ++- Patches/IntroPatch.cs | 47 ++++---- Patches/LadderPatch.cs | 4 +- Patches/MeetingHudPatch.cs | 4 - Patches/PlayerControlPatch.cs | 2 +- Patches/ShipStatusPatch.cs | 24 +---- Patches/VentSystemPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 18 ++-- Roles/AddOns/Common/Rebirth.cs | 8 +- Roles/AddOns/Common/Spurt.cs | 4 +- Roles/Crewmate/TimeMaster.cs | 8 +- Roles/Impostor/BountyHunter.cs | 2 +- Roles/Neutral/Executioner.cs | 189 +++++++++++++++------------------ Roles/Neutral/Jester.cs | 13 ++- Roles/Neutral/Lawyer.cs | 163 +++++++++++----------------- 22 files changed, 263 insertions(+), 321 deletions(-) diff --git a/GameModes/FFAManager.cs b/GameModes/FFAManager.cs index 2decb6f8d5..a3959a58e4 100644 --- a/GameModes/FFAManager.cs +++ b/GameModes/FFAManager.cs @@ -98,17 +98,6 @@ public static void Init() FFAVentDuration = []; FFAEnterVentTime = []; } - - _ = new LateTask(()=> - { - RoundTime = FFA_GameTime.GetInt() + 8; - var now = Utils.GetTimeStamp() + 8; - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - KBScore[pc.PlayerId] = 0; - if (FFA_DisableVentingWhenKCDIsUp.GetBool()) FFALastKill[pc.PlayerId] = now; - } - }, 18f, "Add data after game start"); } public static void SetData() { diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index be77fb22aa..0106ba3a53 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -68,7 +68,7 @@ public static void Init() { IsCamouflage = false; PlayerSkins.Clear(); - ResetSkinAfterDeathPlayers = []; + ResetSkinAfterDeathPlayers.Clear(); IsActive = Options.CommsCamouflage.GetBool() && !(Options.DisableOnSomeMaps.GetBool() && ( diff --git a/Modules/ChatManager.cs b/Modules/ChatManager.cs index 9a1560c6b7..a4a7527d7f 100644 --- a/Modules/ChatManager.cs +++ b/Modules/ChatManager.cs @@ -10,14 +10,14 @@ namespace TOHE.Modules.ChatManager public class ChatManager { public static bool cancel = false; - private static List> chatHistory = []; - private static Dictionary LastSystemChatMsg = []; + private static readonly List> chatHistory = []; + private static readonly Dictionary LastSystemChatMsg = []; private const int maxHistorySize = 20; public static List ChatSentBySystem = []; public static void ResetHistory() { - chatHistory = []; - LastSystemChatMsg = []; + chatHistory.Clear(); + LastSystemChatMsg.Clear(); } public static void ClearLastSysMsg() { diff --git a/Modules/CustomWinnerHolder.cs b/Modules/CustomWinnerHolder.cs index 2ce81e9f5d..e594da0bd3 100644 --- a/Modules/CustomWinnerHolder.cs +++ b/Modules/CustomWinnerHolder.cs @@ -10,20 +10,20 @@ public static class CustomWinnerHolder public static CustomWinner WinnerTeam; // 追加勝利するプレイヤーのチームが格納されます。 // リザルトの表示に使用されます。 - public static HashSet AdditionalWinnerTeams; + public static HashSet AdditionalWinnerTeams = []; // 勝者の役職が格納され、この変数に格納されている役職のプレイヤーは全員勝利となります。 // チームとなるニュートラルの処理に最適です。 - public static HashSet WinnerRoles; + public static HashSet WinnerRoles = []; // 勝者のPlayerIDが格納され、このIDを持つプレイヤーは全員勝利します。 // 単独勝利するニュートラルの処理に最適です。 - public static HashSet WinnerIds; + public static HashSet WinnerIds = []; public static void Reset() { WinnerTeam = CustomWinner.Default; - AdditionalWinnerTeams = []; - WinnerRoles = []; - WinnerIds = []; + AdditionalWinnerTeams.Clear(); + WinnerRoles.Clear(); + WinnerIds.Clear(); } public static void ClearWinners() { @@ -101,17 +101,17 @@ public static void ReadFrom(MessageReader reader) { WinnerTeam = (CustomWinner)reader.ReadPackedInt32(); - AdditionalWinnerTeams = []; + AdditionalWinnerTeams.Clear(); int AdditionalWinnerTeamsCount = reader.ReadPackedInt32(); for (int i = 0; i < AdditionalWinnerTeamsCount; i++) AdditionalWinnerTeams.Add((AdditionalWinners)reader.ReadPackedInt32()); - WinnerRoles = []; + WinnerRoles.Clear(); int WinnerRolesCount = reader.ReadPackedInt32(); for (int i = 0; i < WinnerRolesCount; i++) WinnerRoles.Add((CustomRoles)reader.ReadPackedInt32()); - WinnerIds = []; + WinnerIds.Clear(); int WinnerIdsCount = reader.ReadPackedInt32(); for (int i = 0; i < WinnerIdsCount; i++) WinnerIds.Add(reader.ReadByte()); diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index ade70a8dc3..5e1018a98f 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -605,7 +605,6 @@ public static void Postfix(MeetingHud __instance) } public static void CreateGuesserButton(MeetingHud __instance) { - foreach (var pva in __instance.playerStates.ToArray()) { var pc = Utils.GetPlayerById(pva.TargetPlayerId); @@ -952,6 +951,7 @@ or CustomRoles.LastImpostor or CustomRoles.Mare or CustomRoles.Cyber or CustomRoles.Sloth + or CustomRoles.Apocalypse || (role.IsTNA() && !Options.TransformedNeutralApocalypseCanBeGuessed.GetBool())) continue; CreateRole(role); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index c267be803b..49ad2ed55e 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -51,6 +51,7 @@ enum CustomRPC : byte // 187/255 USED SyncShieldPersonDiedFirst, RemoveSubRole, SyncGeneralOptions, + SyncSpeedPlayer, Arrow, //Roles @@ -66,15 +67,13 @@ enum CustomRPC : byte // 187/255 USED DoCurse, SniperSync, SetLoversPlayers, - SetExecutionerTarget, - RemoveExecutionerTarget, SendFireworkerState, SetCurrentDousingTarget, SetEvilTrackerTarget, SetDrawPlayer, SetCrewpostorTasksDone, - SetCurrentDrawTarget = 151, // BetterCheck used 150 - RpcPassBomb, + SetCurrentDrawTarget, + RpcPassBomb = 151, // BetterCheck used 150 SyncRomanticTarget, SyncVengefulRomanticTarget, SetJailerTarget, @@ -473,12 +472,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c for (int i = 0; i < count; i++) Main.LoversPlayers.Add(Utils.GetPlayerById(reader.ReadByte())); break; - case CustomRPC.SetExecutionerTarget: - Executioner.ReceiveRPC(reader, SetTarget: true); - break; - case CustomRPC.RemoveExecutionerTarget: - Executioner.ReceiveRPC(reader, SetTarget: false); - break; case CustomRPC.BetterCheck: // Better Among Us RPC { var SetBetterUser = reader.ReadBoolean(); // Used to set player as better user, boolean is used for a future for BAU later on. @@ -585,6 +578,11 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c Main.AllPlayerKillCooldown[paciefID] = Killcd; Main.AllPlayerSpeed[paciefID] = speed; break; + case CustomRPC.SyncSpeedPlayer: + byte readerPlayerId = reader.ReadByte(); + float newSpeed = reader.ReadSingle(); + Main.AllPlayerSpeed[readerPlayerId] = newSpeed; + break; case CustomRPC.SyncPlayerSetting: byte playerid = reader.ReadByte(); CustomRoles rl = (CustomRoles)reader.ReadPackedInt32(); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index a70df6bbf5..f7c1f1bd04 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -44,7 +44,7 @@ public static void ErrorEnd(string text) _ = new LateTask(() => { Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame")); - }, 3f, "Anti-Black Msg SendInGame Error During Loading"); + }, 6f, "Anti-Black Msg SendInGame Error During Loading"); if (GameStates.IsShip || !GameStates.IsLobby || GameStates.IsCoStartGame) { @@ -53,7 +53,7 @@ public static void ErrorEnd(string text) CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); GameManager.Instance.LogicFlow.CheckEndCriteria(); RPC.ForceEndGame(CustomWinner.Error); - }, 5.5f, "Anti-Black End Game As Critical Error"); + }, 9f, "Anti-Black End Game As Critical Error"); } else if (GameStartManager.Instance != null) { @@ -80,14 +80,14 @@ public static void ErrorEnd(string text) _ = new LateTask(() => { Logger.SendInGame(GetString("AntiBlackOutRequestHostToForceEnd")); - }, 3f, "Anti-Black Msg SendInGame Non-Host Modded Has Error During Loading"); + }, 6f, "Anti-Black Msg SendInGame Non-Host Modded Has Error During Loading"); } else { _ = new LateTask(() => { Logger.SendInGame(GetString("AntiBlackOutHostRejectForceEnd")); - }, 3f, "Anti-Black Msg SendInGame Host Reject Force End"); + }, 6f, "Anti-Black Msg SendInGame Host Reject Force End"); _ = new LateTask(() => { @@ -96,7 +96,7 @@ public static void ErrorEnd(string text) AmongUsClient.Instance.ExitGame(DisconnectReasons.Custom); Logger.Fatal($"Error: {text} - Disconnected from the game due critical error", "Anti-black"); } - }, 8f, "Anti-Black Exit Game Due Critical Error"); + }, 11f, "Anti-Black Exit Game Due Critical Error"); } } } @@ -406,7 +406,7 @@ public static void SyncGeneralOptions(this PlayerControl player) { if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions, SendOption.Reliable); writer.Write(player.PlayerId); writer.WritePacked((int)player.GetCustomRole()); writer.Write(Main.PlayerStates[player.PlayerId].IsDead); @@ -416,6 +416,15 @@ public static void SyncGeneralOptions(this PlayerControl player) writer.Write(Main.AllPlayerSpeed[player.PlayerId]); AmongUsClient.Instance.FinishRpcImmediately(writer); } + public static void SyncSpeed(this PlayerControl player) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncSpeedPlayer, SendOption.Reliable); + writer.Write(player.PlayerId); + writer.Write(Main.AllPlayerSpeed[player.PlayerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } public static float GetDistance(Vector2 pos1, Vector2 pos2) => Vector2.Distance(pos1, pos2); public static Color GetRoleColor(CustomRoles role) { @@ -1054,7 +1063,7 @@ public static void ThrowException(Exception ex, [CallerFilePath] string fileName StackFrame firstFrame = stFrames.FirstOrDefault(); var sb = new StringBuilder(); - sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); + sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {firstFrame.GetFileLineNumber()}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); bool skip = true; foreach (StackFrame sf in stFrames) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 8b3b02072a..084faa13f6 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -351,10 +351,16 @@ public static bool Prefix() WinnerIds.Add(Romantic.BetPlayer[pc.PlayerId]); AdditionalWinnerTeams.Add(AdditionalWinners.VengefulRomantic); break; - case CustomRoles.Lawyer when Lawyer.Target.TryGetValue(pc.PlayerId, out var lawyertarget) - && (WinnerIds.Contains(lawyertarget) || (Main.PlayerStates.TryGetValue(lawyertarget, out var lawyerTargetPS) && WinnerRoles.Contains(lawyerTargetPS.MainRole))): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); + case CustomRoles.Lawyer: + if (pc.GetRoleClass() is Lawyer lawerClass) + { + var lawyertarget = lawerClass.GetTargetId(); + if (WinnerIds.Contains(lawyertarget) + || (Main.PlayerStates.TryGetValue(lawyertarget, out var lawyerTargetPS) && WinnerRoles.Contains(lawyerTargetPS.MainRole))) + WinnerIds.Add(pc.PlayerId); + + AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); + } break; case CustomRoles.Follower when Follower.BetPlayer.TryGetValue(pc.PlayerId, out var followerTarget) && (WinnerIds.Contains(followerTarget) || (Main.PlayerStates.TryGetValue(followerTarget, out var followerTargetPS) && WinnerRoles.Contains(followerTargetPS.MainRole))): diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d1f40105fd..4750c7631b 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -42,8 +42,6 @@ public static void Prefix() { if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) { - ShipStatusBeginPatch.RolesIsAssigned = true; - // Assign tasks after assign all roles, as it should be ShipStatus.Instance.Begin(); @@ -63,6 +61,17 @@ public static void Prefix() }, 4f, "Assing Task For All"); } } +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] +class CoBeginPatch +{ + public static void Prefix() + { + GameStates.InGame = true; + RPC.RpcVersionCheck(); + + FFAManager.SetData(); + } +} [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] class SetUpRoleTextPatch { @@ -79,6 +88,17 @@ public static void Postfix(IntroCutscene __instance) Utils.DoNotifyRoles(NoCache: true); } + if (GameStates.IsNormalGame) + { + foreach (var player in Main.AllPlayerControls) + { + Main.PlayerStates[player.PlayerId].InitTask(player); + } + + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + } + var mapName = Utils.GetActiveMapName(); Logger.Msg($"{mapName}", "Map"); if (AmongUsClient.Instance.AmHost && RandomSpawn.IsRandomSpawn() && RandomSpawn.CanSpawnInFirstRound()) @@ -262,17 +282,6 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() Logger.Info(sb.ToString(), "GameInfo", multiLine: true); } } -[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] -class CoBeginPatch -{ - public static void Prefix() - { - GameStates.InGame = true; - RPC.RpcVersionCheck(); - - FFAManager.SetData(); - } -} [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] class BeginCrewmatePatch { @@ -309,9 +318,10 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections { var exeTeam = new Il2CppSystem.Collections.Generic.List(); exeTeam.Add(PlayerControl.LocalPlayer); - foreach (var execution in Executioner.Target.Values) + foreach (var executionId in Executioner.TargetList) { - PlayerControl executing = execution.GetPlayer(); + PlayerControl executing = executionId.GetPlayer(); + if (executing == null) continue; exeTeam.Add(executing); } teamToDisplay = exeTeam; @@ -320,10 +330,11 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections { var lawyerTeam = new Il2CppSystem.Collections.Generic.List(); lawyerTeam.Add(PlayerControl.LocalPlayer); - foreach (var help in Lawyer.Target.Values) + foreach (var lawyerTargetId in Lawyer.TargetList) { - PlayerControl helping = help.GetPlayer(); - lawyerTeam.Add(helping); + PlayerControl lawyerTarget = lawyerTargetId.GetPlayer(); + if (lawyerTarget == null) continue; + lawyerTeam.Add(lawyerTarget); } teamToDisplay = lawyerTeam; } diff --git a/Patches/LadderPatch.cs b/Patches/LadderPatch.cs index a05fa2ce35..b69f148814 100644 --- a/Patches/LadderPatch.cs +++ b/Patches/LadderPatch.cs @@ -4,11 +4,11 @@ namespace TOHE; public class FallFromLadder { - public static Dictionary TargetLadderData; + public static Dictionary TargetLadderData = []; private static int Chance => (Options.LadderDeathChance as StringOptionItem).GetChance(); public static void Reset() { - TargetLadderData = []; + TargetLadderData.Clear(); } public static void OnClimbLadder(PlayerPhysics player, Ladder source) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 966fc5fd12..410d98cf8a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1236,20 +1236,16 @@ public static void Postfix(MeetingHud __instance) bufferTime = 10; var myRole = PlayerControl.LocalPlayer.GetCustomRole(); - //若某玩家死亡则修复会议该玩家状态 __instance.playerStates.Where(x => (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead) && !x.AmDead).Do(x => x.SetDead(x.DidReport, true)); - //若玩家死亡则销毁技能按钮 if (myRole is CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Doomsayer or CustomRoles.Judge or CustomRoles.Councillor or CustomRoles.Guesser or CustomRoles.Swapper && !PlayerControl.LocalPlayer.IsAlive()) ClearShootButton(__instance, true); - //若黑手党死亡则创建技能按钮 if (myRole is CustomRoles.Nemesis && !PlayerControl.LocalPlayer.IsAlive() && GameObject.Find("ShootButton") == null) Nemesis.CreateJudgeButton(__instance); if (myRole is CustomRoles.Retributionist && !PlayerControl.LocalPlayer.IsAlive() && GameObject.Find("ShootButton") == null) Retributionist.CreateJudgeButton(__instance); - //销毁死亡玩家身上的技能按钮 ClearShootButton(__instance); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 93445baa84..a23528fdec 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -712,7 +712,7 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] class ReportDeadBodyPatch { - public static Dictionary CanReport; + public static Dictionary CanReport = []; public static Dictionary> WaitReport = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] NetworkedPlayerInfo target) { diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index fc0147c87e..c4704bdc53 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -218,7 +218,7 @@ public static void Postfix() [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.StartMeeting))] class StartMeetingPatch { - public static void Prefix(ShipStatus __instance, PlayerControl reporter, NetworkedPlayerInfo target) + public static void Prefix([HarmonyArgument(1)] NetworkedPlayerInfo target) { if (GameStates.IsHideNSeek) return; @@ -237,31 +237,9 @@ public static void Postfix() [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] class ShipStatusBeginPatch { - //Prevent code from running twice as it gets activated later in LateTask - public static bool RolesIsAssigned = false; - public static bool Prefix() - { - return RolesIsAssigned; - } public static void Postfix() { Logger.CurrentMethod(); - - _ = new LateTask(() => - { - if (RolesIsAssigned && GameStates.IsNormalGame) - { - foreach (var player in Main.AllPlayerControls) - { - Main.PlayerStates[player.PlayerId].InitTask(player); - } - - GameData.Instance.RecomputeTaskCounts(); - TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - - Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); - } - }, 1f, "Assign Custom Tasks"); } } diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index b38aacdc03..4f675a1f00 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -31,7 +31,7 @@ public static bool Prefix(VentilationSystem __instance, [HarmonyArgument(0)] byt [HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.Deteriorate))] static class VentSystemDeterioratePatch { - public static Dictionary LastClosestVent; + public static Dictionary LastClosestVent = []; public static void Postfix(VentilationSystem __instance) { if (!AmongUsClient.Instance.AmHost) return; diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index b17be97972..97187ea8a5 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -90,12 +90,11 @@ public static void Postfix(AmongUsClient __instance) GameEndCheckerForNormal.ShowAllRolesWhenGameEnd = false; GameStartManagerPatch.GameStartManagerUpdatePatch.AlredyBegin = false; - VentSystemDeterioratePatch.LastClosestVent = []; + VentSystemDeterioratePatch.LastClosestVent.Clear(); ChatManager.ResetHistory(); - ReportDeadBodyPatch.CanReport = []; + ReportDeadBodyPatch.CanReport.Clear(); Options.UsedButtonCount = 0; - ShipStatusBeginPatch.RolesIsAssigned = false; Main.RealOptionsData = new OptionBackupData(GameOptionsManager.Instance.CurrentGameOptions); @@ -140,16 +139,16 @@ public static void Postfix(AmongUsClient __instance) } } - foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator()) + foreach (var target in Main.AllPlayerControls) { - foreach (var seer in PlayerControl.AllPlayerControls.GetFastEnumerator()) + foreach (var seer in Main.AllPlayerControls) { var pair = (target.PlayerId, seer.PlayerId); Main.LastNotifyNames[pair] = target.name; } } - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + foreach (var pc in Main.AllPlayerControls) { var outfit = pc.Data.DefaultOutfit; var colorId = pc.Data.DefaultOutfit.ColorId; @@ -178,7 +177,7 @@ public static void Postfix(AmongUsClient __instance) NormalOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set(currentName, pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.NamePlateId), }; - Main.PlayerColors[pc.PlayerId] = Palette.PlayerColors[colorId]; + Main.PlayerColors[pc.PlayerId] = colorId != 1 ? Palette.PlayerColors[colorId] : new Color32(255, 255, 255, 255); if (GameStates.IsNormalGame) Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); @@ -207,12 +206,12 @@ public static void Postfix(AmongUsClient __instance) // Initialize all roles foreach (var role in EnumHelper.GetAllValues().Where(role => role < CustomRoles.NotAssigned).ToArray()) { - var RoleClass = CustomRoleManager.GetStaticRoleClass(role); + var RoleClass = role.GetStaticRoleClass(); RoleClass?.OnInit(); } // Initialize all add-ons - foreach (var addOn in CustomRoleManager.AddonClasses.Values.ToArray()) + foreach (var addOn in CustomRoleManager.AddonClasses.Values) { addOn?.Init(); } @@ -275,7 +274,6 @@ public static bool CoStartGameHost_Prefix(AmongUsClient __instance, ref Il2CppSy { if (GameStates.IsHideNSeek) { - ShipStatusBeginPatch.RolesIsAssigned = true; return true; } diff --git a/Roles/AddOns/Common/Rebirth.cs b/Roles/AddOns/Common/Rebirth.cs index efb8ab9629..30fd896132 100644 --- a/Roles/AddOns/Common/Rebirth.cs +++ b/Roles/AddOns/Common/Rebirth.cs @@ -59,8 +59,8 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled list = [..VotedCount[pc.PlayerId].Select(x => GetPlayerById(x))]; } - var ViablePlayer = list.Where(x => x != pc).Shuffle() - .FirstOrDefault(x => x != null && !x.IsHost() && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && + var ViablePlayer = list.Where(x => x != null && x.PlayerId != pc.PlayerId).Shuffle() + .FirstOrDefault(x => !x.IsHost() && AntiBlackout.ExilePlayerId != x.PlayerId && !x.IsAnySubRole(x => x.IsConverted()) && !x.Is(CustomRoles.Admired) && !x.Is(CustomRoles.Knighted) && /*All converters */ !x.Is(CustomRoles.Cultist) && !x.Is(CustomRoles.Infectious) && !x.Is(CustomRoles.Virus) && !x.Is(CustomRoles.Jackal) && !x.Is(CustomRoles.Admirer) && !x.Is(CustomRoles.Lovers) && !x.Is(CustomRoles.Romantic) && !x.Is(CustomRoles.Doppelganger) && !x.GetCustomRole().IsImpostor()); @@ -71,10 +71,10 @@ public static bool SwapSkins(PlayerControl pc, out NetworkedPlayerInfo NewExiled return false; } Rebirths[pc.PlayerId]--; - pc.ResetPlayerOutfit(Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, ViablePlayer.Data.PlayerLevel, true); + pc.SetNewOutfit(Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, true, true, ViablePlayer.Data.PlayerLevel); Main.OvverideOutfit[pc.PlayerId] = (Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit, Main.PlayerStates[ViablePlayer.PlayerId].NormalOutfit.PlayerName); - ViablePlayer.ResetPlayerOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit, pc.Data.PlayerLevel, true); + ViablePlayer.SetNewOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit, true, true, pc.Data.PlayerLevel); Main.OvverideOutfit[ViablePlayer.PlayerId] = (Main.PlayerStates[pc.PlayerId].NormalOutfit, Main.PlayerStates[pc.PlayerId].NormalOutfit.PlayerName); NewExiledPlayer = ViablePlayer.Data; diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 371a62e213..dc10b5169c 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -107,17 +107,19 @@ public void OnFixedUpdate(PlayerControl player) { Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); LastUpdate[player.PlayerId] = now; - player.SyncGeneralOptions(); + player.SyncSpeed(); } } if (!moving) { Main.AllPlayerSpeed[player.PlayerId] += Mathf.Clamp(ChargeBy, 0f, MaxSpeed.GetFloat() - Main.AllPlayerSpeed[player.PlayerId]); + player.SyncSpeed(); return; } Main.AllPlayerSpeed[player.PlayerId] -= Mathf.Clamp(Decreaseby, 0f, Main.AllPlayerSpeed[player.PlayerId] - MinSpeed.GetFloat()); + player.SyncSpeed(); player.MarkDirtySettings(); } } diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs index 2d892d1e73..3e361840ac 100644 --- a/Roles/Crewmate/TimeMaster.cs +++ b/Roles/Crewmate/TimeMaster.cs @@ -58,8 +58,10 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { if (player.IsAlive()) + { AbilityLimit += TimeMasterAbilityUseGainWithEachTaskCompleted.GetFloat(); - + SendSkillRPC(); + } return true; } public override void SetAbilityButtonText(HudManager hud, byte id) @@ -102,6 +104,8 @@ public override void OnEnterVent(PlayerControl pc, Vent AirConditioning) if (AbilityLimit >= 1) { AbilityLimit -= 1; + SendSkillRPC(); + TimeMasterInProtect.Remove(pc.PlayerId); TimeMasterInProtect.Add(pc.PlayerId, GetTimeStamp()); @@ -128,7 +132,7 @@ public override void OnEnterVent(PlayerControl pc, Vent AirConditioning) } else { - TimeMasterBackTrack.Add(player.PlayerId, player.GetCustomPosition()); + TimeMasterBackTrack[player.PlayerId] = player.GetCustomPosition(); } } } diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index d727c3b69f..885646e095 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -169,7 +169,7 @@ private static bool PotentialTarget(PlayerControl player, PlayerControl target) && ((Romantic.BetPlayer.TryGetValue(target.PlayerId, out byte romanticPartner) && romanticPartner == player.PlayerId))) return false; if (target.Is(CustomRoles.Lawyer) - && (Lawyer.Target.TryGetValue(target.PlayerId, out byte lawyerTarget) && lawyerTarget == player.PlayerId) && Lawyer.TargetKnowLawyer) return false; + && Lawyer.TargetList.Contains(player.PlayerId) && Lawyer.TargetKnowLawyer) return false; if (player.Is(CustomRoles.Charmed) && (target.Is(CustomRoles.Cultist) || (target.Is(CustomRoles.Charmed) && Cultist.TargetKnowOtherTargets))) return false; diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 637c9435fc..28ca39c7e5 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -1,5 +1,5 @@ using Hazel; -using System; +using InnerNet; using TOHE.Roles.Core; using static TOHE.Options; @@ -25,8 +25,9 @@ internal class Executioner : RoleBase private static OptionItem KnowTargetRole; private static OptionItem ChangeRolesAfterTargetKilled; - public static readonly Dictionary Target = []; - + public static HashSet TargetList = []; + private byte TargetId; + private enum ChangeRolesSelectList { Role_Crewmate, @@ -65,13 +66,15 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - Target.Clear(); + TargetList.Remove(TargetId); + TargetId = byte.MaxValue; } public override void Add(byte playerId) { playerIdList.Add(playerId); - - if (AmongUsClient.Instance.AmHost) + + var executioner = _Player; + if (AmongUsClient.Instance.AmHost && executioner.IsAlive()) { CustomRoleManager.CheckDeadBodyOthers.Add(OnOthersDead); @@ -87,160 +90,142 @@ public override void Add(byte playerId) else if (!CanTargetNeutralEvil.GetBool() && target.GetCustomRole().IsNE()) continue; else if (!CanTargetNeutralChaos.GetBool() && target.GetCustomRole().IsNC()) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; - if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; + if (executioner.Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; targetList.Add(target); } if (targetList.Any()) { - var SelectedTarget = targetList.RandomElement(); - Target.Add(playerId, SelectedTarget.PlayerId); - SendRPC(playerId, SelectedTarget.PlayerId, "SetTarget"); - Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole()}:{SelectedTarget.GetNameWithRole()}", "Executioner"); + var selectedTarget = targetList.RandomElement(); + TargetId = selectedTarget.PlayerId; + TargetList.Add(selectedTarget.PlayerId); + + SendRPC(SetTarget: true); + Logger.Info($"{executioner?.GetNameWithRole()}:{selectedTarget.GetNameWithRole()}", "Executioner"); } else { - Logger.Warn(" Warning! No suitableable target was found for executioner, switching role","Executioner.Add"); - ChangeRole(Utils.GetPlayerById(playerId)); + Logger.Warn("Warning! No suitableable target was found for executioner, switching role","Executioner.Add"); + ChangeRole(); } } } - public static void SendRPC(byte executionerId, byte targetId = 0x73, string Progress = "") + public override void Remove(byte playerId) { - MessageWriter writer; - switch (Progress) + if (AmongUsClient.Instance.AmHost) { - case "SetTarget": - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetExecutionerTarget, SendOption.Reliable); - writer.Write(executionerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - break; - case "": - if (!AmongUsClient.Instance.AmHost) return; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveExecutionerTarget, SendOption.Reliable); - writer.Write(executionerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - break; - case "WinCheck": - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) break; - if (!CustomWinnerHolder.CheckForConvertedWinner(executionerId)) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Executioner); - CustomWinnerHolder.WinnerIds.Add(executionerId); - } - break; + SendRPC(SetTarget: false); } + playerIdList.Remove(playerId); + TargetList.Remove(TargetId); + TargetId = byte.MaxValue; + } + private void SendRPC(bool SetTarget = false) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(_Player); + writer.Write(SetTarget); + writer.Write(TargetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static void ReceiveRPC(MessageReader reader, bool SetTarget) + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { + bool SetTarget = reader.ReadBoolean(); + byte targetId = reader.ReadByte(); if (SetTarget) { - byte ExecutionerId = reader.ReadByte(); - byte TargetId = reader.ReadByte(); - Target[ExecutionerId] = TargetId; + TargetId = targetId; + TargetList.Add(targetId); } else - Target.Remove(reader.ReadByte()); + { + TargetId = byte.MaxValue; + TargetList.Remove(targetId); + } } + + public bool IsTarget(byte playerId) => TargetId == playerId; + public override bool HasTasks(NetworkedPlayerInfo player, CustomRoles role, bool ForRecompute) => !(ChangeRolesAfterTargetKilled.GetValue() is 6 or 7) && !ForRecompute; - public static void ChangeRoleByTarget(PlayerControl target) + private void ChangeRole() { - byte ExecutionerId = 0x73; - Target.Do(x => - { - if (x.Value == target.PlayerId) - ExecutionerId = x.Key; - }); - - var Executioner = Utils.GetPlayerById(ExecutionerId); - Executioner.RpcSetCustomRole(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]); + if (!_Player.IsAlive()) return; - playerIdList.Remove(ExecutionerId); - Target.Remove(ExecutionerId); - SendRPC(ExecutionerId); + var executioner = _Player; + var executionerId = _Player.PlayerId; - Executioner.GetRoleClass().OnAdd(ExecutionerId); - Utils.NotifyRoles(SpecifySeer: Executioner); - } - public static void ChangeRole(PlayerControl executioner) - { - executioner.RpcSetCustomRole(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]); + SendRPC(SetTarget: false); + var valueChanger = ChangeRolesAfterTargetKilled.GetValue(); + var newCustomRole = CRoleChangeRoles[valueChanger]; - playerIdList.Remove(executioner.PlayerId); - Target.Remove(executioner.PlayerId); - SendRPC(executioner.PlayerId); + executioner.GetRoleClass()?.OnRemove(executionerId); + executioner.RpcSetCustomRole(newCustomRole); + executioner.GetRoleClass().OnAdd(executionerId); - var text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), Translator.GetString("")); - text = string.Format(text, Utils.ColorString(Utils.GetRoleColor(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]), Translator.GetString(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()].ToString()))); - executioner.Notify(text); - - try { executioner.GetRoleClass().OnAdd(executioner.PlayerId); } - catch (Exception err) - { Logger.Warn($"Error after attempting to RoleCLass.Add({executioner.GetCustomRole().ToString().RemoveHtmlTags() + ", " + executioner.GetRealName()}.PlayerId): {err}", "Executioner.ChangeRole.Add"); } - } + executioner.RpcChangeRoleBasis(newCustomRole); - public static bool CheckTarget(byte targetId) => Target.ContainsValue(targetId); - public static bool IsTarget(byte executionerId, byte targetId) => Target.TryGetValue(executionerId, out var exeTargetId) && exeTargetId == targetId; + Utils.NotifyRoles(SpecifySeer: executioner); + } public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { - if (Target.ContainsKey(target.PlayerId)) + if (_Player != null && _Player.PlayerId == target.PlayerId) { - Target.Remove(target.PlayerId); - SendRPC(target.PlayerId); + SendRPC(SetTarget: false); + TargetList.Remove(TargetId); + TargetId = byte.MaxValue; } } private void OnOthersDead(PlayerControl killer, PlayerControl target, bool inMeeting) { - if (CheckTarget(target.PlayerId)) - ChangeRoleByTarget(target); + if (IsTarget(target.PlayerId)) + ChangeRole(); } public override bool KnowRoleTarget(PlayerControl player, PlayerControl target) { if (!KnowTargetRole.GetBool()) return false; - return player.Is(CustomRoles.Executioner) && Target.TryGetValue(player.PlayerId, out var tar) && tar == target.PlayerId; + return player.Is(CustomRoles.Executioner) && IsTarget(target.PlayerId); } - - public override string GetMark(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (target == null || !seer.IsAlive()) return string.Empty; - - var GetValue = Target.TryGetValue(seer.PlayerId, out var targetId); - return GetValue && targetId == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), "♦") : string.Empty; + if ((!seer.IsAlive() || seer.Is(CustomRoles.Executioner)) && IsTarget(target.PlayerId)) + { + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), "♦"); + } + return string.Empty; } public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) { - foreach (var kvp in Target.Where(x => x.Value == exiled.PlayerId)) - { - var executioner = Utils.GetPlayerById(kvp.Key); - if (executioner == null || !executioner.IsAlive() || executioner.Data.Disconnected) continue; + if (!_Player.IsAlive() || !IsTarget(exiled.PlayerId)) return; - if (isMeetingHud) - { - name = string.Format(Translator.GetString("ExiledExeTarget"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); - } - else - { - ExeWin(kvp.Key, DecidedWinner); - } - DecidedWinner = true; + if (isMeetingHud) + { + name = string.Format(Translator.GetString("ExiledExeTarget"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + } + else + { + ExeWin(_Player.PlayerId, DecidedWinner); } + DecidedWinner = true; } - private static void ExeWin(byte playerId, bool DecidedWinner) + private static void ExeWin(byte executionerId, bool DecidedWinner) { - if (!DecidedWinner) + if (!DecidedWinner && CustomWinnerHolder.WinnerTeam == CustomWinner.Default) { - SendRPC(playerId, Progress: "WinCheck"); + if (!CustomWinnerHolder.CheckForConvertedWinner(executionerId)) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Executioner); + CustomWinnerHolder.WinnerIds.Add(executionerId); + } } else { CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Executioner); - CustomWinnerHolder.WinnerIds.Add(playerId); + CustomWinnerHolder.WinnerIds.Add(executionerId); } } } \ No newline at end of file diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 2019f897c0..843159d86e 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using TOHE.Roles.Core; using static TOHE.Options; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Neutral; @@ -110,12 +111,16 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn } // Check exile target Executioner - foreach (var executioner in Executioner.playerIdList) + foreach (var executionerId in Executioner.playerIdList) { - if (Executioner.IsTarget(executioner, exiled.PlayerId)) + var executioner = executionerId.GetPlayer(); + if (executioner.IsAlive() && executioner.GetRoleClass() is Executioner executionerClass) { - CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Executioner); - CustomWinnerHolder.WinnerIds.Add(executioner); + if (executionerClass.IsTarget(exiled.PlayerId)) + { + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Executioner); + CustomWinnerHolder.WinnerIds.Add(executionerId); + } } } DecidedWinner = true; diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index 7b36657618..71b11acba8 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -26,7 +26,9 @@ internal class Lawyer : RoleBase private static OptionItem KnowTargetRole; private static OptionItem TargetKnowsLawyer; - public static readonly Dictionary Target = []; + public static HashSet TargetList = []; + private byte TargetId; + private enum ChangeRolesSelectList { Role_Crewmate, @@ -65,11 +67,13 @@ public override void SetupCustomOption() } public override void Init() { - Target.Clear(); + TargetId = byte.MaxValue; + TargetList.Clear(); } public override void Add(byte playerId) { - if (AmongUsClient.Instance.AmHost) + var lawyer = _Player; + if (AmongUsClient.Instance.AmHost && lawyer.IsAlive()) { CustomRoleManager.CheckDeadBodyOthers.Add(OthersAfterPlayerDeathTask); @@ -85,165 +89,122 @@ public override void Add(byte playerId) else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester) && !target.IsNeutralApocalypse()) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; - if (Utils.GetPlayerById(playerId).Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; + if (lawyer.Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; targetList.Add(target); } - if (!targetList.Any()) + if (targetList.Any()) + { + var selectedTarget = targetList.RandomElement(); + TargetId = selectedTarget.PlayerId; + TargetList.Add(selectedTarget.PlayerId); + + SendRPC(SetTarget: true); + Logger.Info($"{lawyer?.GetNameWithRole()}:{selectedTarget.GetNameWithRole()}", "Lawyer"); + } + else { Logger.Info($"Wow, not target for lawyer to select! Changing lawyer role to other", "Lawyer"); + // Unable to find a target? Try to turn to changerole or opportunist var changedRole = ShouldChangeRoleAfterTargetDeath.GetBool() ? CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()] : CustomRoles.Opportunist; - var lawyer = Utils.GetPlayerById(playerId); - if (lawyer.IsAlive()) - { - lawyer.GetRoleClass()?.OnRemove(playerId); - lawyer.RpcSetCustomRole(changedRole); - lawyer.GetRoleClass()?.OnAdd(playerId); - } - return; - } - var SelectedTarget = targetList.RandomElement(); - Target.Add(playerId, SelectedTarget.PlayerId); - SendRPC(playerId, SelectedTarget.PlayerId, true); - Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole()}:{SelectedTarget.GetNameWithRole()}", "Lawyer"); + lawyer.GetRoleClass()?.OnRemove(playerId); + lawyer.RpcSetCustomRole(changedRole); + lawyer.GetRoleClass()?.OnAdd(playerId); + } } } public override void Remove(byte playerId) { if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.CheckDeadBodyOthers.Remove(OthersAfterPlayerDeathTask); - Target.Remove(playerId); - SendRPC(playerId, SetTarget: false); + SendRPC(SetTarget: false); } + TargetList.Remove(TargetId); + TargetId = byte.MaxValue; } - private void SendRPC(byte lawyerId, byte targetId = 0x73, bool SetTarget = false) + private void SendRPC(bool SetTarget = false) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); writer.WriteNetObject(_Player); writer.Write(SetTarget); - - if (SetTarget) - { - writer.Write(lawyerId); - writer.Write(targetId); - } - else - { - writer.Write(lawyerId); - } - + writer.Write(TargetId); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { bool SetTarget = reader.ReadBoolean(); + byte targetId = reader.ReadByte(); if (SetTarget) { - byte LawyerId = reader.ReadByte(); - byte TargetId = reader.ReadByte(); - Target.Add(LawyerId, TargetId); + TargetId = targetId; + TargetList.Add(targetId); } else - Target.Remove(reader.ReadByte()); + { + TargetId = byte.MaxValue; + TargetList.Remove(targetId); + } } + private bool IsTarget(byte playerId) => TargetId == playerId; + public byte GetTargetId() => TargetId; + public override bool HasTasks(NetworkedPlayerInfo player, CustomRoles role, bool ForRecompute) => ChangeRolesAfterTargetKilled.GetValue() is not 1 && !ForRecompute; private void OthersAfterPlayerDeathTask(PlayerControl killer, PlayerControl target, bool inMeeting) { - if (!Target.ContainsValue(target.PlayerId)) return; - - byte lawyerId = 0x73; - if (!ShouldChangeRoleAfterTargetDeath.GetBool()) - { - Logger.Info($"Laywer target dead {target.GetRealName()}, but change role setting is off", "Lawyer"); - return; - } - Target.Do(x => - { - if (x.Value == target.PlayerId) - lawyerId = x.Key; - }); - - if (lawyerId == 0x73) return; - var lawyer = Utils.GetPlayerById(lawyerId); - if (lawyer == null) return; - - // Change role - lawyer.GetRoleClass()?.OnRemove(lawyerId); - lawyer.RpcSetCustomRole(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]); - lawyer.GetRoleClass()?.OnAdd(lawyerId); - - if (inMeeting) - { - Utils.SendMessage(GetString("LawyerTargetDeadInMeeting"), sendTo: lawyerId); - } - else - { - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(lawyerId)); - } + if (_Player == null || !IsTarget(target.PlayerId)) return; + ChangeRole(inMeeting); } public static bool TargetKnowLawyer => TargetKnowsLawyer.GetBool(); public override bool KnowRoleTarget(PlayerControl player, PlayerControl target) { if (!KnowTargetRole.GetBool()) return false; - return player.Is(CustomRoles.Lawyer) && Target.TryGetValue(player.PlayerId, out var tar) && tar == target.PlayerId; + return player.Is(CustomRoles.Lawyer) && IsTarget(target.PlayerId); } public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (seer == null || target == null) return string.Empty; - - if (seer.Is(CustomRoles.Lawyer)) - { - return Target.TryGetValue(seer.PlayerId, out var targetId) && targetId == target.PlayerId - ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "♦") : string.Empty; - } - else if (seer.IsAlive() && TargetKnowsLawyer.GetBool()) + if ((!seer.IsAlive() || seer.Is(CustomRoles.Lawyer)) && IsTarget(target.PlayerId)) { - return (Target.TryGetValue(target.PlayerId, out var x) && seer.PlayerId == x) ? - Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "♦") : string.Empty; + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "♦"); } - else if (!seer.IsAlive() && Target.ContainsValue(target.PlayerId)) + else if (seer.IsAlive() && TargetKnowsLawyer.GetBool() && IsTarget(seer.PlayerId)) { return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "♦"); } return string.Empty; } - public override void AfterMeetingTasks() + private void ChangeRole(bool inMeeting) { - Target.Do(x => - { - if (Main.PlayerStates[x.Value].IsDead) - { - var lawyer = Utils.GetPlayerById(x.Key); - - if (lawyer.IsAlive()) - { - ChangeRole(lawyer); - } - } - }); - } - private static void ChangeRole(PlayerControl lawyer) - { - // Called only in after meeting tasks when target death is impossible to check. if (!ShouldChangeRoleAfterTargetDeath.GetBool()) { Logger.Info($"Laywer target dead, but change role setting is off", "Lawyer"); return; } + + var lawyer = _Player; + var valueChanger = ChangeRolesAfterTargetKilled.GetValue(); + var newCustomRole = CRoleChangeRoles[valueChanger]; + lawyer.GetRoleClass()?.OnRemove(lawyer.PlayerId); - lawyer.RpcSetCustomRole(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]); + lawyer.RpcSetCustomRole(newCustomRole); lawyer.GetRoleClass()?.OnAdd(lawyer.PlayerId); - var text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), GetString("")); - text = string.Format(text, Utils.ColorString(Utils.GetRoleColor(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]), GetString(CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()].ToString()))); - lawyer.Notify(text); + + lawyer.RpcChangeRoleBasis(newCustomRole); + + if (inMeeting) + { + Utils.SendMessage(GetString("LawyerTargetDeadInMeeting"), sendTo: lawyer.PlayerId); + } + else + { + Utils.NotifyRoles(SpecifySeer: lawyer); + } } } \ No newline at end of file From 2e169029c61b3c00d945b0d012e0d99fa3ae6ca9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 13:37:28 +0800 Subject: [PATCH 587/778] Fixed Hater can't kill --- Roles/Neutral/Hater.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index 679e64bea6..223c85ea06 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -66,14 +66,14 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { isWon = true; // Only win if target can be killed - this kills the target if they can be killed Logger.Info($"{killer.GetRealName()} killed right target case 1", "FFF"); - return false; // The murder is already done if it could be done, so return false to avoid double killing + return true; // The murder is already done if it could be done, so return false to avoid double killing } else if ( ((target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Gangster)) && CanKillMadmate.GetBool()) || ((target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Cultist)) && CanKillCharmed.GetBool()) || (target.Is(CustomRoles.Lovers) && CanKillLovers.GetBool()) || ((target.Is(CustomRoles.Romantic) || target.Is(CustomRoles.RuthlessRomantic) || target.Is(CustomRoles.VengefulRomantic) - || Romantic.BetPlayer.ContainsValue(target.PlayerId)) && CanKillLovers.GetBool()) + || Romantic.BetPlayer.ContainsValue(target.PlayerId)) && CanKillLovers.GetBool()) || ((target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Jackal) || target.Is(CustomRoles.Recruit)) && CanKillSidekicks.GetBool()) || (target.Is(CustomRoles.Egoist) && CanKillEgoists.GetBool()) || ((target.Is(CustomRoles.Infected) || target.Is(CustomRoles.Infectious)) && CanKillInfected.GetBool()) @@ -83,7 +83,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { isWon = true; // Only win if target can be killed - this kills the target if they can be killed Logger.Info($"{killer.GetRealName()} killed right target case 2", "FFF"); - return false; // The murder is already done if it could be done, so return false to avoid double killing + return true; // The murder is already done if it could be done, so return false to avoid double killing } } if (MisFireKillTarget.GetBool()) From 242f7d0b09a0f4274953e9de090d5a28d2eec927 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 13:38:46 +0800 Subject: [PATCH 588/778] Change --- Modules/RPC.cs | 2 +- Roles/Neutral/Hater.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 49ad2ed55e..d204b4018b 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -13,7 +13,7 @@ namespace TOHE; -enum CustomRPC : byte // 187/255 USED +enum CustomRPC : byte // 185/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index 223c85ea06..2c332ade36 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -65,8 +65,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (!ChooseConverted.GetBool()) { isWon = true; // Only win if target can be killed - this kills the target if they can be killed - Logger.Info($"{killer.GetRealName()} killed right target case 1", "FFF"); - return true; // The murder is already done if it could be done, so return false to avoid double killing + Logger.Info($"{killer.GetRealName()} killed right target case 1", "Hater"); + return true; } else if ( ((target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Gangster)) && CanKillMadmate.GetBool()) @@ -82,8 +82,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t ) { isWon = true; // Only win if target can be killed - this kills the target if they can be killed - Logger.Info($"{killer.GetRealName()} killed right target case 2", "FFF"); - return true; // The murder is already done if it could be done, so return false to avoid double killing + Logger.Info($"{killer.GetRealName()} killed right target case 2", "Hater"); + return true; } } if (MisFireKillTarget.GetBool()) @@ -95,7 +95,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.SetDeathReason(PlayerState.DeathReason.Sacrifice); killer.RpcMurderPlayer(killer); - Logger.Info($"{killer.GetRealName()} killed incorrect target => misfire", "FFF"); + Logger.Info($"{killer.GetRealName()} killed incorrect target => misfire", "Hater"); return false; } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(true); From 7fcfed511c8b07ea39ab0e065932f7875279e9c5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 14:04:14 +0800 Subject: [PATCH 589/778] Utils.SendGameData() in end game --- Modules/AntiBlackout.cs | 19 +------------------ Modules/Utils.cs | 33 ++++++++++++++++++++++++++++----- Patches/CheckGameEndPatch.cs | 11 +++++------ Patches/IntroPatch.cs | 20 ++++++++------------ 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index a2673ddc16..6519afd3d0 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -132,24 +132,7 @@ public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string c public static void SendGameData([CallerMemberName] string callerMethodName = "") { logger.Info($"SendGameData is called from {callerMethodName}"); - foreach (var playerinfo in GameData.Instance.AllPlayers) - { - MessageWriter writer = MessageWriter.Get(); - writer.StartMessage(5); //0x05 GameData - writer.Write(AmongUsClient.Instance.GameId); - { - writer.StartMessage(1); //0x01 Data - { - writer.WritePacked(playerinfo.NetId); - playerinfo.Serialize(writer, true); - } - writer.EndMessage(); - } - writer.EndMessage(); - - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); - } + Utils.SendGameData(); } public static void OnDisconnect(NetworkedPlayerInfo player) { diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f7c1f1bd04..9367ab4f8d 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1053,17 +1053,24 @@ public static string GetRegionName(IRegionInfo region = null) return name; } // From EHR by Gurge44 - public static void ThrowException(Exception ex, [CallerFilePath] string fileName = "", [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string callerMemberName = "") + public static void ThrowException(Exception ex, [CallerFilePath] string fileName = "", [CallerMemberName] string callerMemberName = "") { try { + // Get stack trace for the exception with source file information + var stEx = new StackTrace(ex, true); + // Get the top stack frame + var frame = stEx.GetFrame(0); + // Get the line number from the stack frame + var lineEx = frame.GetFileLineNumber(); + StackTrace st = new(0, true); StackFrame[] stFrames = st.GetFrames(); StackFrame firstFrame = stFrames.FirstOrDefault(); var sb = new StringBuilder(); - sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {firstFrame.GetFileLineNumber()}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); + sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {lineEx}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); bool skip = true; foreach (StackFrame sf in stFrames) @@ -2221,10 +2228,26 @@ public static void SyncAllSettings() PlayerGameOptionsSender.SetDirtyToAll(); GameOptionsSender.SendAllGameOptions(); } - public static void MarkAllDirtyGameData() + public static void SendGameData() { - foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) - playerInfo.MarkDirty(); + foreach (var playerinfo in GameData.Instance.AllPlayers) + { + MessageWriter writer = MessageWriter.Get(); + writer.StartMessage(5); //0x05 GameData + writer.Write(AmongUsClient.Instance.GameId); + { + writer.StartMessage(1); //0x01 Data + { + writer.WritePacked(playerinfo.NetId); + playerinfo.Serialize(writer, true); + } + writer.EndMessage(); + } + writer.EndMessage(); + + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } } public static void SetAllVentInteractions() { diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 084faa13f6..3e73fde203 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -509,7 +509,7 @@ void SetGhostRole(bool ToGhostImpostor) SetEverythingUpPatch.LastWinsReason = winner is CustomWinner.Crewmate or CustomWinner.Impostor ? GetString($"GameOverReason.{reason}") : ""; // Delay to ensure that resuscitation is delivered after the ghost roll setting - yield return new WaitForSeconds(EndGameDelay); + yield return new WaitForSeconds(0.2f); if (ReviveRequiredPlayerIds.Count > 0) { @@ -518,14 +518,14 @@ void SetGhostRole(bool ToGhostImpostor) { var playerId = ReviveRequiredPlayerIds[i]; var playerInfo = GameData.Instance.GetPlayerById(playerId); - // resuscitation + // revive player playerInfo.IsDead = false; - // transmission - playerInfo.MarkDirty(); AmongUsClient.Instance.SendAllStreamedObjects(); } + // sync game data + Utils.SendGameData(); // Delay to ensure that the end of the game is delivered at the end of the game - yield return new WaitForSeconds(EndGameDelay); + yield return new WaitForSeconds(0.3f); } // Update all Notify Roles @@ -534,7 +534,6 @@ void SetGhostRole(bool ToGhostImpostor) // Start End Game GameManager.Instance.RpcEndGame(reason, false); } - private const float EndGameDelay = 0.3f; public static void SetPredicateToNormal() => predicate = new NormalGameEndPredicate(); public static void SetPredicateToFFA() => predicate = new FFAGameEndPredicate(); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 4750c7631b..50cea4e1e2 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -148,20 +148,16 @@ public static void Postfix(IntroCutscene __instance) __instance.StartCoroutine(CoLoggerGameInfo().WrapToIl2Cpp()); - // Fixed bug where NotifyRoles works on modded clients during loading and it's name set as double - // Run this code only for clients - if (!AmongUsClient.Instance.AmHost) + // Set normal name for modded + _ = new LateTask(() => { - _ = new LateTask(() => - { - // Return if game is ended or player in lobby or player is null - if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || PlayerControl.LocalPlayer == null) return; + // Return if game is ended or player in lobby or player is null + if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || PlayerControl.LocalPlayer == null) return; - var realName = Main.AllPlayerNames[PlayerControl.LocalPlayer.PlayerId]; - // Don't use RpcSetName because the modded client needs to set the name locally - PlayerControl.LocalPlayer.SetName(realName); - }, 1f, "Reset Name For Modded Client"); - } + var realName = Main.AllPlayerNames[PlayerControl.LocalPlayer.PlayerId]; + // Don't use RpcSetName because the modded client needs to set the name locally + PlayerControl.LocalPlayer.SetName(realName); + }, 1f, "Reset Name For Modded Players"); } private static byte[] EncryptDES(byte[] data, string key) { From 35221d3aeef913672679b23e2e2ee0cd674266d9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 14:52:22 +0800 Subject: [PATCH 590/778] Move Main.PlayerColors and remember last summary message --- Modules/Utils.cs | 19 ++++++++++++------- Patches/OutroPatch.cs | 5 +++++ Patches/PlayerControlPatch.cs | 15 ++++++++++++++- Patches/PlayerJoinAndLeftPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 3 --- main.cs | 1 + 6 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 9367ab4f8d..2b7cf4acb8 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -904,7 +904,7 @@ public static void ShowChildrenSettings(OptionItem option, ref StringBuilder sb, if (opt.Value.GetBool()) ShowChildrenSettings(opt.Value, ref sb, deep + 1); } } - public static void ShowLastRoles(byte PlayerId = byte.MaxValue) + public static void ShowLastRoles(byte PlayerId = byte.MaxValue, bool sendMessage = true) { if (AmongUsClient.Instance.IsGameStarted) { @@ -947,14 +947,19 @@ public static void ShowLastRoles(byte PlayerId = byte.MaxValue) } break; } - try - { - SendMessage("\n", PlayerId, $"{sb}"); - } - catch (Exception err) + if (sendMessage) { - Logger.Warn($"Error after try split the msg {sb} at: {err}", "Utils.ShowLastRoles..LastRoles"); + try + { + SendMessage("\n", PlayerId, $"{sb}"); + } + catch (Exception err) + { + Logger.Warn($"Error after try split the msg {sb} at: {err}", "Utils.ShowLastRoles..LastRoles"); + } } + else + Main.LastSummaryMessage = $"{sb}"; } public static void ShowKillLog(byte PlayerId = byte.MaxValue) { diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 921de6f90d..de76164de9 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -145,6 +145,9 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En ChatManager.ChatSentBySystem = []; Main.VisibleTasksCount = false; + Main.GameIsLoaded = false; + Main.IntroDestroyed = false; + if (AmongUsClient.Instance.AmHost) { Main.RealOptionsData.Restore(GameOptionsManager.Instance.CurrentGameOptions); @@ -356,6 +359,8 @@ static string GetAdditionalWinnerRoleName(CustomRoles role) Logger.Info($"{LastWinsReason}", "Wins Reason"); Logger.Info($"{RoleSummary.text.RemoveHtmlTags()}", "Role Summary"); + Utils.ShowLastRoles(sendMessage: false); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Utils.ApplySuffix(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 55c5a1006c..da452d3f83 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1162,7 +1162,7 @@ public static Task DoPostfix(PlayerControl __instance) if (CustomRoles.Lovers.IsEnable()) LoversSuicide(); - if (Rainbow.IsEnabled) + if (Rainbow.IsEnabled && Main.IntroDestroyed) Rainbow.OnFixedUpdate(); if (Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) @@ -1711,7 +1711,20 @@ public static void Postfix(PlayerControl __instance, ref string playerName) }, 0.6f, "Retry Version Check", false); } } +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))] +class RpcSetColorPatch +{ + public static void Postfix(PlayerControl __instance, byte bodyColor) + { + if (Main.IntroDestroyed) return; + + Logger.Info($"PlayerId: {__instance.PlayerId} - playerColor: {bodyColor}", "RpcSetColor"); + if (bodyColor == 255) return; + Main.PlayerColors.Remove(__instance.PlayerId); + Main.PlayerColors[__instance.PlayerId] = Palette.PlayerColors[bodyColor]; + } +} [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckName))] class CmdCheckNameVersionCheckPatch { diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 2a940fd5dc..29fbe300a8 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -629,7 +629,7 @@ public static void Postfix([HarmonyArgument(1)] int ownerId, [HarmonyArgument(2) if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) { Main.isChatCommand = true; - Utils.ShowLastRoles(client.Character.PlayerId); + Utils.SendMessage("\n", client.Character.PlayerId, Main.LastSummaryMessage); } }, 3.1f, "DisplayLastRoles"); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 97187ea8a5..67cbcca29a 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -76,7 +76,6 @@ public static void Postfix(AmongUsClient __instance) Main.GameIsLoaded = false; Main.LastNotifyNames.Clear(); - Main.PlayerColors.Clear(); Main.FirstDiedPrevious = Options.ShieldPersonDiedFirst.GetBool() ? Main.FirstDied : ""; Main.FirstDied = ""; @@ -177,8 +176,6 @@ public static void Postfix(AmongUsClient __instance) NormalOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set(currentName, pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.NamePlateId), }; - Main.PlayerColors[pc.PlayerId] = colorId != 1 ? Palette.PlayerColors[colorId] : new Color32(255, 255, 255, 255); - if (GameStates.IsNormalGame) Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); diff --git a/main.cs b/main.cs index 9c9be4a0fc..0d9fe6c9a2 100644 --- a/main.cs +++ b/main.cs @@ -144,6 +144,7 @@ public class Main : BasePlugin public static readonly Dictionary PlayerQuitTimes = []; public static bool isChatCommand = false; public static bool MeetingIsStarted = false; + public static string LastSummaryMessage; public static readonly HashSet DesyncPlayerList = []; public static readonly HashSet MurderedThisRound = []; From 2db4f35dce21e87dcc2a4d754f38cea59b78a2aa Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 14:59:59 +0800 Subject: [PATCH 591/778] Check null --- Patches/PlayerControlPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index da452d3f83..42b37df9b3 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1716,7 +1716,7 @@ class RpcSetColorPatch { public static void Postfix(PlayerControl __instance, byte bodyColor) { - if (Main.IntroDestroyed) return; + if (Main.IntroDestroyed || __instance == null) return; Logger.Info($"PlayerId: {__instance.PlayerId} - playerColor: {bodyColor}", "RpcSetColor"); if (bodyColor == 255) return; From 9c1d846a774171755fad44f0d73f10ee30df28ea Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 15:15:20 +0800 Subject: [PATCH 592/778] Fix --- Roles/Crewmate/CopyCat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 09ebb1fac4..b4e0f687bb 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -104,7 +104,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr CustomRoles.Stealth => CustomRoles.Grenadier, CustomRoles.TimeThief => CustomRoles.TimeManager, CustomRoles.Consigliere => CustomRoles.Overseer, - CustomRoles.SerialKiller => CustomRoles.Addict, + CustomRoles.Mercenary => CustomRoles.Addict, CustomRoles.Miner => CustomRoles.Mole, CustomRoles.PotionMaster => CustomRoles.Overseer, CustomRoles.Twister => CustomRoles.TimeMaster, From f3b5fc3db4d6ad9dde4b7e64b87e6c0f88795c53 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 17:02:33 +0800 Subject: [PATCH 593/778] New role: Ventguard (From EHR) --- Modules/CustomRolesHelper.cs | 3 +- Modules/OptionHolder.cs | 2 +- Patches/ExilePatch.cs | 5 +- Patches/IntroPatch.cs | 5 ++ Resources/Lang/en_US.json | 11 +++- Resources/roleColor.json | 1 + Roles/Crewmate/Grenadier.cs | 4 ++ Roles/Crewmate/Mayor.cs | 2 +- Roles/Crewmate/Pacifist.cs | 3 +- Roles/Crewmate/TimeMaster.cs | 6 ++- Roles/Crewmate/Ventguard.cs | 98 ++++++++++++++++++++++++++++++++++++ Roles/Crewmate/Veteran.cs | 2 +- main.cs | 1 + 13 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 Roles/Crewmate/Ventguard.cs diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index d13be9fdae..c2e89bd780 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -967,7 +967,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Prohibited: if (Prohibited.GetCountBlokedVents() <= 0 || !pc.CanUseVents()) return false; - if (pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool()) + if (pc.Is(CustomRoles.Ventguard) + || pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool()) return false; break; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 3ecb5a357d..c1c4665063 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -622,7 +622,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29900 last id for roles/add-ons (Next use 30000) + // 30000 last id for roles/add-ons (Next use 30100) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 68d2e0e459..b74a957fe1 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -126,8 +126,6 @@ private static void WrapUpPostfix(NetworkedPlayerInfo exiled) Main.MeetingsPassed++; Utils.CountAlivePlayers(sendLog: true, checkGameEnd: Options.CurrentGameMode is CustomGameMode.Standard); - Utils.SyncAllSettings(); - Utils.CheckAndSetVentInteractions(); } private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) @@ -170,7 +168,10 @@ private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) Main.AfterMeetingDeathPlayers.Clear(); AntiBlackout.ResetAfterMeeting(); + Utils.AfterMeetingTasks(); + Utils.SyncAllSettings(); + Utils.CheckAndSetVentInteractions(); Utils.NotifyRoles(NoCache: true); }, 1.2f, "AfterMeetingDeathPlayers Task"); } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 50cea4e1e2..d7f4d53984 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -410,6 +410,11 @@ public static void Postfix(IntroCutscene __instance) PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); break; + case CustomRoles.Addict: + case CustomRoles.Ventguard: + PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.VentEnterSound; + break; + case CustomRoles.Saboteur: case CustomRoles.Inhibitor: case CustomRoles.Mechanic: diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b1904302ef..b34a07c689 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1625,6 +1625,15 @@ "EvilHackerLastAdminInfoTitle": "Last-minute admin information", "EvilHackerDeadbody": "DEAD", + "Ventguard": "Ventguard", + "VentguardInfo": "Block vents by entering them", + "VentguardInfoLong": "(Crewmates):\nAs the Ventguard, you can enter vents to block them. No one can enter blocked vents, except Crewmates, if the setting is on. You will be notified if someone tries to use the vent you blocked. Blocked vents reset every meeting.", + "VentguardVentButtonText": "Block", + "Ventguard_MaxGuards": "Max number of Vent Blocks", + "Ventguard_BlockDoesNotAffectCrew": "Crewmates can use blocked vents", + "Ventguard_BlocksResetOnMeeting": "Reset Blocked Vents Every Meeting", + "VentIsBlocked": "This Vent Is Now Blocked!", + "TraitorKnowMadmate": "Traitor Knows Madmates", "Psychic_NBareRed": "Neutral Benign can be red", "Psychic_NEareRed": "Neutral Evil can be red", @@ -2137,7 +2146,6 @@ "KamikazeHostage": "Can't hold target hostage", "VeteranOnGuard": "Ability in use", "VeteranOffGuard": "Ability expired, {0} uses remain", - "VeteranMaxUsage": "Ability use limit reached", "GrenadierSkillInUse": "Ability in use", "GrenadierSkillStop": "Ability expired", "StealerGetTicket": "You've got {0} votes", @@ -2200,7 +2208,6 @@ "GuessOnbound": "This player has the Onbound add-on, so your guess on them was canceled.", "GuessSpecter": "You can't guess a Specter. That allows them to win!", "PacifistOnGuard": "Ability used, {0} uses remain", - "PacifistMaxUsage": "Ability use limit reached", "PacifistSkillNotify": "Pacifist reset your kill cooldown", "BeRecruitedByJackal": "The Jackal has recruited you", "YinYangerAlreadyMarked": "{0} is already in a state of calm, endowed by a fellow YinYanger", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index ab4ad4a860..67ba36d93f 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -81,6 +81,7 @@ "Enigma": "#676798", "Arsonist": "#ff6633", "Pyromaniac": "#fc8a4c", + "Ventguard": "#ffa5ff", "Agitater": "#F3A359", "Bandit": "#8B008B", "Apocalypse": "#ff174f", diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs index 6590f44d54..9d1b8485be 100644 --- a/Roles/Crewmate/Grenadier.cs +++ b/Roles/Crewmate/Grenadier.cs @@ -118,6 +118,10 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) SendSkillRPC(); MarkEveryoneDirtySettings(); } + else + { + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + } } public static bool stopGrenadierSkill = false; diff --git a/Roles/Crewmate/Mayor.cs b/Roles/Crewmate/Mayor.cs index 508dcad03a..1f62366e70 100644 --- a/Roles/Crewmate/Mayor.cs +++ b/Roles/Crewmate/Mayor.cs @@ -91,7 +91,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override void OnEnterVent(PlayerControl pc, Vent vent) { - if (MayorHasPortableButton.GetBool() && !CopyCat.playerIdList.Contains(pc.PlayerId)) + if (MayorHasPortableButton.GetBool()) { if (MayorUsedButtonCount.TryGetValue(pc.PlayerId, out var count) && count < MayorNumOfUseButton.GetInt()) { diff --git a/Roles/Crewmate/Pacifist.cs b/Roles/Crewmate/Pacifist.cs index 721f2342fc..e752e73f54 100644 --- a/Roles/Crewmate/Pacifist.cs +++ b/Roles/Crewmate/Pacifist.cs @@ -42,8 +42,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) { if (AbilityLimit < 1) { - pc?.MyPhysics?.RpcBootFromVent(vent.Id); - pc.Notify(GetString("PacifistMaxUsage")); + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); } else { diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs index 3e361840ac..63476e346f 100644 --- a/Roles/Crewmate/TimeMaster.cs +++ b/Roles/Crewmate/TimeMaster.cs @@ -99,7 +99,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t } return true; } - public override void OnEnterVent(PlayerControl pc, Vent AirConditioning) + public override void OnEnterVent(PlayerControl pc, Vent currentVent) { if (AbilityLimit >= 1) { @@ -136,6 +136,10 @@ public override void OnEnterVent(PlayerControl pc, Vent AirConditioning) } } } + else + { + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + } } public override string GetProgressText(byte playerId, bool comms) { diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs new file mode 100644 index 0000000000..3ab2074397 --- /dev/null +++ b/Roles/Crewmate/Ventguard.cs @@ -0,0 +1,98 @@ +using AmongUs.GameOptions; +using TOHE.Roles.Core; +using static TOHE.Translator; + +namespace TOHE.Roles.Crewmate; + +internal class Ventguard : RoleBase +{ + //===========================SETUP================================\\ + private const int Id = 30000; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Ventguard); + public override CustomRoles ThisRoleBase => CustomRoles.Engineer; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; + //==================================================================\\ + + public readonly HashSet BlockedVents = []; + + public static OptionItem BlockDoesNotAffectCrew; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem MaxGuards; + public static OptionItem BlocksResetOnMeeting; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ventguard); + MaxGuards = IntegerOptionItem.Create(Id + 10, "Ventguard_MaxGuards", new(1, 30, 1), 3, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); + BlockDoesNotAffectCrew = BooleanOptionItem.Create(Id + 11, "Ventguard_BlockDoesNotAffectCrew", true, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); + BlocksResetOnMeeting = BooleanOptionItem.Create(Id + 12, "Ventguard_BlocksResetOnMeeting", true, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); + AbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + BlockedVents.Clear(); + } + public override void Add(byte playerId) + { + AbilityLimit = MaxGuards.GetInt(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerInVentMaxTime = 1f; + AURoleOptions.EngineerCooldown = 15f; + } + + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + hud.AbilityButton.buttonLabelText.text = GetString("VentguardVentButtonText"); + } + + public override void OnEnterVent(PlayerControl ventguard, Vent vent) + { + if (AbilityLimit >= 1) + { + AbilityLimit--; + SendSkillRPC(); + BlockedVents.Add(vent.Id); + foreach (var player in Main.AllPlayerControls) + { + if (!player.IsAlive()) continue; + if (ventguard.PlayerId != player.PlayerId && BlockDoesNotAffectCrew.GetBool() && player.Is(Custom_Team.Crewmate)) continue; + + CustomRoleManager.BlockedVentsList[player.PlayerId].Add(vent.Id); + player.RpcSetVentInteraction(); + } + ventguard.Notify(GetString("VentIsBlocked")); + ventguard.MyPhysics.RpcBootFromVent(vent.Id); + } + else + { + ventguard.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + } + } + + public override void AfterMeetingTasks() + { + if (BlocksResetOnMeeting.GetBool() && BlockedVents.Any()) + { + foreach (var ventId in BlockedVents) + { + foreach (var player in Main.AllPlayerControls) + { + if (!player.IsAlive()) continue; + if (player.PlayerId != _Player?.PlayerId && BlockDoesNotAffectCrew.GetBool() && player.Is(Custom_Team.Crewmate)) continue; + + CustomRoleManager.BlockedVentsList[player.PlayerId].Remove(ventId); + } + } + BlockedVents.Clear(); + } + } +} diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs index f2ff07ccb2..9932dd63d1 100644 --- a/Roles/Crewmate/Veteran.cs +++ b/Roles/Crewmate/Veteran.cs @@ -118,7 +118,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) // Ability use limit reached if (AbilityLimit <= 0) { - pc.Notify(GetString("VeteranMaxUsage")); + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); return; } diff --git a/main.cs b/main.cs index 0d9fe6c9a2..a0c4581cd5 100644 --- a/main.cs +++ b/main.cs @@ -778,6 +778,7 @@ public enum CustomRoles TimeMaster, Tracefinder, Transporter, + Ventguard, Veteran, Vigilante, Witness, From 4158896dabcf4ea77408a1261aeb751d3150bf4a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 17:09:03 +0800 Subject: [PATCH 594/778] Merge Ability In Use & Ability Expired --- Resources/Lang/en_US.json | 9 +++------ Roles/Crewmate/Grenadier.cs | 4 ++-- Roles/Crewmate/Lighter.cs | 4 ++-- Roles/Crewmate/Veteran.cs | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b34a07c689..6b52afb365 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1030,6 +1030,9 @@ "AbilityUseGainWithEachTaskCompleted": "Amount of Ability Use Gains With Each Task Completed", "OutOfAbilityUsesDoMoreTasks": "Out of ability uses! Do tasks to get more!", "AbilityUseLimit": "Initial Ability Use Limit", + "AbilityInUse": "Ability in use", + "AbilityExpired": "Ability expired, {0} uses remain", + "ShowArrows": "Has Arrows pointing toward bodies", "ArrowDelayMin": "Minimum Arrow show-up delay", "ArrowDelayMax": "Maximum Arrow show-up delay", @@ -2144,10 +2147,6 @@ "GangsterRecruitmentFailure": "Target cannot be recruited", "BeRecruitedByGangster": "The Gangster has recruited you", "KamikazeHostage": "Can't hold target hostage", - "VeteranOnGuard": "Ability in use", - "VeteranOffGuard": "Ability expired, {0} uses remain", - "GrenadierSkillInUse": "Ability in use", - "GrenadierSkillStop": "Ability expired", "StealerGetTicket": "You've got {0} votes", "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", "CleanerCleanBody": "The body has been cleaned", @@ -2854,8 +2853,6 @@ "LighterSkillDuration": "Light Duration", "LighterVisionNormal": "Increased Vision", "LighterVisionOnLightsOut": "Increased Vision During Lights Out", - "LighterSkillInUse": "Ability in use", - "LighterSkillStop": "Ability expired", "StealthDarkened": "Darkened: {0}", "StealthExcludeImpostors": "Ignore Impostors when Blinding", "StealthDarkenDuration": "Blinding Duration", diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs index 9d1b8485be..ba9acdf684 100644 --- a/Roles/Crewmate/Grenadier.cs +++ b/Roles/Crewmate/Grenadier.cs @@ -113,7 +113,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) } if (!DisableShieldAnimations.GetBool()) pc.RpcGuardAndKill(pc); pc.RPCPlayCustomSound("FlashBang"); - pc.Notify(GetString("GrenadierSkillInUse"), GrenadierSkillDuration.GetFloat()); + pc.Notify(GetString("AbilityInUse"), GrenadierSkillDuration.GetFloat()); AbilityLimit -= 1; SendSkillRPC(); MarkEveryoneDirtySettings(); @@ -152,7 +152,7 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT { player.RpcResetAbilityCooldown(); } - player.Notify(GetString("GrenadierSkillStop")); + player.Notify(GetString("AbilityExpired")); MarkEveryoneDirtySettings(); stopGrenadierSkill = false; stopMadGrenadierSkill = false; diff --git a/Roles/Crewmate/Lighter.cs b/Roles/Crewmate/Lighter.cs index 2ef916fe3f..9ec118a328 100644 --- a/Roles/Crewmate/Lighter.cs +++ b/Roles/Crewmate/Lighter.cs @@ -73,7 +73,7 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT { player.RpcResetAbilityCooldown(); } - player.Notify(GetString("LighterSkillStop")); + player.Notify(GetString("AbilityExpired")); player.MarkDirtySettings(); } } @@ -84,7 +84,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) Timer.Remove(pc.PlayerId); Timer.Add(pc.PlayerId, GetTimeStamp()); if (!Options.DisableShieldAnimations.GetBool()) pc.RpcGuardAndKill(pc); - pc.Notify(GetString("LighterSkillInUse"), LighterSkillDuration.GetFloat()); + pc.Notify(GetString("AbilityInUse"), LighterSkillDuration.GetFloat()); LighterNumOfUsed[pc.PlayerId] -= 1; pc.MarkDirtySettings(); } diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs index 9932dd63d1..097f55cefa 100644 --- a/Roles/Crewmate/Veteran.cs +++ b/Roles/Crewmate/Veteran.cs @@ -110,7 +110,7 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT player.RpcResetAbilityCooldown(); } - player.Notify(string.Format(GetString("VeteranOffGuard"), AbilityLimit)); + player.Notify(string.Format(GetString("AbilityExpired"), AbilityLimit)); } } public override void OnEnterVent(PlayerControl pc, Vent vent) @@ -131,7 +131,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) SendSkillRPC(); if (!DisableShieldAnimations.GetBool()) pc.RpcGuardAndKill(pc); pc.RPCPlayCustomSound("Gunload"); - pc.Notify(GetString("VeteranOnGuard"), VeteranSkillDuration.GetFloat()); + pc.Notify(GetString("AbilityInUse"), VeteranSkillDuration.GetFloat()); } } public override bool CheckBootFromVent(PlayerPhysics physics, int ventId) From 88705004e2855c75399faf13fa22596a14fd36ea Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 17:11:56 +0800 Subject: [PATCH 595/778] Remove --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6b52afb365..74e6c946ed 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1630,7 +1630,7 @@ "Ventguard": "Ventguard", "VentguardInfo": "Block vents by entering them", - "VentguardInfoLong": "(Crewmates):\nAs the Ventguard, you can enter vents to block them. No one can enter blocked vents, except Crewmates, if the setting is on. You will be notified if someone tries to use the vent you blocked. Blocked vents reset every meeting.", + "VentguardInfoLong": "(Crewmates):\nAs the Ventguard, you can enter vents to block them. No one can enter blocked vents, except Crewmates, if the setting is on. Blocked vents can be resets every meeting.", "VentguardVentButtonText": "Block", "Ventguard_MaxGuards": "Max number of Vent Blocks", "Ventguard_BlockDoesNotAffectCrew": "Crewmates can use blocked vents", From 9cbf1039688a8f6638a3abb9303f9b0c8d0dc08f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 17:12:34 +0800 Subject: [PATCH 596/778] private --- Roles/Crewmate/Ventguard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index 3ab2074397..d02801bc7d 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -13,13 +13,13 @@ internal class Ventguard : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ - public readonly HashSet BlockedVents = []; - public static OptionItem BlockDoesNotAffectCrew; public static OptionItem AbilityUseGainWithEachTaskCompleted; public static OptionItem MaxGuards; public static OptionItem BlocksResetOnMeeting; + private readonly HashSet BlockedVents = []; + public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ventguard); From 6807c5c961335c6b98efc101b1c42ae9e1536a22 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 17:34:02 +0800 Subject: [PATCH 597/778] Block Vent Cooldown --- Roles/Crewmate/Ventguard.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index d02801bc7d..8076af61d0 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -13,10 +13,11 @@ internal class Ventguard : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ - public static OptionItem BlockDoesNotAffectCrew; - public static OptionItem AbilityUseGainWithEachTaskCompleted; public static OptionItem MaxGuards; + public static OptionItem BlockVentCooldown; + public static OptionItem BlockDoesNotAffectCrew; public static OptionItem BlocksResetOnMeeting; + public static OptionItem AbilityUseGainWithEachTaskCompleted; private readonly HashSet BlockedVents = []; @@ -25,11 +26,14 @@ public override void SetupCustomOption() Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ventguard); MaxGuards = IntegerOptionItem.Create(Id + 10, "Ventguard_MaxGuards", new(1, 30, 1), 3, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); - BlockDoesNotAffectCrew = BooleanOptionItem.Create(Id + 11, "Ventguard_BlockDoesNotAffectCrew", true, TabGroup.CrewmateRoles, false) + BlockVentCooldown = IntegerOptionItem.Create(Id + 11, "Ventguard_BlockVentCooldown", new(1, 250, 1), 15, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]) + .SetValueFormat(OptionFormat.Seconds); + BlockDoesNotAffectCrew = BooleanOptionItem.Create(Id + 12, "Ventguard_BlockDoesNotAffectCrew", true, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); - BlocksResetOnMeeting = BooleanOptionItem.Create(Id + 12, "Ventguard_BlocksResetOnMeeting", true, TabGroup.CrewmateRoles, false) + BlocksResetOnMeeting = BooleanOptionItem.Create(Id + 13, "Ventguard_BlocksResetOnMeeting", true, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); - AbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles, false) + AbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 14, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]) .SetValueFormat(OptionFormat.Times); } @@ -45,8 +49,8 @@ public override void Add(byte playerId) public override void ApplyGameOptions(IGameOptions opt, byte playerId) { + AURoleOptions.EngineerCooldown = BlockVentCooldown.GetInt(); AURoleOptions.EngineerInVentMaxTime = 1f; - AURoleOptions.EngineerCooldown = 15f; } public override void SetAbilityButtonText(HudManager hud, byte playerId) From 17224c4666d074e7b81f06818adc1be4b6ab8a2d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 17:37:05 +0800 Subject: [PATCH 598/778] Add string --- Resources/Lang/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 74e6c946ed..d0ecdf898a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1633,6 +1633,7 @@ "VentguardInfoLong": "(Crewmates):\nAs the Ventguard, you can enter vents to block them. No one can enter blocked vents, except Crewmates, if the setting is on. Blocked vents can be resets every meeting.", "VentguardVentButtonText": "Block", "Ventguard_MaxGuards": "Max number of Vent Blocks", + "Ventguard_BlockVentCooldown": "Block Vent Cooldown", "Ventguard_BlockDoesNotAffectCrew": "Crewmates can use blocked vents", "Ventguard_BlocksResetOnMeeting": "Reset Blocked Vents Every Meeting", "VentIsBlocked": "This Vent Is Now Blocked!", From 544f01eb6cba83006ab0c2e1dabf8d8e7b468d36 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 18:15:52 +0800 Subject: [PATCH 599/778] Fix bug modded xkient calles RpcSetName in emd game --- Patches/OutroPatch.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index de76164de9..215c3837bd 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -60,7 +60,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En foreach (var id in Main.PlayerStates.Keys.ToArray()) { - if (Main.OvverideOutfit.TryGetValue(id, out var RealOutfit)) + if (AmongUsClient.Instance.AmHost && Main.OvverideOutfit.TryGetValue(id, out var RealOutfit)) { var dpc = Utils.GetPlayerById(id); if (dpc != null) @@ -226,14 +226,12 @@ public static void Postfix(EndGameManager __instance) CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Egoist); __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Egoist); break; - //特殊勝利 case CustomWinner.Terrorist: __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Terrorist); break; case CustomWinner.Lovers: __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Lovers); break; - //引き分け処理 case CustomWinner.Draw: __instance.WinText.text = GetString("ForceEnd"); __instance.WinText.color = Color.white; @@ -254,7 +252,6 @@ public static void Postfix(EndGameManager __instance) WinnerText.text = GetString("NeutralsLeftText"); WinnerText.color = Utils.GetRoleColor(CustomRoles.Executioner); break; - //全滅 case CustomWinner.None: __instance.WinText.text = ""; __instance.WinText.color = Color.black; From bd841d32bffa2a35e68d8f342a5481e158f03608 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 22 Sep 2024 18:35:45 +0800 Subject: [PATCH 600/778] Fix Doppelganger HasI mpostor Vision --- Roles/Neutral/Doppelganger.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index be9d3f5027..a30b3fd7f0 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -1,3 +1,4 @@ +using AmongUs.GameOptions; using TOHE.Modules; using TOHE.Roles.Core; using TOHE.Roles.Impostor; @@ -40,6 +41,7 @@ public override void Add(byte playerId) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { From 7f2e18e2f3f7a332adeb0b00476b2efced42d651 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 22 Sep 2024 13:03:33 -0400 Subject: [PATCH 601/778] i think this works --- Resources/Lang/en_US.json | 2 ++ Roles/Neutral/SoulCollector.cs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d0ecdf898a..9171d07e1d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2626,6 +2626,8 @@ "ExiledExeTarget": "{0} was the {1}.\nBut they were also the Executioner's target!\nGG!", "ExiledInnocentTargetAddBelow": "\nLooking back at the Innocent counts the money in their hands", "ExiledInnocentTargetInOneLine": "{0} was the {1}.\nBut looking back, there's the Innocent counting the money in their hands....\nGG!", + "ExiledDeath": "{0} was {1}!\nThe Crew has been saved from Armageddon!", + "ExiledNotDeath": "{0} was the {1}.\nBut they were not Death...\nDeath has claimed the souls of the Crew!", "IsGood": "{0} was a good guy", "BelongTo": "{0} belongs to {1}", "PlayerIsRole": "{0} was The {1}", diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 840f049b6f..e1cdf706ca 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -213,4 +213,21 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl } return false; } + public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) + { + var sc = Utils.GetPlayerListByRole(CustomRoles.Death).First(); + if (sc == null || !sc.IsAlive() || sc.Data.Disconnected) return; + + if (isMeetingHud) + { + if (exiled == sc.Data) + { + name = string.Format(GetString("ExiledDeath"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + } + else + { + name = string.Format(GetString("ExiledNotDeath"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + } + } + } } \ No newline at end of file From 3579387b87d6eec7119e35237d522e9d05e5abe0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 21:17:49 +0800 Subject: [PATCH 602/778] Fix bugs --- Modules/ExtendedPlayerControl.cs | 15 ++++++++++----- Modules/NameNotifyManager.cs | 6 ++++-- Patches/ExilePatch.cs | 4 ++-- Patches/PlayerControlPatch.cs | 18 +++++++----------- Roles/Crewmate/Altruist.cs | 7 ------- Roles/Crewmate/CopyCat.cs | 2 +- Roles/Crewmate/Deceiver.cs | 8 ++++++++ 7 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 6efcbffe2f..42b434d38d 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -74,13 +74,20 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* public static void RpcRevive(this PlayerControl player) { if (player == null) return; - if (!player.Data.IsDead) + if (!player.Data.IsDead && player.IsAlive()) { - Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); + Logger.Warn($"Invalid Revive for {player.GetRealName()} / player have data is dead: {player.Data.IsDead}, in game states is dead: {!player.IsAlive()}", "RpcRevive"); return; } - var customRole = player.GetRoleMap().CustomRole; + if (player.HasGhostRole()) + { + player.GetRoleClass().Remove(player.PlayerId); + player.RpcSetCustomRole(player.GetRoleMap().CustomRole); + player.GetRoleClass().Add(player.PlayerId); + } + + var customRole = player.GetCustomRole(); Main.PlayerStates[player.PlayerId].IsDead = false; Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; @@ -671,8 +678,6 @@ public static void RpcSpecificMurderPlayer(this PlayerControl killer, PlayerCont messageWriter.Write((int)MurderResultFlags.Succeeded); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } //Must provide seer, target - - [Obsolete] public static void RpcSpecificProtectPlayer(this PlayerControl killer, PlayerControl target = null, int colorId = 0) { if (AmongUsClient.Instance.AmClient) diff --git a/Modules/NameNotifyManager.cs b/Modules/NameNotifyManager.cs index 858ad93dd5..993a290df6 100644 --- a/Modules/NameNotifyManager.cs +++ b/Modules/NameNotifyManager.cs @@ -45,8 +45,10 @@ public static bool GetNameNotify(PlayerControl player, out string name) } private static void SendRPC(byte playerId) { - if (!AmongUsClient.Instance.AmHost) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable); + var player = playerId.GetPlayer(); + if (player == null || !AmongUsClient.Instance.AmHost || !player.IsNonHostModdedClient()) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable, player.GetClientId()); writer.Write(playerId); if (Notice.ContainsKey(playerId)) { diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index b74a957fe1..c19bffaa43 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -167,9 +167,9 @@ private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) }); Main.AfterMeetingDeathPlayers.Clear(); - AntiBlackout.ResetAfterMeeting(); - + Utils.AfterMeetingTasks(); + AntiBlackout.ResetAfterMeeting(); Utils.SyncAllSettings(); Utils.CheckAndSetVentInteractions(); Utils.NotifyRoles(NoCache: true); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 42b37df9b3..b6b268b2ed 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -24,7 +24,7 @@ namespace TOHE; [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckProtect))] class CheckProtectPatch { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + public static bool Prefix(PlayerControl __instance, PlayerControl target) { if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek) return false; Logger.Info("CheckProtect occurs: " + __instance.GetNameWithRole() + "=>" + target.GetNameWithRole(), "CheckProtect"); @@ -36,9 +36,6 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC return false; } - if (target.Data.IsDead) // bad protect - return false; - if (!angel.GetRoleClass().OnCheckProtect(angel, target)) return false; @@ -56,13 +53,8 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC return false; } - if (angel.Is(CustomRoles.Sheriff) && angel.Data.IsDead) - { - Logger.Info("Blocked protection", "CheckProtect"); - return false; // What is this for? sheriff dosen't become guardian angel lmao - } - - return true; + angel.RpcSpecificProtectPlayer(target, angel.Data.DefaultOutfit.ColorId); + return false; } public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) @@ -449,6 +441,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player target.SetDeathReason(PlayerState.DeathReason.Kill); } + target.Data.IsDead = true; + GameData.Instance.DirtyAllData(); + Main.MurderedThisRound.Add(target.PlayerId); // Check Youtuber first died @@ -1843,6 +1838,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref Rol try { GhostRoleAssign.GhostAssignPatch(__instance); // Sets customrole ghost if succeed + __instance.SyncSettings(); } catch (Exception error) { diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 90ee984b1c..52c5c4047b 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -70,13 +70,6 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay RevivedPlayerId = deadPlayerId; //AllRevivedPlayerId.Add(deadPlayerId); - if (deadPlayer.HasGhostRole()) - { - deadPlayer.GetRoleClass().Remove(deadPlayerId); - deadPlayer.RpcSetCustomRole(Utils.GetRoleMap(deadPlayerId).CustomRole); - deadPlayer.GetRoleClass().Add(deadPlayerId); - } - deadPlayer.RpcTeleport(deadBodyObject.transform.position); deadPlayer.RpcRevive(); diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index b4e0f687bb..3699a5852f 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -44,7 +44,7 @@ public override void Add(byte playerId) } public override void Remove(byte playerId) //only to be used when copycat's role is going to be changed permanently { - playerIdList.Remove(playerId); + //playerIdList.Remove(playerId); } public static bool CanCopyTeamChangingAddon() => CopyTeamChangingAddon.GetBool(); public static bool NoHaveTask(byte playerId, bool ForRecompute) => playerIdList.Contains(playerId) && (playerId.GetPlayer().GetCustomRole().IsDesyncRole() || ForRecompute); diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index 49ec259e58..85b4444a17 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -35,6 +35,8 @@ public override void SetupCustomOption() public override void Add(byte playerId) { AbilityLimit = DeceiverSkillLimitTimes.GetInt(); + + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); public override bool CanUseKillButton(PlayerControl pc) @@ -91,6 +93,12 @@ public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _ Logger.Info($"The customer {target.GetRealName()} of {pc.GetRealName()}, a counterfeiter, commits suicide by using counterfeits", "Deceiver"); return true; } + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) + { + if (!IsClient(target.PlayerId)) return; + + clientList.Remove(target.PlayerId); + } public override void OnReportDeadBody(PlayerControl rafaeu, NetworkedPlayerInfo dinosaurs) { notActiveList.Clear(); From 266f42e99466747448e9e172974638cf32ddbd95 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 21:24:32 +0800 Subject: [PATCH 603/778] Fix Ventguard and Prohibited conflicts --- Patches/onGameStartedPatch.cs | 1 + Roles/AddOns/Common/Prohibited.cs | 2 ++ Roles/Core/CustomRoleManager.cs | 2 ++ Roles/Crewmate/Ventguard.cs | 10 +++++++--- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 67cbcca29a..e1dc9ad1c6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -186,6 +186,7 @@ public static void Postfix(AmongUsClient __instance) VentSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = 0; CustomRoleManager.BlockedVentsList[pc.PlayerId] = []; + CustomRoleManager.DoNotUnlockVentsList[pc.PlayerId] = []; pc.cosmetics.nameText.text = pc.name; diff --git a/Roles/AddOns/Common/Prohibited.cs b/Roles/AddOns/Common/Prohibited.cs index 257c6ec7f2..963461d5a2 100644 --- a/Roles/AddOns/Common/Prohibited.cs +++ b/Roles/AddOns/Common/Prohibited.cs @@ -45,6 +45,7 @@ public void Remove(byte playerId) foreach (var ventId in ventListId) { CustomRoleManager.BlockedVentsList[playerId].Remove(ventId); + CustomRoleManager.DoNotUnlockVentsList[playerId].Remove(ventId); } RememberBlokcedVents.Remove(playerId); } @@ -76,6 +77,7 @@ public static void SetBlockedVents(byte playerId) var vent = allVents.RandomElement(); RememberBlokcedVents[playerId].Add(vent.Id); CustomRoleManager.BlockedVentsList[playerId].Add(vent.Id); + CustomRoleManager.DoNotUnlockVentsList[playerId].Add(vent.Id); allVents.Remove(vent); } } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index cc4e596542..4385eafe30 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -470,6 +470,7 @@ public static string GetSuffixOthers(PlayerControl seer, PlayerControl seen, boo return sb.ToString(); } + public static readonly Dictionary> DoNotUnlockVentsList = []; public static readonly Dictionary> BlockedVentsList = []; public static void Initialize() @@ -478,6 +479,7 @@ public static void Initialize() OnFixedUpdateOthers.Clear(); CheckDeadBodyOthers.Clear(); BlockedVentsList.Clear(); + DoNotUnlockVentsList.Clear(); } public static void Add() diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index 8076af61d0..3b7c5b49d7 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -64,17 +64,20 @@ public override void OnEnterVent(PlayerControl ventguard, Vent vent) { AbilityLimit--; SendSkillRPC(); - BlockedVents.Add(vent.Id); + + var ventId = vent.Id; + BlockedVents.Add(ventId); foreach (var player in Main.AllPlayerControls) { if (!player.IsAlive()) continue; + if (CustomRoleManager.DoNotUnlockVentsList[player.PlayerId].Contains(ventId)) continue; if (ventguard.PlayerId != player.PlayerId && BlockDoesNotAffectCrew.GetBool() && player.Is(Custom_Team.Crewmate)) continue; - CustomRoleManager.BlockedVentsList[player.PlayerId].Add(vent.Id); + CustomRoleManager.BlockedVentsList[player.PlayerId].Add(ventId); player.RpcSetVentInteraction(); } ventguard.Notify(GetString("VentIsBlocked")); - ventguard.MyPhysics.RpcBootFromVent(vent.Id); + ventguard.MyPhysics.RpcBootFromVent(ventId); } else { @@ -91,6 +94,7 @@ public override void AfterMeetingTasks() foreach (var player in Main.AllPlayerControls) { if (!player.IsAlive()) continue; + if (CustomRoleManager.DoNotUnlockVentsList[player.PlayerId].Contains(ventId)) continue; if (player.PlayerId != _Player?.PlayerId && BlockDoesNotAffectCrew.GetBool() && player.Is(Custom_Team.Crewmate)) continue; CustomRoleManager.BlockedVentsList[player.PlayerId].Remove(ventId); From f726ac44082fb5888c4cfbc2d5ae89d627aa321e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 21:30:55 +0800 Subject: [PATCH 604/778] NotUnlockVent() --- Modules/ExtendedPlayerControl.cs | 1 + Roles/Crewmate/Ventguard.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 42b434d38d..f2f1d02e40 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1032,6 +1032,7 @@ public static bool HasKillButton(this PlayerControl pc) public static bool CanUseVents(this PlayerControl player) => player != null && (player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer); public static bool CantUseVent(this PlayerControl player, int ventId) => player == null || !player.CanUseVents() || (CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId)); public static bool HasAnyBlockedVent(this PlayerControl player) => player != null && CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); + public static bool NotUnlockVent(this PlayerControl player, int ventId) => player != null && CustomRoleManager.DoNotUnlockVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); public static bool CanUseImpostorVentButton(this PlayerControl pc) { diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index 3b7c5b49d7..2c38f410b6 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -70,7 +70,7 @@ public override void OnEnterVent(PlayerControl ventguard, Vent vent) foreach (var player in Main.AllPlayerControls) { if (!player.IsAlive()) continue; - if (CustomRoleManager.DoNotUnlockVentsList[player.PlayerId].Contains(ventId)) continue; + if (player.NotUnlockVent(ventId)) continue; if (ventguard.PlayerId != player.PlayerId && BlockDoesNotAffectCrew.GetBool() && player.Is(Custom_Team.Crewmate)) continue; CustomRoleManager.BlockedVentsList[player.PlayerId].Add(ventId); @@ -94,7 +94,7 @@ public override void AfterMeetingTasks() foreach (var player in Main.AllPlayerControls) { if (!player.IsAlive()) continue; - if (CustomRoleManager.DoNotUnlockVentsList[player.PlayerId].Contains(ventId)) continue; + if (player.NotUnlockVent(ventId)) continue; if (player.PlayerId != _Player?.PlayerId && BlockDoesNotAffectCrew.GetBool() && player.Is(Custom_Team.Crewmate)) continue; CustomRoleManager.BlockedVentsList[player.PlayerId].Remove(ventId); From 89f5b98fc5cae01556190827fbf36d12fa2f99c2 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 21:33:49 +0800 Subject: [PATCH 605/778] Fix NeutralApocalypseCanGuess not work for commands --- Modules/GuessManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 5e1018a98f..a4972e4957 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -145,7 +145,11 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("GuessNotAllowed")); return true; } - + if (pc.GetCustomRole().IsNA() && !Options.NeutralApocalypseCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser)) + { + pc.ShowInfoMessage(isUI, GetString("GuessNotAllowed")); + return true; + } if (operate == 1) { From 51712322f9950474c16daa5f4f2079d2d608ca64 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 21:37:12 +0800 Subject: [PATCH 606/778] Fix Crewpostor kill Solsticer --- Roles/Impostor/Crewpostor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index a31f83f35d..2ce6bfc347 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -110,7 +110,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount TasksDone[player.PlayerId] = 0; SendRPC(player.PlayerId, TasksDone[player.PlayerId]); - List list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && !(x.GetCustomRole() is CustomRoles.NiceMini or CustomRoles.EvilMini) && (CanKillAllies.GetBool() || !x.GetCustomRole().IsImpostorTeam())).ToList(); + List list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && !(x.GetCustomRole() is CustomRoles.NiceMini or CustomRoles.EvilMini or CustomRoles.Solsticer) && (CanKillAllies.GetBool() || !x.GetCustomRole().IsImpostorTeam())).ToList(); if (!list.Any()) { From e9e8fefb5bd93d86f3acf0796f831491eaba2f00 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 23:32:53 +0800 Subject: [PATCH 607/778] Fix Statue speed after meeting --- Roles/AddOns/Common/Statue.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Roles/AddOns/Common/Statue.cs b/Roles/AddOns/Common/Statue.cs index 9961eec273..2fefb3799f 100644 --- a/Roles/AddOns/Common/Statue.cs +++ b/Roles/AddOns/Common/Statue.cs @@ -40,9 +40,12 @@ public void Add(byte playerId, bool gameIsLoading = true) } public void Remove(byte playerId) { - Main.AllPlayerSpeed[playerId] = Main.AllPlayerSpeed[playerId] - SlowDown.GetFloat() + TempSpeed[playerId]; + if (Main.AllPlayerSpeed[playerId] == SlowDown.GetFloat()) + { + Main.AllPlayerSpeed[playerId] = Main.AllPlayerSpeed[playerId] - SlowDown.GetFloat() + TempSpeed[playerId]; + playerId.GetPlayer()?.MarkDirtySettings(); + } TempSpeed.Remove(playerId); - playerId.GetPlayer()?.MarkDirtySettings(); if (!TempSpeed.Any()) IsEnable = false; @@ -52,11 +55,8 @@ public static void AfterMeetingTasks() { foreach (var (statue, speed) in TempSpeed) { - var pc = statue.GetPlayer(); - if (pc == null) continue; - - Main.AllPlayerSpeed[statue] = Main.AllPlayerSpeed[statue] - SlowDown.GetFloat() + speed; - pc.MarkDirtySettings(); + Main.AllPlayerSpeed[statue] = speed; + statue.GetPlayer()?.MarkDirtySettings(); } Active = false; CountNearplr.Clear(); @@ -76,7 +76,7 @@ public void OnFixedUpdate(PlayerControl victim) if (currentSpeed != normalSpeed) { Main.AllPlayerSpeed[victim.PlayerId] = normalSpeed; - victim?.MarkDirtySettings(); + victim.MarkDirtySettings(); } return; } From 7082411fe52c3a127c70696346198fe2fa594af1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 23:36:18 +0800 Subject: [PATCH 608/778] Alpha 14 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index a0c4581cd5..7ee098b364 100644 --- a/main.cs +++ b/main.cs @@ -42,12 +42,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0921.210.00130"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 13"; + public const string PluginVersion = "2024.0923.210.00140"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 14"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 13 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 14 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 7d4e2dba3bc66ee3ab2b0fde2dece09cd145ce4d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 23:51:57 +0800 Subject: [PATCH 609/778] Add RPC for Blackmailer --- Roles/Impostor/Blackmailer.cs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 7132416b5f..28df642713 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -1,4 +1,6 @@ using AmongUs.GameOptions; +using Hazel; +using InnerNet; using TOHE.Roles.Core; using static TOHE.MeetingHudStartPatch; using static TOHE.Translator; @@ -36,6 +38,22 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.ShapeshifterCooldown = SkillCooldown.GetFloat(); AURoleOptions.ShapeshifterDuration = 1f; } + private void SendRPC(byte target = byte.MaxValue) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(_Player); + writer.Write(target); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + var targetId = reader.ReadByte(); + + if (targetId == byte.MaxValue) + ForBlackmailer.Clear(); + else + ForBlackmailer.Add(targetId); + } public override bool OnCheckShapeshift(PlayerControl blackmailer, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) { if (ShowShapeshiftAnimationsOpt.GetBool() || blackmailer.PlayerId == target.PlayerId) return true; @@ -51,7 +69,7 @@ public override void OnShapeshift(PlayerControl blackmailer, PlayerControl targe DoBlackmaile(blackmailer, target); } } - private static void DoBlackmaile(PlayerControl blackmailer, PlayerControl target) + private void DoBlackmaile(PlayerControl blackmailer, PlayerControl target) { if (!target.IsAlive()) { @@ -61,6 +79,7 @@ private static void DoBlackmaile(PlayerControl blackmailer, PlayerControl target ClearBlackmaile(); ForBlackmailer.Add(target.PlayerId); + SendRPC(target.PlayerId); } public override void AfterMeetingTasks() @@ -71,7 +90,11 @@ public override void OnCoEndGame() { ClearBlackmaile(); } - private static void ClearBlackmaile() => ForBlackmailer.Clear(); + private void ClearBlackmaile() + { + ForBlackmailer.Clear(); + SendRPC(); + } public static bool CheckBlackmaile(PlayerControl player) => HasEnabled && GameStates.IsInGame && ForBlackmailer.Contains(player.PlayerId); From 17cae70236f3b2bbeb1bd6aa83503f7ac6958ea0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 23:54:23 +0800 Subject: [PATCH 610/778] Change --- Roles/Impostor/Blackmailer.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 28df642713..9e03ea29de 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -40,6 +40,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } private void SendRPC(byte target = byte.MaxValue) { + if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); writer.WriteNetObject(_Player); writer.Write(target); @@ -50,7 +51,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) var targetId = reader.ReadByte(); if (targetId == byte.MaxValue) - ForBlackmailer.Clear(); + ClearBlackmaile(true); else ForBlackmailer.Add(targetId); } @@ -77,23 +78,23 @@ private void DoBlackmaile(PlayerControl blackmailer, PlayerControl target) return; } - ClearBlackmaile(); + ClearBlackmaile(true); ForBlackmailer.Add(target.PlayerId); SendRPC(target.PlayerId); } public override void AfterMeetingTasks() { - ClearBlackmaile(); + ClearBlackmaile(true); } public override void OnCoEndGame() { - ClearBlackmaile(); + ClearBlackmaile(false); } - private void ClearBlackmaile() + private void ClearBlackmaile(bool sendRpc) { ForBlackmailer.Clear(); - SendRPC(); + if (sendRpc) SendRPC(); } public static bool CheckBlackmaile(PlayerControl player) => HasEnabled && GameStates.IsInGame && ForBlackmailer.Contains(player.PlayerId); From 60f1a29c983fd186d083f26a503d9419240cc8d0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 23 Sep 2024 23:54:46 +0800 Subject: [PATCH 611/778] Fix --- Roles/Impostor/Blackmailer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 9e03ea29de..8c01154f9b 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -51,7 +51,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) var targetId = reader.ReadByte(); if (targetId == byte.MaxValue) - ClearBlackmaile(true); + ClearBlackmaile(false); else ForBlackmailer.Add(targetId); } From 0467c9147adaafb83ebed761e14dabe029487206 Mon Sep 17 00:00:00 2001 From: Moe Date: Mon, 23 Sep 2024 22:57:48 -0400 Subject: [PATCH 612/778] Added Eavesdropper (Helpful Addon) --- Modules/OptionHolder.cs | 2 ++ Patches/MeetingHudPatch.cs | 9 ++++++ Resources/Lang/en_US.json | 8 +++++- Resources/roleColor.json | 3 +- Roles/AddOns/Common/Eavesdropper.cs | 44 +++++++++++++++++++++++++++++ main.cs | 7 +++-- 6 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 Roles/AddOns/Common/Eavesdropper.cs diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c1c4665063..94fb78089a 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1,3 +1,5 @@ +// Ignore Spelling: Adt + using System; using System.Reflection; using TOHE.Modules; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 410d98cf8a..fff861c337 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -883,6 +883,7 @@ public static void NotifyRoleSkillOnMeetingStart() SendMessage(text, sendTo, title); } }, 3f, "Skill Description First Meeting"); + } msgToSend = []; @@ -940,6 +941,14 @@ public static void NotifyRoleSkillOnMeetingStart() // Check Mimic kill if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive()) Main.AllAlivePlayerControls.Where(x => x.GetRealKiller()?.PlayerId == pc.PlayerId).Do(x => MimicMsg += $"\n{x.GetNameWithRole(true)}"); + + // Eavesdropper notify msg + if (Eavesdropper.EavesdropperNotify.ContainsKey(pc.PlayerId) && IRandom.Instance.Next(0,100) < Eavesdropper.EavesdropPercentChance.GetFloat()) + { + var eavesdropperMsg = msgToSend.Where(x => x.Item2 != 255).Select(x => x.Item1).ToList(); + var randomMsg = eavesdropperMsg[IRandom.Instance.Next(0, eavesdropperMsg.Count)]; + AddMsg(randomMsg, pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Eavesdropper), GetString("EavesdropperMsgTitle"))); + } } // Add Mimic msg diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9171d07e1d..96d835e4da 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -398,6 +398,7 @@ "DoubleAgent": "Double Agent", "Sloth": "Sloth", "Prohibited": "Prohibited", + "Eavesdropper": "Eavesdropper", "BracketAddons": "Add Brackets To Add-ons", "EngineerTOHEInfo": "Use the vents to catch the Impostors", "ScientistTOHEInfo": "Access portable vitals from anywhere", @@ -708,6 +709,7 @@ "DoubleAgentInfo": "Plant bombs on players in meetings", "SlothInfo": "You're slower", "ProhibitedInfo": "Certain vents are blocked", + "EavesdropperInfo": "Listen in on other roles", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", @@ -1018,6 +1020,7 @@ "DoubleAgentInfoLong": "(Impostor):\nAs the Double Agent, you cannot access the kill button. However, you can vote for someone in a meeting to pass a bomb onto them, which can only be done one player at a time. Once the meeting has finished, the bomb will activate and explode in a set amount of time.\nNote: when you pass the bomb onto someone in a meeting, you can vote afterward.\n\nAdditionally depending on settings the Double Agent can diffuse Bastion and Agitator bombs when venting.\n\nThe Double Agent can change roles when they are the Last Imposter, depending on the settings the role can be a Admired Impostor, Trickster, Traitor, or stay as the Double Agent.", "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have some vents blocked for use.", + "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", @@ -3774,5 +3777,8 @@ "MinionAbilityTime": "Ability Duration", "Minion_Blind": "blinded", - "Evader_ChanceNotExiled": "Chance not be exiled" + "Evader_ChanceNotExiled": "Chance not be exiled", + + "EavesdropperMsgTitle": "You found a secret", + "EavesdropPercentChance": "Chance to eavesdrop" } diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 67ba36d93f..92684cb179 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -246,5 +246,6 @@ "Glow": "#E2F147", "Radar": "#1eff1e", "Rebirth": "#f08c22", - "Sloth": "#376db8" + "Sloth": "#376db8", + "Eavesdropper": "#ffe6bf" } diff --git a/Roles/AddOns/Common/Eavesdropper.cs b/Roles/AddOns/Common/Eavesdropper.cs new file mode 100644 index 0000000000..790a67100d --- /dev/null +++ b/Roles/AddOns/Common/Eavesdropper.cs @@ -0,0 +1,44 @@ +using static TOHE.Options; +using static TOHE.Roles.AddOns.Common.Sloth; + +namespace TOHE.Roles.AddOns.Common; + +public class Eavesdropper : IAddon +{ + public const int Id = 29900; + public static readonly HashSet IsActive = []; + public static bool IsEnable = false; + public AddonTypes Type => AddonTypes.Helpful; + private static readonly HashSet playerList = []; + + public static OptionItem ImpCanBeEavesdropper; + public static OptionItem CrewCanBeEavesdropper; + public static OptionItem NeutralCanBeEavesDropper; + + public static readonly Dictionary EavesdropperNotify = []; + + public static OptionItem EavesdropPercentChance; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(Id, CustomRoles.Eavesdropper, canSetNum: true, teamSpawnOptions: true); + EavesdropPercentChance = FloatOptionItem.Create(Id + 10, "EavesdropPercentChance", new(5f, 100f, 5f), 50f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Eavesdropper]) + .SetValueFormat(OptionFormat.Percent); + } + + public void Init() + { + IsEnable = false; + playerList.Clear(); + } + + public void Add(byte playerId, bool gameIsLoading = true) + { } + public void Remove(byte playerId) + { + playerList.Remove(playerId); + + if (!playerList.Any()) + IsEnable = false; + } +} diff --git a/main.cs b/main.cs index 7ee098b364..203449966b 100644 --- a/main.cs +++ b/main.cs @@ -1,3 +1,5 @@ +// Ignore Spelling: Auth Plugin + using AmongUs.GameOptions; using BepInEx; using BepInEx.Configuration; @@ -42,8 +44,8 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0923.210.00140"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 14"; + public const string PluginVersion = "2024.0923.210.00150"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 15"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ @@ -891,6 +893,7 @@ public enum CustomRoles Cyber, Diseased, DoubleShot, + Eavesdropper, Egoist, Evader, EvilSpirit, From 03c66da710e7e7527046e22f01d3b93858f65a57 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:20:49 -0400 Subject: [PATCH 613/778] Fix typos --- Resources/Lang/en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 96d835e4da..d72af7fe63 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -908,7 +908,7 @@ "CursedSoulInfoLong": "(Neutrals):\nAs the Cursed Soul, you steal the victory if you survive to the end of the game.\n\nYou can steal the win from a Jester or Executioner.\n\nAdditionally, you can steal the souls of other players.\nSoulless players win with you and count as dead.", "PickpocketInfoLong": "(Neutrals):\nAs the Pickpocket, you steal votes from your kills.\n\nKill everyone to win.", "TraitorInfoLong": "(Neutrals):\nAs the Traitor, you were an Impostor that betrayed the Impostors.\nYou know the Impostors, but they don't know you.\nThe twist? They can kill you, but you can't kill them.\n\nEliminate the Impostors by other means, then kill everyone else to win!", - "TrollerInfoLong": "(Neutrals):\nAs a Troller, you can complete tasks so that random events can happen to players.\nFor example, changing the speed of all players, teleportation, influencing sabotage, etc.\nAlso you can wins with the winner team.", + "TrollerInfoLong": "(Neutrals):\nAs a Troller, you can complete tasks so that random events can happen to players.\nFor example, changing the speed of all players, teleportation, influencing sabotage, etc.\nAlso you can win with the winning team.", "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", @@ -1019,7 +1019,7 @@ "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your Deeds!", "DoubleAgentInfoLong": "(Impostor):\nAs the Double Agent, you cannot access the kill button. However, you can vote for someone in a meeting to pass a bomb onto them, which can only be done one player at a time. Once the meeting has finished, the bomb will activate and explode in a set amount of time.\nNote: when you pass the bomb onto someone in a meeting, you can vote afterward.\n\nAdditionally depending on settings the Double Agent can diffuse Bastion and Agitator bombs when venting.\n\nThe Double Agent can change roles when they are the Last Imposter, depending on the settings the role can be a Admired Impostor, Trickster, Traitor, or stay as the Double Agent.", "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", - "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have some vents blocked for use.", + "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", "ShowTextOverlay": "Text Overlay", From 963639a4ea0e63fbf45ac878bfa84c5c82d582c0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 24 Sep 2024 12:43:03 +0800 Subject: [PATCH 614/778] Fix Eavesdropper --- Modules/OptionHolder.cs | 2 +- Patches/MeetingHudPatch.cs | 13 ++++-------- Roles/AddOns/Common/Eavesdropper.cs | 32 ++++++++++++++++++++++------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 94fb78089a..377ead5f1b 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -624,7 +624,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 30000 last id for roles/add-ons (Next use 30100) + // 30100 last id for roles/add-ons (Next use 30200) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index fff861c337..7e0ebf83fa 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -941,17 +941,12 @@ public static void NotifyRoleSkillOnMeetingStart() // Check Mimic kill if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive()) Main.AllAlivePlayerControls.Where(x => x.GetRealKiller()?.PlayerId == pc.PlayerId).Do(x => MimicMsg += $"\n{x.GetNameWithRole(true)}"); - - // Eavesdropper notify msg - if (Eavesdropper.EavesdropperNotify.ContainsKey(pc.PlayerId) && IRandom.Instance.Next(0,100) < Eavesdropper.EavesdropPercentChance.GetFloat()) - { - var eavesdropperMsg = msgToSend.Where(x => x.Item2 != 255).Select(x => x.Item1).ToList(); - var randomMsg = eavesdropperMsg[IRandom.Instance.Next(0, eavesdropperMsg.Count)]; - AddMsg(randomMsg, pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Eavesdropper), GetString("EavesdropperMsgTitle"))); - } } - // Add Mimic msg + if (Eavesdropper.IsEnable) + Eavesdropper.GetMessage(); + + // Add Mimic msg if (MimicMsg != "") { MimicMsg = GetString("MimicDeadMsg") + "\n" + MimicMsg; diff --git a/Roles/AddOns/Common/Eavesdropper.cs b/Roles/AddOns/Common/Eavesdropper.cs index 790a67100d..9816f12e2f 100644 --- a/Roles/AddOns/Common/Eavesdropper.cs +++ b/Roles/AddOns/Common/Eavesdropper.cs @@ -1,22 +1,18 @@ using static TOHE.Options; -using static TOHE.Roles.AddOns.Common.Sloth; namespace TOHE.Roles.AddOns.Common; public class Eavesdropper : IAddon { - public const int Id = 29900; - public static readonly HashSet IsActive = []; + public const int Id = 30100; + private static readonly HashSet playerList = []; public static bool IsEnable = false; public AddonTypes Type => AddonTypes.Helpful; - private static readonly HashSet playerList = []; public static OptionItem ImpCanBeEavesdropper; public static OptionItem CrewCanBeEavesdropper; public static OptionItem NeutralCanBeEavesDropper; - public static readonly Dictionary EavesdropperNotify = []; - public static OptionItem EavesdropPercentChance; public void SetupCustomOption() @@ -33,7 +29,10 @@ public void Init() } public void Add(byte playerId, bool gameIsLoading = true) - { } + { + playerList.Add(playerId); + IsEnable = true; + } public void Remove(byte playerId) { playerList.Remove(playerId); @@ -41,4 +40,23 @@ public void Remove(byte playerId) if (!playerList.Any()) IsEnable = false; } + public static void GetMessage() + { + foreach (var eavesdropperId in playerList) + { + if (IRandom.Instance.Next(0, 100) < EavesdropPercentChance.GetFloat()) + { + // Get all specific msg + var eavesdropperMsg = MeetingHudStartPatch.msgToSend.Where(x => x.Item2 != 255).Select(x => x.Item1).ToList(); + + // Check any data + if (eavesdropperMsg.Any()) + { + // Get random message and send Eavesdropper + var randomMsg = eavesdropperMsg.RandomElement(); + MeetingHudStartPatch.AddMsg(randomMsg, eavesdropperId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eavesdropper), Translator.GetString("EavesdropperMsgTitle"))); + } + } + } + } } From 499654a66b13acb8e022d50fb80702e6bea0a70b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 24 Sep 2024 12:43:29 +0800 Subject: [PATCH 615/778] hm --- Patches/MeetingHudPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 7e0ebf83fa..6f5ca2e7c1 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -946,7 +946,7 @@ public static void NotifyRoleSkillOnMeetingStart() if (Eavesdropper.IsEnable) Eavesdropper.GetMessage(); - // Add Mimic msg + // Add Mimic msg if (MimicMsg != "") { MimicMsg = GetString("MimicDeadMsg") + "\n" + MimicMsg; From 8f9e9f6bddfd02574d3548ec7e832c59ed5cb147 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 24 Sep 2024 13:02:59 +0800 Subject: [PATCH 616/778] Recode label Id (Thanks D1GQ) --- Modules/GuessManager.cs | 40 ----------------------------------- Patches/CheckGameEndPatch.cs | 2 -- Patches/ClientOptionsPatch.cs | 12 ----------- Patches/DialogueBoxPatch.cs | 6 ------ Patches/HudPatch.cs | 19 ----------------- Patches/MeetingHudPatch.cs | 16 ++++++++++++++ Patches/PlayerControlPatch.cs | 16 -------------- 7 files changed, 16 insertions(+), 95 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index a4972e4957..f9d118f8e5 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -1048,8 +1048,6 @@ public static void Postfix(MeetingHud __instance) { return; } - - DestroyIDLabels(); UnityEngine.Object.Destroy(textTemplate.gameObject); } } @@ -1075,42 +1073,4 @@ public static void ReceiveRPC(MessageReader reader, PlayerControl pc) GuesserMsg(pc, $"/bt {PlayerId} {GetString(role.ToString())}", true); } - - // From EHR (By Gurge44 https://github.com/Gurge44/EndlessHostRoles) - private static List IDPanels = []; - public static void CreateIDLabels(MeetingHud __instance) - { - DestroyIDLabels(); - if (__instance == null) return; - const int max = 2; - foreach (var pva in __instance.playerStates) - { - if (pva == null) continue; - var levelDisplay = pva.transform.FindChild("PlayerLevel").gameObject; - var panel = UnityEngine.Object.Instantiate(levelDisplay, pva.transform, true); - panel.gameObject.name = "PlayerIDLabel"; - var panelTransform = panel.transform; - var background = panel.GetComponent(); - background.color = Palette.Purple; - background.sortingOrder = max - 1; - panelTransform.SetAsFirstSibling(); - panelTransform.localPosition = new(-1.21f, -0.15f, 0f); - var levelLabel = panelTransform.FindChild("LevelLabel").GetComponents()[0]; - levelLabel.DestroyTranslator(); - levelLabel.text = "ID"; - levelLabel.sortingOrder = max; - levelLabel.gameObject.name = "IDLabel"; - var levelNumber = panelTransform.FindChild("LevelNumber").GetComponent(); - levelNumber.text = pva.TargetPlayerId.ToString(); - levelNumber.sortingOrder = max; - levelNumber.gameObject.name = "IDNumber"; - IDPanels.Add(panel); - } - } - - public static void DestroyIDLabels() - { - IDPanels.ForEach(UnityEngine.Object.Destroy); - IDPanels = []; - } } \ No newline at end of file diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 3e73fde203..b91ac1f29e 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -80,8 +80,6 @@ public static bool Prefix() // Update all Notify Roles Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); - GuessManager.DestroyIDLabels(); - Logger.Info("Start end game", "CheckEndCriteria.Prefix"); if (reason == GameOverReason.ImpostorBySabotage && (CustomRoles.Jackal.RoleExist() || CustomRoles.Sidekick.RoleExist()) && Jackal.CanWinBySabotageWhenNoImpAlive.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.GetCustomRole().IsImpostorTeam())) diff --git a/Patches/ClientOptionsPatch.cs b/Patches/ClientOptionsPatch.cs index 06a226e726..e43e5b6661 100644 --- a/Patches/ClientOptionsPatch.cs +++ b/Patches/ClientOptionsPatch.cs @@ -146,23 +146,11 @@ static void SwitchVanillaButtonToggle() #endif } } -[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] -public static class OptionsMenuBehaviourOpenPatch -{ - public static void Postfix() - { - if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) - GuessManager.DestroyIDLabels(); - } -} [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] public static class OptionsMenuBehaviourClosePatch { public static void Postfix() { ClientOptionItem.CustomBackground?.gameObject.SetActive(false); - - if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) - GuessManager.CreateIDLabels(MeetingHud.Instance); } } \ No newline at end of file diff --git a/Patches/DialogueBoxPatch.cs b/Patches/DialogueBoxPatch.cs index dd18029ea8..8c3f648f87 100644 --- a/Patches/DialogueBoxPatch.cs +++ b/Patches/DialogueBoxPatch.cs @@ -21,9 +21,6 @@ public static void Show_Postfix(DialogueBox __instance, string dialogue) { PlayerControl.LocalPlayer.ForceKillTimerContinue = true; } - - if (GameStates.IsMeeting) - GuessManager.DestroyIDLabels(); } [HarmonyPatch(nameof(DialogueBox.Hide)), HarmonyPostfix] @@ -33,9 +30,6 @@ public static void Hide_Postfix(DialogueBox __instance) { PlayerControl.LocalPlayer.ForceKillTimerContinue = false; } - - if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) - GuessManager.CreateIDLabels(MeetingHud.Instance); } /* diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 8539314dba..b05864299c 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -264,25 +264,6 @@ public static void Prefix(ref MapOptions opts) } } } -[HarmonyPatch(typeof(MapTaskOverlay), nameof(MapTaskOverlay.Show))] -static class MapTaskOverlayShowPatch -{ - public static void Postfix() - { - if (GameStates.IsMeeting) - GuessManager.DestroyIDLabels(); - } -} - -[HarmonyPatch(typeof(MapTaskOverlay), nameof(MapTaskOverlay.Hide))] -static class MapTaskOverlayHidePatch -{ - public static void Postfix() - { - if (GameStates.IsMeeting && !DestroyableSingleton.Instance.Chat.IsOpenOrOpening) - GuessManager.CreateIDLabels(MeetingHud.Instance); - } -} [HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.SetTaskText))] class TaskPanelBehaviourPatch { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 6f5ca2e7c1..741a7a7134 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using TMPro; using System; using System.Text; using TOHE.Roles.AddOns.Common; @@ -1028,6 +1029,21 @@ public static void Postfix(MeetingHud __instance) deathReasonText.enableWordWrapping = false; deathReasonText.enabled = PlayerControl.LocalPlayer.KnowDeathReason(pc); + // Thanks BAU (By D1GQ) + var PlayerLevel = pva.transform.Find("PlayerLevel"); + var LevelDisplay = UnityEngine.Object.Instantiate(PlayerLevel, pva.transform); + LevelDisplay.localPosition = new Vector3(-1.21f, -0.15f, PlayerLevel.transform.localPosition.z); + LevelDisplay.transform.SetSiblingIndex(pva.transform.Find("PlayerLevel").GetSiblingIndex() + 1); + LevelDisplay.gameObject.name = "PlayerId"; + LevelDisplay.GetComponent().color = Palette.Purple; + var IdLabel = LevelDisplay.transform.Find("LevelLabel"); + var IdNumber = LevelDisplay.transform.Find("LevelNumber"); + UnityEngine.Object.Destroy(IdLabel.GetComponent()); + IdLabel.GetComponent().text = "ID"; + IdNumber.GetComponent().text = pva.TargetPlayerId.ToString(); + IdLabel.name = "IdLabel"; + IdNumber.name = "IdNumber"; + var myRole = PlayerControl.LocalPlayer.GetRoleClass(); var enable = true; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b6b268b2ed..e99e436ee2 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -938,7 +938,6 @@ class FixedUpdateInNormalGamePatch private static readonly StringBuilder Suffix = new(120); private static readonly Dictionary BufferTime = []; private static int LevelKickBufferTime = 20; - private static bool ChatOpen; public static async void Postfix(PlayerControl __instance) { @@ -962,21 +961,6 @@ public static async void Postfix(PlayerControl __instance) } } - if (GameStates.IsMeeting) - { - switch (ChatOpen) - { - case false when DestroyableSingleton.Instance.Chat.IsOpenOrOpening: - ChatOpen = true; - break; - case true when DestroyableSingleton.Instance.Chat.IsClosedOrClosing: - ChatOpen = false; - if (GameStates.IsVoting) - GuessManager.CreateIDLabels(MeetingHud.Instance); - break; - } - } - try { await DoPostfix(__instance); From db67b6088699f55e15c1bfd57b4b2313c1260f4c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 24 Sep 2024 13:05:52 +0800 Subject: [PATCH 617/778] hm --- Modules/OptionHolder.cs | 2 -- main.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 377ead5f1b..edc3e409b4 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1,5 +1,3 @@ -// Ignore Spelling: Adt - using System; using System.Reflection; using TOHE.Modules; diff --git a/main.cs b/main.cs index 203449966b..ce097ccbc8 100644 --- a/main.cs +++ b/main.cs @@ -1,5 +1,3 @@ -// Ignore Spelling: Auth Plugin - using AmongUs.GameOptions; using BepInEx; using BepInEx.Configuration; From 9de9eadf99b11cf6da72ba437ab2d6c58489f419 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 15:40:22 +0800 Subject: [PATCH 618/778] Fix bugs --- Modules/ExtendedPlayerControl.cs | 7 ---- Modules/Utils.cs | 64 +++++++++++++++++-------------- Patches/LobbyPatch.cs | 9 ++++- Patches/PlayerControlPatch.cs | 2 +- Patches/PlayerJoinAndLeftPatch.cs | 2 + Patches/SabotageSystemPatch.cs | 2 +- Roles/Crewmate/CopyCat.cs | 2 +- Roles/Crewmate/Mortician.cs | 54 ++++++++++++-------------- Roles/Impostor/Chronomancer.cs | 16 +++++--- Roles/Impostor/Hangman.cs | 2 +- Roles/Impostor/Swooper.cs | 8 ++-- Roles/Neutral/Pirate.cs | 60 +++++++++++++++-------------- Roles/Neutral/SoulCollector.cs | 5 ++- Roles/Neutral/Wraith.cs | 2 +- 14 files changed, 123 insertions(+), 112 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f2f1d02e40..acddd0dcda 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1103,13 +1103,6 @@ public static void ResetKillCooldown(this PlayerControl player) if (!player.HasImpKillButton(considerVanillaShift: false)) Main.AllPlayerKillCooldown[player.PlayerId] = 300f; - if (player.GetRoleClass() is Chronomancer ch) - { - ch.realcooldown = Main.AllPlayerKillCooldown[player.PlayerId]; - ch.SetCooldown(); - } - - if (Main.AllPlayerKillCooldown[player.PlayerId] == 0) { Main.AllPlayerKillCooldown[player.PlayerId] = 0.3f; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 2b7cf4acb8..150db58779 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1860,7 +1860,7 @@ public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => private static readonly StringBuilder TargetMark = new(20); public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro || OnPlayerLeftPatch.StartingProcessing) return; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro) return; if (Main.MeetingIsStarted && !isForMeeting) return; if (Main.AllPlayerControls == null) return; @@ -1877,7 +1877,7 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon } public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro || OnPlayerLeftPatch.StartingProcessing) return Task.CompletedTask; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro) return Task.CompletedTask; if (Main.MeetingIsStarted && !isForMeeting) return Task.CompletedTask; if (Main.AllPlayerControls == null) return Task.CompletedTask; @@ -1911,8 +1911,8 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl // Do nothing when the seer is not present in the game if (seer == null) continue; - // Only non-modded players - if (seer.IsModded()) continue; + // Only non-modded players or player left + if (seer.IsModded() || seer.PlayerId == OnPlayerLeftPatch.LeftPlayerId || seer.Data.Disconnected) continue; // Size of player roles string fontSize = isForMeeting ? "1.6" : "1.8"; @@ -2064,7 +2064,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl foreach (var realTarget in targetList) { // if the target is the seer itself, do nothing - if (realTarget == null || (realTarget.PlayerId == seer.PlayerId)) continue; + if (realTarget == null || (realTarget.PlayerId == seer.PlayerId) || realTarget.PlayerId == OnPlayerLeftPatch.LeftPlayerId || realTarget.Data.Disconnected) continue; var target = realTarget; @@ -2343,39 +2343,45 @@ var Breason when BannedReason(Breason) => false, } public static void AfterMeetingTasks() { - ChatManager.ClearLastSysMsg(); - FallFromLadder.Reset(); - - if (Diseased.IsEnable) Diseased.AfterMeetingTasks(); - if (Antidote.IsEnable) Antidote.AfterMeetingTasks(); - - AntiBlackout.AfterMeetingTasks(); - - foreach (var playerState in Main.PlayerStates.Values.ToArray()) + try { - if (playerState.RoleClass == null) continue; + ChatManager.ClearLastSysMsg(); + FallFromLadder.Reset(); - playerState.RoleClass.AfterMeetingTasks(); - playerState.RoleClass.HasVoted = false; - } + if (Diseased.IsEnable) Diseased.AfterMeetingTasks(); + if (Antidote.IsEnable) Antidote.AfterMeetingTasks(); - //Set kill timer - foreach (var player in Main.AllAlivePlayerControls) - { - player.SetKillTimer(); + AntiBlackout.AfterMeetingTasks(); - if (player.Is(CustomRoles.Prohibited)) + foreach (var playerState in Main.PlayerStates.Values.ToArray()) { - Prohibited.AfterMeetingTasks(player.PlayerId); + if (playerState.RoleClass == null) continue; + + playerState.RoleClass.AfterMeetingTasks(); + playerState.RoleClass.HasVoted = false; } - } - if (Statue.IsEnable) Statue.AfterMeetingTasks(); - if (Burst.IsEnable) Burst.AfterMeetingTasks(); + //Set kill timer + foreach (var player in Main.AllAlivePlayerControls) + { + player.SetKillTimer(); - if (CustomRoles.CopyCat.HasEnabled()) CopyCat.UnAfterMeetingTasks(); // All crew hast to be before this - + if (player.Is(CustomRoles.Prohibited)) + { + Prohibited.AfterMeetingTasks(player.PlayerId); + } + } + if (Statue.IsEnable) Statue.AfterMeetingTasks(); + if (Burst.IsEnable) Burst.AfterMeetingTasks(); + + if (CustomRoles.CopyCat.HasEnabled()) CopyCat.UnAfterMeetingTasks(); // All crew hast to be before this + } + catch (Exception error) + { + Logger.Error($"Error after meeting: {error}", "AfterMeetingTasks"); + } + if (Options.AirshipVariableElectrical.GetBool()) AirshipElectricalDoors.Initialize(); diff --git a/Patches/LobbyPatch.cs b/Patches/LobbyPatch.cs index c996af0406..814abd033b 100644 --- a/Patches/LobbyPatch.cs +++ b/Patches/LobbyPatch.cs @@ -91,9 +91,14 @@ public static void Update_Postfix(LobbyBehaviour __instance) public static class HostInfoPanelUpdatePatch { private static TextMeshPro HostText; - public static void Postfix(HostInfoPanel __instance) + public static bool Prefix(HostInfoPanel __instance) { - if (AmongUsClient.Instance.AmHost) + // sometimse AU get exeption "System.IndexOutOfRangeException: Index was outside the bounds of the array" + return __instance != null && GameStates.IsLobby && __instance.player.ColorId > 0 && __instance.player.ColorId != 255 && __instance.player.ColorId != int.MaxValue; + } + public static void Postfix(HostInfoPanel __instance, bool __runOriginal) + { + if (AmongUsClient.Instance.AmHost && __runOriginal) { if (HostText == null) HostText = __instance.content.transform.FindChild("Name").GetComponent(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e99e436ee2..11cf5cf7bf 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -442,7 +442,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player } target.Data.IsDead = true; - GameData.Instance.DirtyAllData(); + target.Data.MarkDirty(); Main.MurderedThisRound.Add(target.PlayerId); diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 29fbe300a8..d6dd8b2c3f 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -303,9 +303,11 @@ public static void Postfix(/*AmongUsClient __instance,*/ [HarmonyArgument(0)] Cl class OnPlayerLeftPatch { public static bool StartingProcessing = false; + public static byte LeftPlayerId; static void Prefix([HarmonyArgument(0)] ClientData data) { StartingProcessing = true; + LeftPlayerId = data.Character.PlayerId; if (GameStates.IsInGame) { diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index d84bde71ad..6db11ec7bf 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -331,7 +331,7 @@ private static bool CanSabotage(PlayerControl player, SystemTypes systemType) public static void Postfix(SabotageSystemType __instance, bool __runOriginal) { // __runOriginal - the result that was returned from Prefix - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || !(isCooldownModificationEnabled && __runOriginal)) + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || !__runOriginal || !isCooldownModificationEnabled) { return; } diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 3699a5852f..26cba176c5 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -56,7 +56,7 @@ public static void UnAfterMeetingTasks() foreach (var playerId in playerIdList.ToArray()) { var pc = playerId.GetPlayer(); - if (!pc.IsAlive()) + if (pc != null && !pc.IsAlive()) { if (!pc.HasGhostRole()) { diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index 95b8238b9a..cd269cb1d8 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -18,7 +18,6 @@ internal class Mortician : RoleBase private static OptionItem ShowArrows; - private static readonly Dictionary lastPlayerName = []; private static readonly Dictionary msgToSend = []; public override void SetupCustomOption() @@ -29,7 +28,6 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - lastPlayerName.Clear(); msgToSend.Clear(); } public override void Add(byte playerId) @@ -49,24 +47,23 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe { if (inMeeting || target.IsDisconnected()) return; - Vector2 pos = target.transform.position; - float minDis = float.MaxValue; - string minName = ""; - foreach (var pc in Main.AllAlivePlayerControls) - { - if (pc.PlayerId == target.PlayerId || playerIdList.Any(p => p == pc.PlayerId)) continue; - var dis = Utils.GetDistance(pc.transform.position, pos); - if (dis < minDis && dis < 1.5f) - { - minDis = dis; - minName = pc.GetRealName(clientData: true); - } - } + //Vector2 pos = target.transform.position; + //float minDis = float.MaxValue; + //string minName = ""; + //foreach (var pc in Main.AllAlivePlayerControls) + //{ + // if (pc.PlayerId == target.PlayerId || playerIdList.Any(p => p == pc.PlayerId)) continue; + // var dis = Utils.GetDistance(pc.transform.position, pos); + // if (dis < minDis && dis < 0.5f) + // { + // minDis = dis; + // minName = pc.GetRealName(clientData: true); + // } + //} - lastPlayerName.TryAdd(target.PlayerId, minName); foreach (var pc in playerIdList.ToArray()) { - var player = Utils.GetPlayerById(pc); + var player = pc.GetPlayer(); if (player == null || !player.IsAlive()) continue; LocateArrow.Add(pc, target.transform.position); } @@ -77,24 +74,23 @@ public override void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo targ { LocateArrow.RemoveAllTarget(apc); } + if (pc == null || target == null || !pc.Is(CustomRoles.Mortician) || pc.PlayerId == target.PlayerId) return; + + string name = string.Empty; + var killer = target.PlayerId.GetRealKillerById(); + if (killer == null) + { + name = killer.GetRealName(); + } - if (pc == null || target == null || target.Object == null || !pc.Is(CustomRoles.Mortician) || pc.PlayerId == target.PlayerId) return; - lastPlayerName.TryGetValue(target.PlayerId, out var name); - if (name == "") msgToSend.TryAdd(pc.PlayerId, string.Format(GetString("MorticianGetNoInfo"), target.PlayerName)); + if (name == string.Empty) msgToSend.TryAdd(pc.PlayerId, string.Format(GetString("MorticianGetNoInfo"), target.PlayerName)); else msgToSend.TryAdd(pc.PlayerId, string.Format(GetString("MorticianGetInfo"), target.PlayerName, name)); } public override string GetSuffix(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) { - if (isForMeeting) return string.Empty; + if (!ShowArrows.GetBool() || isForMeeting || seer.PlayerId != target.PlayerId) return string.Empty; - if (ShowArrows.GetBool()) - { - if (!seer.Is(CustomRoles.Mortician)) return ""; - if (target != null && seer.PlayerId != target.PlayerId) return ""; - if (GameStates.IsMeeting) return ""; - return Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); - } - else return ""; + return Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); } public override void OnMeetingHudStart(PlayerControl pc) { diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index 506dfc3339..a1d221dbe5 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -100,23 +100,29 @@ private string GetCharge() return sb.ToString(); } - public void SetCooldown() + public override void SetKillCooldown(byte id) { if (IsInMassacre) { - Main.AllPlayerKillCooldown[_state.PlayerId] = 0.1f; + Main.AllPlayerKillCooldown[id] = 0.1f; } else { - Main.AllPlayerKillCooldown[_state.PlayerId] = realcooldown; + Main.AllPlayerKillCooldown[id] = realcooldown; } - _Player.SyncSettings(); + _Player?.SyncSettings(); } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { ChargedTime = 0; IsInMassacre = false; - _Player?.MarkDirtySettings(); + _Player?.ResetKillCooldown(); + } + public override void AfterMeetingTasks() + { + ChargedTime = 0; + IsInMassacre = false; + _Player?.ResetKillCooldown(); } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { diff --git a/Roles/Impostor/Hangman.cs b/Roles/Impostor/Hangman.cs index aaa9967700..62f28b34e5 100644 --- a/Roles/Impostor/Hangman.cs +++ b/Roles/Impostor/Hangman.cs @@ -38,7 +38,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr if (target.Is(CustomRoles.NiceMini) && Mini.Age < 18) return true; - if (target.IsTransformedNeutralApocalypse()) + if (target.IsTransformedNeutralApocalypse() || target.Is(CustomRoles.Solsticer)) return true; if (target.Is(CustomRoles.Madmate) && !Madmate.ImpCanKillMadmate.GetBool()) diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs index 85de85808a..de0de9c31d 100644 --- a/Roles/Impostor/Swooper.cs +++ b/Roles/Impostor/Swooper.cs @@ -46,11 +46,11 @@ public override void Add(byte playerId) } private void SendRPC(PlayerControl pc) { - if (pc.IsHost()) return; + if (!pc.IsNonHostModdedClient()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, pc.GetClientId()); writer.WriteNetObject(_Player); - writer.Write((InvisCooldown.TryGetValue(pc.PlayerId, out var x) ? x : -1).ToString()); - writer.Write((InvisDuration.TryGetValue(pc.PlayerId, out var y) ? y : -1).ToString()); + writer.Write(InvisCooldown.GetValueOrDefault(pc.PlayerId, -1).ToString()); + writer.Write(InvisDuration.GetValueOrDefault(pc.PlayerId, -1).ToString()); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) @@ -60,7 +60,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) long cooldown = long.Parse(reader.ReadString()); long invis = long.Parse(reader.ReadString()); if (cooldown > 0) InvisCooldown.Add(PlayerControl.LocalPlayer.PlayerId, cooldown); - if (invis > 0) InvisCooldown.Add(PlayerControl.LocalPlayer.PlayerId, invis); + if (invis > 0) InvisDuration.Add(PlayerControl.LocalPlayer.PlayerId, invis); } private static bool CanGoInvis(byte id) diff --git a/Roles/Neutral/Pirate.cs b/Roles/Neutral/Pirate.cs index eccd1cf483..113a677445 100644 --- a/Roles/Neutral/Pirate.cs +++ b/Roles/Neutral/Pirate.cs @@ -32,7 +32,7 @@ internal class Pirate : RoleBase public override void SetupCustomOption() { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pirate); + Options.SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pirate); DuelCooldown = FloatOptionItem.Create(Id + 12, "DuelCooldown", new(0f, 180f, 2.5f), 22.5f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pirate]) .SetValueFormat(OptionFormat.Seconds); TryHideMsg = BooleanOptionItem.Create(Id + 10, "PirateTryHideMsg", true, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pirate]) @@ -51,7 +51,7 @@ public override void Init() } public override void Add(byte playerId) { - DuelDone.Add(playerId, false); + DuelDone[playerId] = false; } public override void MeetingHudClear() { @@ -113,7 +113,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Logger.Msg($"{killer.GetNameWithRole()} chose a target {target.GetNameWithRole()}", "Pirate"); PirateTarget = target.PlayerId; SendRPC(operate: 0, target: target.PlayerId, points: -1); - DuelDone.Add(PirateTarget, false); + DuelDone[PirateTarget] = false; if (!Options.DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(killer); else killer.SetKillCooldown(); return false; @@ -122,43 +122,43 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.KillButton.OverrideText(GetString("PirateDuelButtonText")); } + public override Sprite GetKillButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Challenge"); public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isMeeting = false) + => isMeeting && target.PlayerId == PirateTarget ? ColorString(GetRoleColor(CustomRoles.Pirate), " ⦿") : string.Empty; + + public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (target != null && isMeeting && target.PlayerId == PirateTarget) - { - return ColorString(GetRoleColor(CustomRoles.Pirate), " ⦿"); - } - return ""; - } - public override void AfterMeetingTasks() - { + if (_Player == null || PirateTarget == byte.MaxValue || deathReason != PlayerState.DeathReason.Vote) return; + var pirateId = _state.PlayerId; - if (PirateTarget != byte.MaxValue) + if (!DuelDone[pirateId]) return; + + var pirateTarget = PirateTarget.GetPlayer(); + if (DuelDone[PirateTarget]) { - if (DuelDone[pirateId]) + if (targetChose == pirateChose) { - if (DuelDone[PirateTarget]) - { - if (targetChose == pirateChose) - { - NumWin++; - if (GetPlayerById(PirateTarget).IsAlive()) - { - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Pirate, PirateTarget); - GetPlayerById(PirateTarget).SetRealKiller(GetPlayerById(pirateId)); - } - } - } - else - if (GetPlayerById(PirateTarget).IsAlive()) + NumWin++; + if (pirateTarget.IsAlive()) { CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Pirate, PirateTarget); - GetPlayerById(PirateTarget).SetRealKiller(GetPlayerById(pirateId)); + pirateTarget.SetRealKiller(_Player); } } } + else if (pirateTarget.IsAlive()) + { + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Pirate, PirateTarget); + pirateTarget.SetRealKiller(_Player); + } + } + public override void AfterMeetingTasks() + { + if (_Player == null) return; + var pirateId = _state.PlayerId; + if (NumWin >= SuccessfulDuelsToWin.GetInt()) { NumWin = SuccessfulDuelsToWin.GetInt(); @@ -168,10 +168,12 @@ public override void AfterMeetingTasks() CustomWinnerHolder.WinnerIds.Add(pirateId); } } + DuelDone.Clear(); PirateTarget = byte.MaxValue; + SendRPC(operate: 1, target: byte.MaxValue, points: NumWin); - foreach (byte playerId in Main.PlayerStates.Values.Where(x => x.MainRole == CustomRoles.Pirate).Select(x => x.PlayerId)) { DuelDone.Add(playerId, false); } + foreach (byte playerId in Main.PlayerStates.Values.Where(x => x.MainRole == CustomRoles.Pirate).Select(x => x.PlayerId)) { DuelDone[playerId] = false; } } public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index e1cdf706ca..79a26b4291 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -215,12 +215,13 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl } public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) { - var sc = Utils.GetPlayerListByRole(CustomRoles.Death).First(); + if (exiled == null) return; + var sc = Utils.GetPlayerListByRole(CustomRoles.Death).FirstOrDefault(); if (sc == null || !sc.IsAlive() || sc.Data.Disconnected) return; if (isMeetingHud) { - if (exiled == sc.Data) + if (exiled.PlayerId == sc.PlayerId) { name = string.Format(GetString("ExiledDeath"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); } diff --git a/Roles/Neutral/Wraith.cs b/Roles/Neutral/Wraith.cs index 464a41515c..e71aac3270 100644 --- a/Roles/Neutral/Wraith.cs +++ b/Roles/Neutral/Wraith.cs @@ -48,7 +48,7 @@ public override void Init() } private void SendRPC(PlayerControl pc) { - if (pc.IsHost()) return; + if (!pc.IsNonHostModdedClient()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, pc.GetClientId()); writer.WriteNetObject(_Player);//SetWraithTimer writer.Write((InvisTime.TryGetValue(pc.PlayerId, out var x) ? x : -1).ToString()); From fbe0f59abe69182861036facf3b8d6ac1d7e5313 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 21:02:42 +0800 Subject: [PATCH 619/778] SendOption.Reliable --- Modules/ExtendedPlayerControl.cs | 2 +- Modules/Utils.cs | 2 +- Patches/onGameStartedPatch.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index acddd0dcda..c6dc8d7c9e 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -60,7 +60,7 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role,/* player.SetRole(role, true); return; } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, clientId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); writer.Write((ushort)role); writer.Write(true); AmongUsClient.Instance.FinishRpcImmediately(writer); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 150db58779..7ff15baff0 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2237,7 +2237,7 @@ public static void SendGameData() { foreach (var playerinfo in GameData.Instance.AllPlayers) { - MessageWriter writer = MessageWriter.Get(); + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); writer.StartMessage(5); //0x05 GameData writer.Write(AmongUsClient.Instance.GameId); { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index e1dc9ad1c6..9d585c82b5 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -676,7 +676,7 @@ public static void RpcSetDisconnected(bool disconnected) playerInfo.IsDead = data; } - var stream = MessageWriter.Get(SendOption.None); + var stream = MessageWriter.Get(SendOption.Reliable); stream.StartMessage(5); stream.Write(AmongUsClient.Instance.GameId); { From 6873e3a444583719a58f2575611c76d1d57f3567 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 21:05:06 +0800 Subject: [PATCH 620/778] Change --- Modules/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 7ff15baff0..f8a43b0702 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2237,7 +2237,7 @@ public static void SendGameData() { foreach (var playerinfo in GameData.Instance.AllPlayers) { - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + MessageWriter writer = MessageWriter.Get(SendOption.None); writer.StartMessage(5); //0x05 GameData writer.Write(AmongUsClient.Instance.GameId); { From 9afd2b8bd746df235861acc032495cf54996dc79 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 21:31:45 +0800 Subject: [PATCH 621/778] Revert --- Patches/onGameStartedPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 9d585c82b5..e1dc9ad1c6 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -676,7 +676,7 @@ public static void RpcSetDisconnected(bool disconnected) playerInfo.IsDead = data; } - var stream = MessageWriter.Get(SendOption.Reliable); + var stream = MessageWriter.Get(SendOption.None); stream.StartMessage(5); stream.Write(AmongUsClient.Instance.GameId); { From 2f5450a872989564f4be06363d95616473fd09d3 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:38:17 +0800 Subject: [PATCH 622/778] Slightly Improve Deleayed Networked Data --- Modules/DelayNetworkedData.cs | 23 ++++++++++++++++++----- Patches/onGameStartedPatch.cs | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index bb9e2f9c3c..24152f7862 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -8,6 +8,17 @@ namespace TOHE.Modules.DelayNetworkDataSpawn; [HarmonyPatch(typeof(InnerNetClient))] public class InnerNetClientPatch { + // This function allows a Reliable Message to be sent without disconnecting the sender. + // Not sure whether this is stable. Putting a logger here. + public static void Send(this InnerNetClient client, MessageWriter writer) + { + SendErrors sendErrors = client.connection.Send(writer); + if (sendErrors != null) + { + Logger.Error($"Send Error: {sendErrors}", "InnerNetClientPatch.Send"); + } + } + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))] [HarmonyPrefix] public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId) @@ -136,12 +147,17 @@ public static void FixedUpdatePostfix(InnerNetClient __instance) if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (!__instance.AmHost || __instance.Streams == null) return; - var players = GameData.Instance.AllPlayers.ToArray().Where(x => x.IsDirty).ToList(); + // We are serializing 2 Networked playerinfo maxium per fixed update + var players = GameData.Instance.AllPlayers.ToArray() + .Where(x => x.IsDirty) + .Take(2) + .ToList(); + if (players != null) { foreach (var player in players) { - MessageWriter messageWriter = MessageWriter.Get(SendOption.None); + MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); messageWriter.StartMessage(5); messageWriter.Write(__instance.GameId); messageWriter.StartMessage(1); @@ -174,8 +190,6 @@ public static void FixedUpdatePostfix(InnerNetClient __instance) } } -// Seems like there is no need to patch this if we are always sending with None calls -/* [HarmonyPatch(typeof(GameData), nameof(GameData.DirtyAllData))] internal class DirtyAllDataPatch { @@ -188,4 +202,3 @@ public static bool Prefix() return false; } } -*/ diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 9d585c82b5..d461e13f2b 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -676,6 +676,7 @@ public static void RpcSetDisconnected(bool disconnected) playerInfo.IsDead = data; } + //Not sure whether this is stable. This is necessary of course, to make sure every player got displayed intro correctly. var stream = MessageWriter.Get(SendOption.Reliable); stream.StartMessage(5); stream.Write(AmongUsClient.Instance.GameId); From c6946e08575458972cd95e2365fc5afb3cd44807 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:05:50 +0800 Subject: [PATCH 623/778] RpcSetDisconnected with Delay --- Modules/DelayNetworkedData.cs | 11 ----------- Patches/onGameStartedPatch.cs | 6 +++++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index 24152f7862..68959227ff 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -8,17 +8,6 @@ namespace TOHE.Modules.DelayNetworkDataSpawn; [HarmonyPatch(typeof(InnerNetClient))] public class InnerNetClientPatch { - // This function allows a Reliable Message to be sent without disconnecting the sender. - // Not sure whether this is stable. Putting a logger here. - public static void Send(this InnerNetClient client, MessageWriter writer) - { - SendErrors sendErrors = client.connection.Send(writer); - if (sendErrors != null) - { - Logger.Error($"Send Error: {sendErrors}", "InnerNetClientPatch.Send"); - } - } - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))] [HarmonyPrefix] public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index d461e13f2b..d38eed452f 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -676,7 +676,7 @@ public static void RpcSetDisconnected(bool disconnected) playerInfo.IsDead = data; } - //Not sure whether this is stable. This is necessary of course, to make sure every player got displayed intro correctly. + /* var stream = MessageWriter.Get(SendOption.Reliable); stream.StartMessage(5); stream.Write(AmongUsClient.Instance.GameId); @@ -689,6 +689,10 @@ public static void RpcSetDisconnected(bool disconnected) stream.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(stream); stream.Recycle(); + */ + + // Let Delayed Networked Data send with delay. + playerInfo.SetDirtyBit(uint.MaxValue); } } From 558ff305f67ea3ff8a6739cb0eb51bc065e8cfb8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 22:39:40 +0800 Subject: [PATCH 624/778] VentilationSystem LastUpadate --- Patches/PlayerControlPatch.cs | 13 ++----------- Patches/VentSystemPatch.cs | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 11cf5cf7bf..bd1e771024 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1493,17 +1493,8 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) if (!__instance.myPlayer.CanUseVents() || (playerRoleClass != null && playerRoleClass.CheckBootFromVent(__instance, id)) ) { - try - { - __instance?.RpcBootFromVent(id); - } - catch - { - _ = new LateTask(() => __instance?.RpcBootFromVent(id), 0.5f, "Prevent Enter Vents"); - } - // Returning false causes errors in the logs - // I don’t yet know how to patch the IEnumerator function in Harmony, but need to send false in a certain place - return false; + _ = new LateTask(() => __instance?.RpcBootFromVent(id), 0.5f, "Prevent Enter Vents"); + return true; } playerRoleClass?.OnCoEnterVent(__instance, id); diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 4f675a1f00..0070667911 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -32,16 +32,23 @@ public static bool Prefix(VentilationSystem __instance, [HarmonyArgument(0)] byt static class VentSystemDeterioratePatch { public static Dictionary LastClosestVent = []; + private static long LastUpadate; + public static void Postfix(VentilationSystem __instance) { - if (!AmongUsClient.Instance.AmHost) return; - if (!Main.IntroDestroyed) return; - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + if (!AmongUsClient.Instance.AmHost || !Main.IntroDestroyed) return; + + var nowTime = Utils.GetTimeStamp(); + if (nowTime != LastUpadate) { - if (pc.BlockVentInteraction()) + LastUpadate = nowTime; + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { - LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - pc.RpcCloseVent(__instance); + if (pc.BlockVentInteraction()) + { + LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + pc.RpcCloseVent(__instance); + } } } } From ec8a31d5c4c848d655428fe8759c04a1bd5779e7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 22:45:51 +0800 Subject: [PATCH 625/778] Fix Ventguard --- Roles/Crewmate/Ventguard.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index 2c38f410b6..fb90300742 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -1,4 +1,7 @@ using AmongUs.GameOptions; +using System; +using System.Text; +using UnityEngine; using TOHE.Roles.Core; using static TOHE.Translator; @@ -77,14 +80,13 @@ public override void OnEnterVent(PlayerControl ventguard, Vent vent) player.RpcSetVentInteraction(); } ventguard.Notify(GetString("VentIsBlocked")); - ventguard.MyPhysics.RpcBootFromVent(ventId); + _ = new LateTask(() => ventguard?.MyPhysics?.RpcBootFromVent(ventId), 0.5f, $"Ventguard {ventguard.PlayerId} Boot From Vent"); } else { ventguard.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); } } - public override void AfterMeetingTasks() { if (BlocksResetOnMeeting.GetBool() && BlockedVents.Any()) @@ -103,4 +105,21 @@ public override void AfterMeetingTasks() BlockedVents.Clear(); } } + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + var taskState = Main.PlayerStates?[playerId].TaskState; + Color TextColor; + var TaskCompleteColor = Color.green; + var NonCompleteColor = Color.yellow; + var NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; + TextColor = comms ? Color.gray : NormalColor; + string Completed2 = comms ? "?" : $"{taskState.CompletedTasksCount}"; + Color TextColor21; + if (AbilityLimit < 1) TextColor21 = Color.red; + else TextColor21 = Color.white; + ProgressText.Append(Utils.ColorString(TextColor, $"({Completed2}/{taskState.AllTasksCount})")); + ProgressText.Append(Utils.ColorString(TextColor21, $" - {Math.Round(AbilityLimit, 1)}")); + return ProgressText.ToString(); + } } From 0af8a9d23e238d184c6067ca833d11bdb2ab3faa Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:54:24 +0800 Subject: [PATCH 626/778] Directly send spawn instead of streams --- Modules/DelayNetworkedData.cs | 56 +++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index 68959227ff..dce39ab582 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -8,6 +8,8 @@ namespace TOHE.Modules.DelayNetworkDataSpawn; [HarmonyPatch(typeof(InnerNetClient))] public class InnerNetClientPatch { + public static List DelayedSpawnPlayers = []; + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))] [HarmonyPrefix] public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId) @@ -45,12 +47,12 @@ public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId __instance.SendOrDisconnect(messageWriter); messageWriter.Recycle(); } - DelaySpawnPlayerInfo(__instance, clientId); + DelayInitialSpawnPlayerInfo(__instance, clientId); return false; } // InnerSloth vanilla officials send PlayerInfo in spilt reliable packets - private static void DelaySpawnPlayerInfo(InnerNetClient __instance, int clientId) + private static void DelayInitialSpawnPlayerInfo(InnerNetClient __instance, int clientId) { List players = GameData.Instance.AllPlayers.ToArray().ToList(); @@ -128,6 +130,42 @@ public static bool SendAllStreamedObjectsPrefix(InnerNetClient __instance, ref b return false; } + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] + [HarmonyPrefix] + public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjParent, int ownerId = -2, SpawnFlags flags = SpawnFlags.None) + { + if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true; + + if (__instance.AmHost) + { + ownerId = ((ownerId == -3) ? __instance.ClientId : ownerId); + MessageWriter msg = MessageWriter.Get(SendOption.Reliable); + msg.StartMessage(5); + msg.Write(__instance.GameId); + __instance.WriteSpawnMessage(netObjParent, ownerId, flags, msg); + msg.EndMessage(); + + //For unknow reason delaying playerinfo spawn here will make beans much easier to appear. + //Especially when spawning lots of players on game end + //Leaving these codes for further use. + /* + if (netObjParent is NetworkedPlayerInfo) + { + DelayedSpawnPlayers.Add(msg); + return false; + } + */ + + AmongUsClient.Instance.SendOrDisconnect(msg); + } + + if (__instance.AmClient) + { + Debug.LogError("Tried to spawn while not host:" + ((netObjParent != null) ? netObjParent.ToString() : null)); + } + return false; + } + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))] [HarmonyPostfix] public static void FixedUpdatePostfix(InnerNetClient __instance) @@ -136,12 +174,24 @@ public static void FixedUpdatePostfix(InnerNetClient __instance) if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (!__instance.AmHost || __instance.Streams == null) return; + /* + var delayedPlayers = DelayedSpawnPlayers.Take(2); + foreach (var msg in delayedPlayers) + { + AmongUsClient.Instance.SendOrDisconnect(msg); + msg.Recycle(); + DelayedSpawnPlayers.Remove(msg); + } + + if (DelayedSpawnPlayers.Count >= 2) return; + */ + // We are serializing 2 Networked playerinfo maxium per fixed update var players = GameData.Instance.AllPlayers.ToArray() .Where(x => x.IsDirty) .Take(2) .ToList(); - + if (players != null) { foreach (var player in players) From b1ca29a88f7fd284315024ca9edb9dcb799c243f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 22:59:31 +0800 Subject: [PATCH 627/778] Check win --- Roles/Neutral/Terrorist.cs | 14 ++++++++++---- Roles/Neutral/Workaholic.cs | 4 +++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Roles/Neutral/Terrorist.cs b/Roles/Neutral/Terrorist.cs index aabb0bfe74..33889dd1e3 100644 --- a/Roles/Neutral/Terrorist.cs +++ b/Roles/Neutral/Terrorist.cs @@ -52,11 +52,17 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn { CheckTerroristWin(exiled); } - private static void CheckTerroristWin(NetworkedPlayerInfo terrorist) + private static void CheckTerroristWin(NetworkedPlayerInfo terroristData) { - var taskState = Utils.GetPlayerById(terrorist.PlayerId).GetPlayerTaskState(); - if (taskState.IsTaskFinished && (!Main.PlayerStates[terrorist.PlayerId].IsSuicide || CanTerroristSuicideWin.GetBool()) && (Main.PlayerStates[terrorist.PlayerId].deathReason != PlayerState.DeathReason.Armageddon)) + var terrorist = terroristData.Object; + if (terrorist == null) return; + + var state = Main.PlayerStates[terrorist.PlayerId]; + var taskState = terrorist.GetPlayerTaskState(); + if (taskState.IsTaskFinished && (!state.IsSuicide || CanTerroristSuicideWin.GetBool()) && (state.deathReason != PlayerState.DeathReason.Armageddon)) { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + if (!CustomWinnerHolder.CheckForConvertedWinner(terrorist.PlayerId)) { CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Terrorist); @@ -80,7 +86,7 @@ private static void CheckTerroristWin(NetworkedPlayerInfo terrorist) pc.SetDeathReason(PlayerState.DeathReason.Bombed); Main.PlayerStates[pc.PlayerId].SetDead(); pc.RpcMurderPlayer(pc); - pc.SetRealKiller(terrorist.Object); + pc.SetRealKiller(terrorist); } } } diff --git a/Roles/Neutral/Workaholic.cs b/Roles/Neutral/Workaholic.cs index 18e39b67a4..a573c795e4 100644 --- a/Roles/Neutral/Workaholic.cs +++ b/Roles/Neutral/Workaholic.cs @@ -66,6 +66,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount if (!((completedTaskCount) >= AllTasksCount && !(WorkaholicCannotWinAtDeath.GetBool() && !player.IsAlive()))) return true; Logger.Info("The Workaholic task is done", "Workaholic"); + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return true; if (!CustomWinnerHolder.CheckForConvertedWinner(player.PlayerId)) { @@ -78,9 +79,10 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount { if (pc.PlayerId != player.PlayerId) { - Main.PlayerStates[pc.PlayerId].deathReason = pc.PlayerId == player.PlayerId ? + var deathReason = pc.PlayerId == player.PlayerId ? PlayerState.DeathReason.Overtired : PlayerState.DeathReason.Ashamed; + pc.SetDeathReason(deathReason); pc.RpcMurderPlayer(pc); pc.SetRealKiller(player); } From d7ddd517fd6b2681d19121b8a02043042a8efc4f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 25 Sep 2024 23:26:34 +0800 Subject: [PATCH 628/778] CoEnterVent Force Update --- Patches/PlayerControlPatch.cs | 3 ++- Patches/VentSystemPatch.cs | 6 ++++-- Roles/Neutral/Jester.cs | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index bd1e771024..1c8deba6f5 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1470,6 +1470,8 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek) return true; Logger.Info($" {__instance.myPlayer.GetNameWithRole().RemoveHtmlTags()}, Vent ID: {id}", "CoEnterVent"); + VentSystemDeterioratePatch.ForceUpadate = true; + //FFA if (Options.CurrentGameMode == CustomGameMode.FFA && FFAManager.CheckCoEnterVent(__instance, id)) { @@ -1498,7 +1500,6 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) } playerRoleClass?.OnCoEnterVent(__instance, id); - return true; } } diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 0070667911..56eb206d86 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -32,15 +32,17 @@ public static bool Prefix(VentilationSystem __instance, [HarmonyArgument(0)] byt static class VentSystemDeterioratePatch { public static Dictionary LastClosestVent = []; - private static long LastUpadate; + public static long LastUpadate; + public static bool ForceUpadate; public static void Postfix(VentilationSystem __instance) { if (!AmongUsClient.Instance.AmHost || !Main.IntroDestroyed) return; var nowTime = Utils.GetTimeStamp(); - if (nowTime != LastUpadate) + if (nowTime != LastUpadate || ForceUpadate) { + ForceUpadate = false; LastUpadate = nowTime; foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 843159d86e..5560a071d8 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -1,7 +1,7 @@ using AmongUs.GameOptions; +using TOHE.Patches; using TOHE.Roles.Core; using static TOHE.Options; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Neutral; @@ -72,6 +72,7 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) RememberBlockedVents.Add(vent.Id); CustomRoleManager.BlockedVentsList[player.PlayerId].Add(vent.Id); } + VentSystemDeterioratePatch.ForceUpadate = true; player.RpcSetVentInteraction(); } public override void OnExitVent(PlayerControl pc, int ventId) From 82efc8656d778bb4ac52d4ab15227716d901cf7d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 00:06:20 +0800 Subject: [PATCH 629/778] Fix Force Upadate --- Modules/ExtendedPlayerControl.cs | 2 +- Modules/RPC.cs | 6 +++++- Patches/PlayerControlPatch.cs | 6 ++++-- Patches/VentSystemPatch.cs | 3 +-- Roles/Neutral/Jester.cs | 1 - 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index c6dc8d7c9e..e4a5a2568b 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -581,7 +581,7 @@ public static List GetVentsFromClosest(this PlayerControl player) // If player is inside a vent, we get the nearby vents that the player can snapto and insert them to the top of the list // Idk how to directly get the vent a player is in, so just assume the closet vent from the player is the vent that player is in // Not sure about whether inVent flags works 100% correct here. Maybe player is being kicked from a vent and inVent flags can return true there - if ((player.walkingToVent || player.inVent) && vents[0] != null) + if ((player.MyPhysics.Animations.IsPlayingEnterVentAnimation() || player.walkingToVent || player.inVent) && vents[0] != null) { var nextvents = vents[0].NearbyVents.ToList(); nextvents.RemoveAll(v => v == null); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index d204b4018b..595305458a 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using TOHE.Modules; +using TOHE.Patches; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; using TOHE.Roles.Crewmate; @@ -727,8 +728,11 @@ public static bool Prefix(PlayerPhysics __instance, byte callId, MessageReader r Logger.Warn("Received Physics RPC without a player", "PlayerPhysics_ReceiveRPC"); return false; } - Logger.Info($"{player.PlayerId}({(__instance.IsHost() ? "Host" : player.Data.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "PlayerPhysics_ReceiveRPC"); + __instance.myPlayer.walkingToVent = true; + VentSystemDeterioratePatch.ForceUpadate = true; + + Logger.Info($"{player.PlayerId}({(__instance.IsHost() ? "Host" : player.Data.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "PlayerPhysics_ReceiveRPC"); return true; } } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 1c8deba6f5..fae751e7c3 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1470,8 +1470,6 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek) return true; Logger.Info($" {__instance.myPlayer.GetNameWithRole().RemoveHtmlTags()}, Vent ID: {id}", "CoEnterVent"); - VentSystemDeterioratePatch.ForceUpadate = true; - //FFA if (Options.CurrentGameMode == CustomGameMode.FFA && FFAManager.CheckCoEnterVent(__instance, id)) { @@ -1502,6 +1500,10 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) playerRoleClass?.OnCoEnterVent(__instance, id); return true; } + public static void Postfix() + { + _ = new LateTask(() => VentSystemDeterioratePatch.ForceUpadate = false, 1f, "Set Force Upadate As False", shoudLog: false); + } } // Player entered in vent [HarmonyPatch(typeof(Vent), nameof(Vent.EnterVent))] diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 56eb206d86..c8bab2bb8a 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -40,9 +40,8 @@ public static void Postfix(VentilationSystem __instance) if (!AmongUsClient.Instance.AmHost || !Main.IntroDestroyed) return; var nowTime = Utils.GetTimeStamp(); - if (nowTime != LastUpadate || ForceUpadate) + if (ForceUpadate || (nowTime != LastUpadate)) { - ForceUpadate = false; LastUpadate = nowTime; foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) { diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 5560a071d8..e42096a48e 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -72,7 +72,6 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) RememberBlockedVents.Add(vent.Id); CustomRoleManager.BlockedVentsList[player.PlayerId].Add(vent.Id); } - VentSystemDeterioratePatch.ForceUpadate = true; player.RpcSetVentInteraction(); } public override void OnExitVent(PlayerControl pc, int ventId) From cf4ac242f1da3e756d0fcb3fa7332f0cb1bef453 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 00:12:05 +0800 Subject: [PATCH 630/778] Check meeting --- Modules/RPC.cs | 7 +++++-- Roles/Neutral/Jester.cs | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 595305458a..c53ce4870c 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -729,8 +729,11 @@ public static bool Prefix(PlayerPhysics __instance, byte callId, MessageReader r return false; } - __instance.myPlayer.walkingToVent = true; - VentSystemDeterioratePatch.ForceUpadate = true; + if (!Main.MeetingIsStarted) + { + __instance.myPlayer.walkingToVent = true; + VentSystemDeterioratePatch.ForceUpadate = true; + } Logger.Info($"{player.PlayerId}({(__instance.IsHost() ? "Host" : player.Data.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "PlayerPhysics_ReceiveRPC"); return true; diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index e42096a48e..1383dcb0ca 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using TOHE.Patches; using TOHE.Roles.Core; using static TOHE.Options; From 25aa03237c58be7921e368135d046e035ab2763d Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:59:08 +0800 Subject: [PATCH 631/778] Add Rpc for Altruist --- Roles/Crewmate/Altruist.cs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 52c5c4047b..6286891bb8 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -1,4 +1,6 @@ using AmongUs.GameOptions; +using Hazel; +using InnerNet; using TOHE.Roles.Core; namespace TOHE.Roles.Crewmate; @@ -50,10 +52,27 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) AURoleOptions.EngineerCooldown = 1f; AURoleOptions.EngineerInVentMaxTime = 1f; } + + public void SendRPC() + { + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(IsRevivingMode); + writer.Write(RevivedPlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + IsRevivingMode = reader.ReadBoolean(); + RevivedPlayerId = reader.ReadByte(); + } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) { IsRevivingMode = !IsRevivingMode; Utils.NotifyRoles(SpecifySeer: physics.myPlayer, ForceLoop: false); + SendRPC(); } public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { @@ -112,6 +131,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay } } Utils.NotifyRoles(); + SendRPC(); return false; } else if ((RevivedDeadBodyCannotBeReported.GetBool() || reporter.PlayerId == RevivedPlayerId) && deadBody.PlayerId == RevivedPlayerId) @@ -120,6 +140,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay if (countDeadBody >= 2) return true; reporter.Notify(Translator.GetString("Altruist_YouTriedReportRevivedDeadBody")); + SendRPC(); return false; } @@ -127,7 +148,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay } public override string GetLowerText(PlayerControl seer, PlayerControl target, bool isForMeeting = false, bool isForHud = false) { - if (seer.PlayerId != target.PlayerId || isForMeeting) return string.Empty; + if (seer.PlayerId != target.PlayerId || isForMeeting || !_Player.IsAlive()) return string.Empty; return string.Format(Translator.GetString("AltruistSuffix"), Translator.GetString(IsRevivingMode ? "AltruistReviveMode" : "AltruistReportMode")); } public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) @@ -160,9 +181,12 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf public override void SetAbilityButtonText(HudManager hud, byte playerId) { - hud?.AbilityButton?.OverrideText(Translator.GetString("AltruistAbilityButton")); + if (_Player.IsAlive()) + { + hud?.AbilityButton?.OverrideText(Translator.GetString("AltruistAbilityButton")); - if (IsRevivingMode) - hud?.ReportButton?.OverrideText(Translator.GetString("AltruistReviveMode")); + if (IsRevivingMode) + hud?.ReportButton?.OverrideText(Translator.GetString("AltruistReviveMode")); + } } } From d486cbc9e177cfa2f36db51d062046fc6d2eeea6 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 16:18:47 +0800 Subject: [PATCH 632/778] Discord rip? omg bro --- Patches/MeetingHudPatch.cs | 7 +++++-- Patches/PlayerControlPatch.cs | 3 --- Roles/Crewmate/Jailer.cs | 20 +++++++------------- Roles/Impostor/EvilHacker.cs | 19 +++++++++---------- Roles/Impostor/Witch.cs | 2 -- Roles/Neutral/Baker.cs | 3 +-- Roles/Neutral/HexMaster.cs | 2 -- Roles/Neutral/Pirate.cs | 13 +++++-------- Roles/Neutral/PlagueBearer.cs | 2 +- Roles/Neutral/SoulCollector.cs | 29 +++++++++++++---------------- Roles/Neutral/Virus.cs | 2 +- 11 files changed, 42 insertions(+), 60 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 741a7a7134..091d22a480 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -611,9 +611,12 @@ public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathR } private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, params byte[] playerIds) { - foreach (var roleClass in CustomRoleManager.AllEnabledRoles.ToArray()) + if (deathReason == PlayerState.DeathReason.Vote) { - roleClass?.OnCheckForEndVoting(deathReason, playerIds); + foreach (var player in Main.AllPlayerControls) + { + player.GetRoleClass()?.OnCheckForEndVoting(deathReason, playerIds); + } } foreach (var playerId in playerIds) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index fae751e7c3..47ad489ab9 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -441,9 +441,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player target.SetDeathReason(PlayerState.DeathReason.Kill); } - target.Data.IsDead = true; - target.Data.MarkDirty(); - Main.MurderedThisRound.Add(target.PlayerId); // Check Youtuber first died diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 809a5e1bd9..bc9feb1633 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -137,24 +137,18 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); - public override void OnReportDeadBody(PlayerControl sob, NetworkedPlayerInfo bakugan) + + public override void OnMeetingHudStart(PlayerControl pc) { + if (!NotifyJailedOnMeetingOpt.GetBool()) return; + foreach (var targetId in JailerTarget.Values) { if (targetId == byte.MaxValue) continue; - var tpc = Utils.GetPlayerById(targetId); - if (tpc == null) continue; + var tpc = targetId.GetPlayer(); + if (!tpc.IsAlive()) continue; - if (NotifyJailedOnMeetingOpt.GetBool() && tpc.IsAlive()) - { - _ = new LateTask(() => - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("JailedNotifyMsg"), targetId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); - } - }, 5f, $"Jailer Notify Jailed - id:{targetId}"); - } + MeetingHudStartPatch.AddMsg(GetString("JailedNotifyMsg"), targetId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); } } diff --git a/Roles/Impostor/EvilHacker.cs b/Roles/Impostor/EvilHacker.cs index 409233f070..ef37463df5 100644 --- a/Roles/Impostor/EvilHacker.cs +++ b/Roles/Impostor/EvilHacker.cs @@ -25,6 +25,7 @@ internal class EvilHacker : RoleBase private static OptionItem OptionCanSeeMurderRoom; private static byte player = 0; + private string message; public enum OptionName { @@ -56,6 +57,7 @@ public override void SetupCustomOption() public override void Init() { evilHackerPlayer = null; + message = string.Empty; canSeeDeadMark = OptionCanSeeDeadMark.GetBool(); canSeeImpostorMark = OptionCanSeeImpostorMark.GetBool(); @@ -116,18 +118,15 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf builder.Append('\n'); } - var message = builder.ToString(); - var title = Utils.ColorString(Color.green, Translator.GetString("EvilHackerLastAdminInfoTitle")); + message = builder.ToString(); + } + public override void OnMeetingHudStart(PlayerControl pc) + { + if (message == string.Empty || !evilHackerPlayer.IsAlive()) return; - _ = new LateTask(() => - { - if (GameStates.IsInGame) - { - Utils.SendMessage(message, evilHackerPlayer.PlayerId, title, false); - } - }, 5f, "EvilHacker Admin Message"); - return; + MeetingHudStartPatch.AddMsg(message, evilHackerPlayer.PlayerId, Utils.ColorString(Color.green, Translator.GetString("EvilHackerLastAdminInfoTitle"))); } + public override void MeetingHudClear() => message = string.Empty; public override bool KillFlashCheck(PlayerControl killer, PlayerControl target, PlayerControl seer) => CheckKillFlash(killer, target) && killer.PlayerId != seer.PlayerId; diff --git a/Roles/Impostor/Witch.cs b/Roles/Impostor/Witch.cs index ad89b69130..c1c847de2f 100644 --- a/Roles/Impostor/Witch.cs +++ b/Roles/Impostor/Witch.cs @@ -157,8 +157,6 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (deathReason != PlayerState.DeathReason.Vote) return; - foreach (var id in exileIds) { if (SpelledPlayer.ContainsKey(id)) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 56157d7f3c..b276809ec7 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -342,8 +342,7 @@ public override void OnReportDeadBody(PlayerControl sylveon, NetworkedPlayerInfo } public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (_Player == null || deathReason != PlayerState.DeathReason.Vote) return; - if (exileIds.Contains(_Player.PlayerId) || Baker.StarvedNonBreaded) return; + if (_Player == null || exileIds == null || exileIds.Contains(_Player.PlayerId) || Baker.StarvedNonBreaded) return; var deathList = new HashSet(); var baker = _Player; diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index 46fcc55897..bb38197860 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -178,8 +178,6 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (deathReason != PlayerState.DeathReason.Vote) return; - foreach (var id in exileIds) { if (HexedPlayer.ContainsKey(id)) diff --git a/Roles/Neutral/Pirate.cs b/Roles/Neutral/Pirate.cs index 113a677445..ba0ee1c0a2 100644 --- a/Roles/Neutral/Pirate.cs +++ b/Roles/Neutral/Pirate.cs @@ -53,18 +53,15 @@ public override void Add(byte playerId) { DuelDone[playerId] = false; } - public override void MeetingHudClear() + public override void OnMeetingHudStart(PlayerControl pc) { if (!HasEnabled || PirateTarget == byte.MaxValue) return; - var pc = _Player; var tpc = GetPlayerById(PirateTarget); if (!tpc.IsAlive()) return; - _ = new LateTask(() => - { - SendMessage(GetString("PirateMeetingMsg"), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Pirate), GetString("PirateTitle"))); - SendMessage(GetString("PirateTargetMeetingMsg"), tpc.PlayerId, ColorString(GetRoleColor(CustomRoles.Pirate), GetString("PirateTitle"))); - }, 3f, "Pirate Meeting Messages"); + + MeetingHudStartPatch.AddMsg(GetString("PirateMeetingMsg"), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Pirate), GetString("PirateTitle"))); + MeetingHudStartPatch.AddMsg(GetString("PirateTargetMeetingMsg"), tpc.PlayerId, ColorString(GetRoleColor(CustomRoles.Pirate), GetString("PirateTitle"))); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = DuelCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; @@ -130,7 +127,7 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (_Player == null || PirateTarget == byte.MaxValue || deathReason != PlayerState.DeathReason.Vote) return; + if (_Player == null || PirateTarget == byte.MaxValue) return; var pirateId = _state.PlayerId; if (!DuelDone[pirateId]) return; diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 41e6d50835..96f3a4482d 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -229,7 +229,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl internal class Pestilence : RoleBase { //===========================SETUP================================\\ - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PlagueBearer); + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Pestilence); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index 79a26b4291..bbd626ee41 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -100,13 +100,14 @@ public override void OnReportDeadBody(PlayerControl ryuak, NetworkedPlayerInfo i if (!_Player.IsAlive() || !GetPassiveSouls.GetBool()) return; AbilityLimit++; - _ = new LateTask(() => - { - Utils.SendMessage(GetString("PassiveSoulGained"), _Player.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); - - }, 3f, "Passive Soul Gained"); SendRPC(); } + public override void OnMeetingHudStart(PlayerControl pc) + { + if (!pc.IsAlive() || !GetPassiveSouls.GetBool()) return; + + MeetingHudStartPatch.AddMsg(GetString("PassiveSoulGained"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), GetString("SoulCollectorTitle"))); + } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { if (!_Player.IsAlive()) return; @@ -118,7 +119,7 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i { TargetId = byte.MaxValue; AbilityLimit++; - if (GameStates.IsMeeting) + if (inMeeting) { _ = new LateTask(() => { @@ -130,15 +131,12 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i SendRPC(); _Player.Notify(GetString("SoulCollectorSoulGained")); } - if (AbilityLimit >= SoulCollectorPointsOpt.GetInt()) + if (AbilityLimit >= SoulCollectorPointsOpt.GetInt() && !inMeeting) { - if (!GameStates.IsMeeting) - { - PlayerControl sc = _Player; - sc.RpcSetCustomRole(CustomRoles.Death); - sc.Notify(GetString("SoulCollectorToDeath")); - sc.RpcGuardAndKill(sc); - } + PlayerControl sc = _Player; + sc.RpcSetCustomRole(CustomRoles.Death); + sc.Notify(GetString("SoulCollectorToDeath")); + sc.RpcGuardAndKill(sc); } } public override void AfterMeetingTasks() @@ -181,8 +179,7 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (_Player == null || deathReason != PlayerState.DeathReason.Vote) return; - if (exileIds.Contains(_Player.PlayerId)) return; + if (_Player == null || exileIds == null || exileIds.Contains(_Player.PlayerId)) return; var deathList = new List(); var death = _Player; diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs index 27592ca9cc..1bd483019b 100644 --- a/Roles/Neutral/Virus.cs +++ b/Roles/Neutral/Virus.cs @@ -103,7 +103,7 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (!_Player.IsAlive() || deathReason != PlayerState.DeathReason.Vote || !KillInfectedPlayerAfterMeeting.GetBool()) return; + if (!_Player.IsAlive() || !KillInfectedPlayerAfterMeeting.GetBool()) return; var virus = _Player; if (exileIds.Contains(virus.PlayerId)) From b7c3ab962425ae548788956627c0bb9e35ef9d4d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 16:40:08 +0800 Subject: [PATCH 633/778] Hmm --- Modules/DelayNetworkedData.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index dce39ab582..a27e99b566 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -8,7 +8,7 @@ namespace TOHE.Modules.DelayNetworkDataSpawn; [HarmonyPatch(typeof(InnerNetClient))] public class InnerNetClientPatch { - public static List DelayedSpawnPlayers = []; + //public static List DelayedSpawnPlayers = []; [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))] [HarmonyPrefix] @@ -161,7 +161,7 @@ public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjP if (__instance.AmClient) { - Debug.LogError("Tried to spawn while not host:" + ((netObjParent != null) ? netObjParent.ToString() : null)); + Debug.LogError("Tried to spawn while not host:" + (netObjParent?.ToString())); } return false; } From 5f40c3c10f0f10ac41dd66469213466b5fa6eb59 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 18:03:47 +0800 Subject: [PATCH 634/778] Check modded in lobby in sync custom settings --- Modules/RPC.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index c53ce4870c..76ae2071a7 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -753,6 +753,10 @@ public static void SyncCustomSettingsRPC(int targetId = -1) return; } } + else if (!Main.AllPlayerControls.Any(pc => pc.IsNonHostModdedClient())) + { + return; + } if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) { @@ -778,6 +782,10 @@ static void SyncOptionsBetween(int startAmount, int lastAmount, int amountAllOpt return; } } + else if (!Main.AllPlayerControls.Any(pc => pc.IsNonHostModdedClient())) + { + return; + } if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) { From f8de505e0e39f45d725074bfc3c024255d628219 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 18:14:53 +0800 Subject: [PATCH 635/778] Plugin Version --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index ce097ccbc8..5d64df0bd6 100644 --- a/main.cs +++ b/main.cs @@ -42,7 +42,7 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0923.210.00150"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginVersion = "2024.0926.210.00150"; // YEAR.MMDD.VERSION.CANARYDEV public const string PluginDisplayVersion = "2.1.0 Alpha 15"; public const string SupportedVersionAU = "2024.8.13"; From 6f280e45b547b533265f74ca585ee0fc2c869234 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 18:37:54 +0800 Subject: [PATCH 636/778] try {} catch { } --- Patches/LobbyPatch.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Patches/LobbyPatch.cs b/Patches/LobbyPatch.cs index 814abd033b..7b86c2113a 100644 --- a/Patches/LobbyPatch.cs +++ b/Patches/LobbyPatch.cs @@ -91,24 +91,24 @@ public static void Update_Postfix(LobbyBehaviour __instance) public static class HostInfoPanelUpdatePatch { private static TextMeshPro HostText; - public static bool Prefix(HostInfoPanel __instance) + public static void Postfix(HostInfoPanel __instance) { - // sometimse AU get exeption "System.IndexOutOfRangeException: Index was outside the bounds of the array" - return __instance != null && GameStates.IsLobby && __instance.player.ColorId > 0 && __instance.player.ColorId != 255 && __instance.player.ColorId != int.MaxValue; - } - public static void Postfix(HostInfoPanel __instance, bool __runOriginal) - { - if (AmongUsClient.Instance.AmHost && __runOriginal) + try { - if (HostText == null) - HostText = __instance.content.transform.FindChild("Name").GetComponent(); + if (AmongUsClient.Instance.AmHost) + { + if (HostText == null) + HostText = __instance.content.transform.FindChild("Name").GetComponent(); - string htmlStringRgb = ColorUtility.ToHtmlStringRGB(Palette.PlayerColors[__instance.player.ColorId]); - string hostName = Main.HostRealName; - string youLabel = DestroyableSingleton.Instance.GetString(StringNames.HostYouLabel); + string htmlStringRgb = ColorUtility.ToHtmlStringRGB(Palette.PlayerColors[__instance.player.ColorId]); + string hostName = Main.HostRealName; + string youLabel = DestroyableSingleton.Instance.GetString(StringNames.HostYouLabel); - // Set text in host info panel - HostText.text = $"{hostName} ({youLabel})"; + // Set text in host info panel + HostText.text = $"{hostName} ({youLabel})"; + } } + catch + { } } } \ No newline at end of file From b9210a936668f9129bfb697dfc980942b271eb4b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 26 Sep 2024 18:45:44 +0800 Subject: [PATCH 637/778] Add Sync Names --- Modules/OutfitManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index 2a39e8e550..427c75c74e 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -17,6 +17,7 @@ void Setoutfit() .EndRpc(); Main.AllPlayerNames[player.PlayerId] = Outfit.PlayerName; + RPC.SyncAllPlayerNames(); player.SetColor(Outfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetColor) @@ -100,6 +101,8 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P .EndRpc(); Main.AllPlayerNames[player.PlayerId] = newOutfit.PlayerName; + + RPC.SyncAllPlayerNames(); } player.SetColor(newOutfit.ColorId); From 50189a7a122181acf4cd71c8a3bb73942561ea75 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:45:13 -0400 Subject: [PATCH 638/778] Update en_US.json --- Resources/Lang/en_US.json | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d72af7fe63..05ff18a294 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -403,7 +403,7 @@ "EngineerTOHEInfo": "Use the vents to catch the Impostors", "ScientistTOHEInfo": "Access portable vitals from anywhere", "NoisemakerTOHEInfo": "Send out an alert when killed", - "TrackerTOHEInfo": "Track a players with your map", + "TrackerTOHEInfo": "Track players with your map", "ShapeshifterTOHEInfo": "Disguise as crewmates to frame them", "PhantomTOHEInfo": "Turn invisible", "GuardianAngelTOHEInfo": "Protect the crewmates from the Impostors", @@ -485,7 +485,7 @@ "CleanserInfo": "Erase All Add-ons of your vote target", "KeeperInfo": "Reject the Eject, Keeper Protect!", "MayorInfo": "Your vote counts multiple times", - "PsychicInfo": "One of the red names are evil", + "PsychicInfo": "One of the red names is evil", "MechanicInfo": "Vent around and fix sabotages", "SheriffInfo": "Shoot the Impostors", "VigilanteInfo": "Not the hero we deserved but the hero we needed", @@ -494,17 +494,17 @@ "SnitchInfo": "Finish your tasks to find the Impostors", "MarshallInfo": "Finish your tasks to prove your innocence", "DoctorInfo": "Know how each player died", - "DictatorInfo": "Exile a player based on your own judgment", + "DictatorInfo": "Exile a player based on your judgment", "DetectiveInfo": "Gain extra info from your body reports", "UndercoverInfo": "Impostors see you as their partner", - "KnightInfo": "You can kill 1 player", + "KnightInfo": "You can kill one player", "NiceGuesserInfo": "Guess Impostor roles in meetings to kill", "GuessMasterInfo": "Whispers heard, every guessed word.", "TransporterInfo": "Do tasks to swap two players' locations", "TimeManagerInfo": "Increase meeting time by doing tasks", "VeteranInfo": "Alert to kill anyone who interacts with you", "BastionInfo": "Bomb vents", - "YinYangerInfo": "Spontaneously combust 2 players", + "YinYangerInfo": "Spontaneously combust two players", "BodyguardInfo": "Prevent nearby kills", "DeceiverInfo": "Try to fool the players", "GrenadierInfo": "Reduce Impostors' vision by venting", @@ -699,12 +699,12 @@ "TrickyInfo": "Tricky slays, in mysterious ways.", "TiredInfo": "Labor makes you sleepy.. Zzz..", "StatueInfo": "You're still as a rock around people", - "EvaderInfo": "You have a chance to not be exiled!", + "EvaderInfo": "You have a chance not to be exiled!", "GMInfo": "Spectate the chaos!", "NotAssignedInfo": "No assigned role", "SunnyboyInfo": "Shine, shine my sunshine!", "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", - "RainbowInfo": "Colorful melodies! You don't even know your own color.", + "RainbowInfo": "Colorful melodies! You don't even know your color.", "DollMasterInfo": "Take control of players actions!", "DoubleAgentInfo": "Plant bombs on players in meetings", "SlothInfo": "You're slower", @@ -712,7 +712,7 @@ "EavesdropperInfo": "Listen in on other roles", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", - "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", + "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die, you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", "TrackerTOHEInfoLong": "(Crewmates):\nAs the Tracker, press your tracker button on a player to track their location via the map for a limited amount of time.", "ShapeshifterTOHEInfoLong": "(Impostors):\nAs the Shapeshifter, you can shapeshift into other players. It is obvious when you shapeshift or revert shifting.", "PhantomTOHEInfoLong": "(Impostors):\nAs the Phantom, you can press your vanish button to go invisible to escape a kill. You can click your appear button if you want to become visible before the timer runs out or not.\nNote: You will make a smoke cloud whenever you go invisible and become visible. So make sure you are in a safe area where no one will see you.", @@ -723,7 +723,7 @@ "FireworkerInfoLong": "(Impostors):\nAs the Fireworker, you can Shapeshift to place Fireworks up to the maximum amount the host sets.\nWhen you are the last Impostor and all Fireworks have been placed, shapeshift again to detonate them and kill everyone in their radius, including you.\nIf you kill all players with your Fireworks, it's considered an Impostor victory.", "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your Deadline, as shown by your Shapeshift cooldown (which you cannot use). If you fail to kill, you die.", "ShapeMasterInfoLong": "(Impostors):\nAs the Shapemaster, you have no Shapeshift cooldown.", - "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. This means that even if a meeting is called first, your target still dies. However, if you bite a Bait, you kill normally and report the body. Depending on the settings, you can use double trigger (bite players - single click, kill normally - double click).", + "VampireInfoLong": "(Impostors):\nAs the Vampire, your kills are delayed. This means that your target still dies even if a meeting is called first. However, if you bite a Bait, you kill normally and report the body. Depending on the settings, you can use double trigger (bite players - single click, kill normally - double click).", "WarlockInfoLong": "(Impostors):\nAs the Warlock, you can Curse up to one other player at a time.\nWhen you Shapeshift, if you have Cursed a player, they kill the nearest person, which, depending on settings, can include you or other Impostors.\nYou can kill normally while Shapeshifted.", "ZombieInfoLong": "(Impostors):\nZombie has a short kill cooldown but moves very slowly and has very little vision. Zombie can not be voted out by anyone other than the Dictator, and the movement speed of Zombie will gradually slow down as they make kills or time passes.", "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your kill button to Mark a target (single click) or kill normally (double click). You may then Shapeshift to teleport to the Marked target and kill them.", @@ -734,30 +734,30 @@ "WitchInfoLong": "(Impostors):\nAs the Witch, you can use your kill button to Spell (single click) or kill normally (double click).\nDuring the next meeting, the spelled target(s) will have a 「†」 next to their name visible to everyone. Unless you die by the end of that meeting, all Spelled targets will die.", "NemesisInfoLong": "(Impostors):\nAs the Nemesis, you can only kill if you are the last Impostor.\nIf you are dead, you can use the command /rv [ID] to kill the player whose ID you typed. Use /id to show the IDs of all players, or look next to their names.", "BloodmoonInfoLong": "(Impostors [Ghost]):\nAs the Bloodmoon, attack the enemies to make them drip blood, this means they will die in a time set by the host, and will be aware of it.", - "PossessorInfoLong": "(Impostors [Ghost]):\nAs the Possessor, you are able to possess players when others aren't in the Alert Range. Lead the possessed player as far as possible from other players that are in the Focus Range. Once the possession duration is up, the possessed player will be killed if others aren't in the Focus Range. If you run into another player in the Alert Range while possessing, the Possessor will immediately unpossess.", + "PossessorInfoLong": "(Impostors [Ghost]):\nAs the Possessor, you can possess players when others aren't in the Alert Range. Lead the possessed player as far as possible from other players in the Focus Range. Once the possession duration is up, the possessed player will be killed if others aren't in the Focus Range. If you run into another player in the Alert Range while possessing, the Possessor will immediately unpossess.", "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a kill button. But if the target does have a kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", - "YinYangerInfoLong": "(Impostors):\nAs the YinYanger, you can use your kill button one time to pick your Yin, and then a second time to choose a Yang. When those 2 players meet, they'll kill each-other. When Yin & Yang have been chosen you can kill normally.", + "YinYangerInfoLong": "(Impostors):\nAs the YinYanger, you can use your kill button one time to pick your Yin and then a second time to choose a Yang. When those two players meet, they'll kill each other. When Yin & Yang have been chosen, you can kill normally.", "TimeThiefInfoLong": "(Impostors):\nEvery time the Time Thief kills a player, the meeting time will be reduced by a certain amount of time. If the Time Thief dies, the meeting time will return to normal.", "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first shapeshift location towards your unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", - "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting your marked location will reset.\n\nAfter every teleported kill you will freeze for a configurable amount of time", + "UndertakerInfoLong": "(Impostors):\nEverytime you Shapeshift, you mark the location. Your kills will then teleport to the marked location.\nAfter every kill and meeting, your marked location will reset.\n\nAfter every teleported kill, you will freeze for a configurable amount of time", "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker, you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out, therefore destroying all the rifts.\n\nNote: Up to two rifts can be placed at a time; if you try to place a third, it removes the first one.", "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other players, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", - "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If it is correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", + "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a bait, no self-report will be made.", "TrapsterInfoLong": "(Impostors):\nThe Trapster has a unique method of killing. By initiating a body report, the Trapster can eliminate the player attempting to report the body the Trapster killed.\nNote: If Trapster kills the Bait, the Trapster will die immediately.", - "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the max is set by the Host). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", + "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the kill button. If the recruitment is successful, both the Gangster and the target will see the shield animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the Host sets the max). If the Gangster tries to recruit players who cannot be recruited, such as neutrals or some special crews, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a shield animation on their body as a reminder (only visible to himself). The cleaned-up body cannot be reported (including bait).", "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your kill button quantizes targets, which activates after a delay, causing the next person they encounter to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different kill cooldowns. Greedy's kill cooldown is reset every meeting, and Greedy's first kill is always odd.", "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the max of times you can counterattack)", "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the kill cooldown; he will kill even if it's still on cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", - "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shapeshifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", + "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shape-shifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA crew target can only be erased once (include Oiiai)", "ButcherInfoLong": "(Impostors):\nThe Butcher's kills, including passive ones, leave multiple dead bodies on targets, which can be a bit confusing when reporting. Here's the rule: the killed target must repeatedly display the animation of being killed, which cannot be skipped, and they cannot participate in the meeting normally during this period. And if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", @@ -792,7 +792,7 @@ "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the crewmates against each other. Each time a Crewmate gets voted out in a meeting, if you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The Host determines the number of additional players dying.", "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task. In addition, the Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for Anonymous, being marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any add-ons.", - "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only die when the Murderer is alone with the Super Star (regular kills only). In addition, the Guessers can't guess the Super Star. ", + "SuperStarInfoLong": "(Crewmates):\nThere will be a star logo next to the Super Star's name, so everyone knows who the Super Star is. The Super Star can only die when the murderer is alone with the Super Star (regular kills only). In addition, the Guessers can't guess the Super Star. ", "CelebrityInfoLong": "(Crewmates):\nAll Crewmates see the kill-flash when the Celebrity dies (same as the Seer sees the kill-flash) and get a notice at the next meeting. The Impostors don't know anything about this.", "CleanserInfoLong": "(Crewmates):\nAs The Cleanser, you can vote to erase the add-ons of any target at the meeting. This erasure takes effect after the meeting ends. Depending on the settings, the cleansed player may never receive add-ons again.", "KeeperInfoLong": "(Crewmates):\nAs keeper, you can vote for someone to protect them from being ejected. You can only do this a configurable number of times.", @@ -846,7 +846,7 @@ "ChameleonInfoLong": "(Crewmates):\nAs the Chameleon, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", "InspectorInfoLong": "(Crewmates):\nCheck If two players are in the same team or not. You will get an affirmation message if they are on the same team or a denial message if they are not on the same team.\n\nAll neutrals and converted players are counted in the same team. Trickster counts as Crew, and Rascal counts as Impostor.\nChecking command: /cmp [player id 1] [player id 2].", "CaptainInfoLong": "(Crewmates):\nWith each completed task, the Captain gains the power to slow down a random non-crew role. Crewmates can see ☆besides Captain's name.\n\nIf anyone betrays the Captain's trust by voting Captain out, they will lose an add-on.", - "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", + "AdmirerInfoLong": "(Crewmates):\nAs the Admirer, admire a player to make them Crewmate aligned.\nThey'll win with crewmates and not their original team.\n\nYou can only do this once per player.", "TimeMasterInfoLong": "(Crewmates):\nAs the Time Master, use the vents to mark everyone's position.\nWhen using the ability again, every alive player will rewind to the marked positions.\n\nDuring the ability duration, the Time Master gains a time shield, which protects them from death.", "CrusaderInfoLong": "(Crewmates):\nAs the Crusader, use your kill button to crusade a player.\nIf that player gets attacked, you'll kill the attacker.", "AltruistInfoLong": "(Crewmates):\nAs the Altruist, you can sacrifice yourself to revive a dead body using the «Report» button.\nNote: If a dead player has left the game, you report that body normally.\nAlso revived player cannot report self dead body", @@ -860,14 +860,14 @@ "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, your survival is crucial. You can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses. This unique role adds a new dynamic to the game, where your survival is not just for your benefit but for the entire Crew's success.", "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the kill button interaction is blocked, the player's cooldown will reset to 10s'", "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player.", - "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the kill button on the player and following them for a few seconds. When the dousing starts, and it's successful, a shield animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", - "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting. You may have to report the body to receive a clue, depending on the settings. The more tasks you complete, the more precise the clues get.", - "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players does nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", - "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your kill cooldown goes down permanently by the set amount. However, if you kill someone who is not one of your targets, your kill cooldown permanently increases by the set amount. A colored name indicates your targets.", + "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the kill button on the player and following them for a few seconds. When the dousing starts and it's successful, a shield animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", + "EnigmaInfoLong": "(Crewmates):\nAs the Enigma, you get a random clue about the killer each meeting. Depending on the settings, you may have to report the body to receive a clue. The more tasks you complete, the more precise the clues get.", + "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players do nothing immediately, but killing a doused player will significantly shorten your kill cooldown. To win, be the last player alive.", + "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your kill cooldown goes down permanently by the set amount. However, if you kill someone not one of your targets, your kill cooldown permanently increases by the set amount. A colored name indicates your targets.", "MiniInfoLong": "(Crewmate or Impostor):\nThe Mini has two roles. A Nice or Evil Mini is chosen.\n\nUse'/r nice mini' and '/r evil mini' respectively for more details.", - "JesterInfoLong": "(Neutrals):\nIf the Jester gets voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses the game. Note: Jester, Executioner, and Innocent can win together.", + "JesterInfoLong": "(Neutrals):\nIf the Jester gets voted out, the Jester wins the game alone. If the Jester is still alive at the end of the game, the Jester loses. Note: Jester, Executioner, and Innocent can win together.", "TerroristInfoLong": "(Neutrals):\nIf the Terrorist dies after completing all tasks, the Terrorist wins the game alone. (They can win by either being voted out or killed).", - "ExecutionerInfoLong": "(Neutrals):\nThe Executioner is a role with an execution target, indicated by a diamond symbol「♦」next to their name. If the execution target is killed, the Executioner's role will change to either Crewmate, Jester, or Opportunist, depending on the game settings. However, if the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", + "ExecutionerInfoLong": "(Neutrals):\nThe Executioner is a role with an execution target, indicated by a diamond symbol「♦」next to their name. If the execution target is killed, the Executioner's role will change to Crewmate, Jester, or Opportunist, depending on the game settings. However, if the execution target is voted out in the meeting, the Executioner wins. Note: Jester, Executioner, and Innocent can win together.", "LawyerInfoLong": "(Neutrals):\nLawyer has a target to defend, which will be indicated by a diamond 「♦」 next to their name.\nIf your target wins, you win.\nIf they lose, you lose.", "OpportunistInfoLong": "(Neutrals):\nIf the Opportunist survives at the end of the game, the Opportunist will win with the winning player.", "VectorInfoLong": "(Neutrals):\nVector will win alone by venting a certain number of times.", @@ -887,19 +887,19 @@ "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", - "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote, or by it misguessing.\nYour presence is announced to everyone at the meeting after you transform.", + "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote or by misguessing.\nYour presence is announced to everyone at the meeting after you transform.", "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them Bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after meeting)", - "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. If Famine is not voted out after the meeting they become Famine, every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible and your presence is announced to everyone at the meeting after you transform.", - "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level you become War.", + "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", + "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects and the setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after the meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after the meeting)", + "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. Suppose Famine is not voted out after the meeting. Then they become Famine, and every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", + "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the Host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level, you become War.", "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", "CultistInfoLong": "(Neutrals):\nAs the Cultist, your kill button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain the majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", "SerialKillerInfoLong": "(Neutrals):\nAs the Serial Killer, you win if you are the last player alive.", "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your kill cooldown decreases with each kill you make.\n\nKill everyone to win.", "InfectiousInfoLong": "(Neutrals):\nAs the Infectious, your job is to infect as many players as you can.\n\nIf you infect all the killers, you can outnumber the Crew and win the game.\n\nIf you die, all the players you've infected will die after the next meeting.\nIf they achieve your win condition before then, you can still win.", - "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a crewmate, their corpse is infected with a virus. The crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", + "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a crewmate, their corpse is infected with a virus. The Crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the Virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", "PursuerInfoLong": "(Neutrals):\nAs the Pursuer, you can use your ability on someone to make them misfire when they try to kill.\n\nTo win, survive to the end of the game.", "SpecterInfoLong": "(Neutrals):\nAs the Specter, your job is to get killed and finish your tasks.\nYou can do your tasks while alive.\nYou cannot win if you're alive.\nIf you get killed, you win with the winning team if your tasks are completed.", "PirateInfoLong": "(Neutrals):\nAs the Pirate, use your kill button to select a target every round.\nYou will duel with your target in the next meeting. \nIf both the Pirate and the target choose the same number, the Pirate wins.\nAdditionally, if the Pirate wins the duel or the target doesn't participate in the duel, the Pirate kills the target.\n\nDueling command: /duel X (where X can be 0, 1, or 2)\n\nYou win after winning a certain number of duels set by the Host.\n\nNote: The kill would not count towards pirate victory if the target did not participate in the duel.", @@ -916,7 +916,7 @@ "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", - "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button one time to steal a player's addon and twice to kill. You may instantly steal the addon or after the meeting starts, depending on the settings. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable addons on the target or the target is stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can vent is on, Nimble will become unstealable.", + "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your kill button one time to steal a player's addon and twice to kill. Depending on the settings, you may instantly steal the addon or after the meeting starts. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable addons on the target or the target is stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can vent is on, Nimble will become unstealable.", "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", "PunchingBagInfoLong": "(Neutrals):\nAs the Punching Bag, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the host settings), then he wins.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.", @@ -1005,7 +1005,7 @@ "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", - "EvaderInfoLong": "(Add-ons):\nWhen the Evader gets voted out, there is a chance the Evader will not get ejected. (Chance set by host.)", + "EvaderInfoLong": "(Add-ons):\nWhen the Evader gets voted out, there is a chance the Evader will not get ejected. (Chance set by the Host.)", "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", @@ -1021,7 +1021,6 @@ "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", - "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", From 06fcc0fa8d9b0482d96d2f1c7a62874067657d56 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:13:39 +0800 Subject: [PATCH 639/778] Add notifyGameEnding --- Modules/RPC.cs | 2 ++ Modules/Utils.cs | 13 +++++++++++++ Patches/ChatCommandPatch.cs | 2 ++ Patches/CheckGameEndPatch.cs | 1 + Patches/PlayerJoinAndLeftPatch.cs | 1 + Resources/Lang/en_US.json | 1 + 6 files changed, 20 insertions(+) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index c53ce4870c..b79af8e037 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -920,6 +920,8 @@ public static void ForceEndGame(CustomWinner win) if (AmongUsClient.Instance.AmHost) { ShipStatus.Instance.enabled = false; + Utils.NotifyGameEnding(); + try { GameManager.Instance.LogicFlow.CheckEndCriteria(); } catch { } try { GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f8a43b0702..2743aa51e2 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -100,6 +100,19 @@ public static void ErrorEnd(string text) } } } + + // Should happen before EndGame messages is sent + public static void NotifyGameEnding() + { + if (!AmongUsClient.Instance.AmHost) return; + foreach (var player in Main.AllPlayerControls.Where(x => x.GetClient() != null && !x.Data.Disconnected)) + { + var writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SendChat, SendOption.Reliable, player.OwnerId); + writer.Write(GetString("NotifyGameEnding")); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + } + public static ClientData GetClientById(int id) { try diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index e258f3c2cb..ecf950b329 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -262,12 +262,14 @@ public static bool Prefix(ChatController __instance) case "crew": case "tripulante": GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); break; case "imp": case "impostor": GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); break; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index b91ac1f29e..f996862d77 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -526,6 +526,7 @@ void SetGhostRole(bool ToGhostImpostor) yield return new WaitForSeconds(0.3f); } + Utils.NotifyGameEnding(); // Update all Notify Roles Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index d6dd8b2c3f..075124e66c 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -471,6 +471,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client { CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); }, 3f, "Disconnect Error Auto-end"); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d72af7fe63..3d599cdba8 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2380,6 +2380,7 @@ "DefaultSystemMessageTitle": "SYSTEM MESSAGE", "MessageFromTheHost": "HOST MESSAGE", "MessageFromEAC": "EAC", + "NotifyGameEnding": "This game is ending.\nIf you find yourself stuck in game,\npls just quit and rejoin the room.", "DetectiveNoticeTitle": "INVESTIGATION", "SleuthNoticeTitle": "SLEUTH", "GuessKillTitle": "GUESSING INFO", From b6de1e1d03a2289ba724347bd1aac4adf1552597 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:17:08 +0800 Subject: [PATCH 640/778] Move before gamedata sync --- Patches/CheckGameEndPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index f996862d77..6a542a7362 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -505,6 +505,7 @@ void SetGhostRole(bool ToGhostImpostor) // Remember true win to display in chat SetEverythingUpPatch.LastWinsReason = winner is CustomWinner.Crewmate or CustomWinner.Impostor ? GetString($"GameOverReason.{reason}") : ""; + Utils.NotifyGameEnding(); // Delay to ensure that resuscitation is delivered after the ghost roll setting yield return new WaitForSeconds(0.2f); @@ -526,7 +527,6 @@ void SetGhostRole(bool ToGhostImpostor) yield return new WaitForSeconds(0.3f); } - Utils.NotifyGameEnding(); // Update all Notify Roles Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); From 7be9c7a4cf659b0e4d9705eb1dbfae4a08e8b03e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 19:39:46 +0800 Subject: [PATCH 641/778] Some fix --- Modules/DelayNetworkedData.cs | 2 +- Modules/Utils.cs | 2 +- Roles/Core/RoleBase.cs | 2 +- Roles/Crewmate/CopyCat.cs | 4 +++- Roles/Neutral/Baker.cs | 2 ++ Roles/Neutral/Berserker.cs | 2 ++ Roles/Neutral/Lawyer.cs | 4 +++- Roles/Neutral/SoulCollector.cs | 9 +++++++-- 8 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index a27e99b566..44b2720c8f 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -171,7 +171,7 @@ public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjP public static void FixedUpdatePostfix(InnerNetClient __instance) { // Just send with None calls. Who cares? - if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return; + if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance == null || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (!__instance.AmHost || __instance.Streams == null) return; /* diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f8a43b0702..85146f52b4 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1735,7 +1735,7 @@ public static bool CheckCamoflague(this PlayerControl PC) => Camouflage.IsCamouf || (Main.CheckShapeshift.TryGetValue(PC.PlayerId, out bool isShapeshifitng) && isShapeshifitng); public static PlayerControl GetPlayerById(int PlayerId) { - return Main.AllPlayerControls.FirstOrDefault(pc => pc.PlayerId == PlayerId); + return Main.AllPlayerControls.FirstOrDefault(pc => pc.PlayerId == PlayerId) ?? null; } public static PlayerControl GetPlayer(this byte id) => GetPlayerById(id); public static List GetPlayerListByIds(this IEnumerable PlayerIdList) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index 143879da38..d9b0c921f3 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -10,7 +10,7 @@ public abstract class RoleBase { public PlayerState _state; #pragma warning disable IDE1006 - public PlayerControl _Player => Utils.GetPlayerById(_state.PlayerId); + public PlayerControl _Player => Utils.GetPlayerById(_state.PlayerId) ?? null; public List _playerIdList => Main.PlayerStates.Values.Where(x => x.MainRole == _state.MainRole).Select(x => x.PlayerId).Cast().ToList(); #pragma warning restore IDE1006 diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 26cba176c5..d930802362 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -56,7 +56,9 @@ public static void UnAfterMeetingTasks() foreach (var playerId in playerIdList.ToArray()) { var pc = playerId.GetPlayer(); - if (pc != null && !pc.IsAlive()) + if (pc == null) continue; + + if (!pc.IsAlive()) { if (!pc.HasGhostRole()) { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index b276809ec7..72a40eaebc 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -259,6 +259,8 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT if (lowLoad || !AllHasBread(player) || player.Is(CustomRoles.Famine)) return; player.RpcSetCustomRole(CustomRoles.Famine); + player.GetRoleClass()?.OnAdd(_Player.PlayerId); + player.Notify(GetString("BakerToFamine")); player.RpcGuardAndKill(player); } diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index f8525a1473..463d7e3122 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -172,6 +172,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (BerserkerKillMax[killer.PlayerId] >= BerserkerImmortalLevel.GetInt() && BerserkerFourCanNotKill.GetBool()&& !killer.Is(CustomRoles.War)) { killer.RpcSetCustomRole(CustomRoles.War); + killer.GetRoleClass()?.OnAdd(killer.PlayerId); + killer.Notify(GetString("BerserkerToWar")); Main.AllPlayerKillCooldown[killer.PlayerId] = WarKillCooldown.GetFloat(); } diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index 71b11acba8..b63113f870 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -169,11 +169,13 @@ public override bool KnowRoleTarget(PlayerControl player, PlayerControl target) public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { + if (TargetId == byte.MaxValue) return string.Empty; + if ((!seer.IsAlive() || seer.Is(CustomRoles.Lawyer)) && IsTarget(target.PlayerId)) { return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "♦"); } - else if (seer.IsAlive() && TargetKnowsLawyer.GetBool() && IsTarget(seer.PlayerId)) + else if (seer.IsAlive() && TargetKnowsLawyer.GetBool() && IsTarget(seer.PlayerId) && _state.PlayerId == target.PlayerId) { return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "♦"); } diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index bbd626ee41..c823fff6ae 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -97,7 +97,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override void OnReportDeadBody(PlayerControl ryuak, NetworkedPlayerInfo iscute) { - if (!_Player.IsAlive() || !GetPassiveSouls.GetBool()) return; + if (_Player == null || !_Player.IsAlive() || !GetPassiveSouls.GetBool()) return; AbilityLimit++; SendRPC(); @@ -110,7 +110,7 @@ public override void OnMeetingHudStart(PlayerControl pc) } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { - if (!_Player.IsAlive()) return; + if (_Player == null || !_Player.IsAlive()) return; if (TargetId == byte.MaxValue) return; var playerId = _Player.PlayerId; @@ -134,7 +134,10 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i if (AbilityLimit >= SoulCollectorPointsOpt.GetInt() && !inMeeting) { PlayerControl sc = _Player; + sc.RpcSetCustomRole(CustomRoles.Death); + sc.GetRoleClass()?.OnAdd(sc.PlayerId); + sc.Notify(GetString("SoulCollectorToDeath")); sc.RpcGuardAndKill(sc); } @@ -147,6 +150,8 @@ public override void AfterMeetingTasks() if (AbilityLimit >= SoulCollectorPointsOpt.GetInt() && !_Player.Is(CustomRoles.Death)) { _Player.RpcSetCustomRole(CustomRoles.Death); + _Player.GetRoleClass()?.OnAdd(_Player.PlayerId); + _Player.Notify(GetString("SoulCollectorToDeath")); _Player.RpcGuardAndKill(_Player); } From 834ba6e1e489d398a6695ef02603e93184cae2cd Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 19:52:23 +0800 Subject: [PATCH 642/778] Fix --- Patches/IntroPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 66 ++++++++++------------------------- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d7f4d53984..11e1e4a016 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -25,7 +25,7 @@ public static void Prefix() { if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) { - StartGameHostPatch.RpcSetDisconnected(disconnected: false); + StartGameHostPatch.RpcSetDisconnected(disconnected: false, forced: true); DestroyableSingleton.Instance.SetHudActive(true); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index d38eed452f..359d248b9b 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -519,7 +519,7 @@ public static System.Collections.IEnumerator AssignRoles() Logger.Info("Send rpc disconnected for all", "AssignRoleTypes"); DataDisconnected.Clear(); - RpcSetDisconnected(disconnected: true); + RpcSetDisconnected(disconnected: true, forced: false); yield return new WaitForSeconds(4f); @@ -622,42 +622,11 @@ private static void SetRoleSelf(PlayerControl target) roleType = RpcSetRoleReplacer.StoragedData[target.PlayerId]; } - // For host - //if (AmongUsClient.Instance.ClientId == targetClientId) - //{ - // // canOverride should be false for the host during assign - // target.SetRole(roleType, true); - // return; - //} - target.RpcSetRoleDesync(roleType, targetClientId); - - // For vanilla clients - //var stream = MessageWriter.Get(SendOption.None); - //stream.StartMessage(6); - //stream.Write(AmongUsClient.Instance.GameId); - //stream.WritePacked(targetClientId); - //{ - // RpcSetDisconnected(stream, true, true); - - // stream.StartMessage(2); - // stream.WritePacked(target.NetId); - // { - // stream.Write((byte)RpcCalls.SetRole); - // stream.Write((ushort)roleType); - // stream.Write(true); //canOverride - // } - // stream.EndMessage(); - - // RpcSetDisconnected(stream, false, true); - //} - //stream.EndMessage(); - //AmongUsClient.Instance.SendOrDisconnect(stream); - //stream.Recycle(); } private static readonly Dictionary DataDisconnected = []; - public static void RpcSetDisconnected(bool disconnected) + public static void RpcSetDisconnected(bool disconnected, bool forced) { foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) { @@ -676,23 +645,26 @@ public static void RpcSetDisconnected(bool disconnected) playerInfo.IsDead = data; } - /* - var stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(5); - stream.Write(AmongUsClient.Instance.GameId); + if (forced) { - stream.StartMessage(1); - stream.WritePacked(playerInfo.NetId); - playerInfo.Serialize(stream, false); + var stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(5); + stream.Write(AmongUsClient.Instance.GameId); + { + stream.StartMessage(1); + stream.WritePacked(playerInfo.NetId); + playerInfo.Serialize(stream, false); + stream.EndMessage(); + } stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); + } + else + { + // Let Delayed Networked Data send with delay. + playerInfo.SetDirtyBit(uint.MaxValue); } - stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); - */ - - // Let Delayed Networked Data send with delay. - playerInfo.SetDirtyBit(uint.MaxValue); } } From 2ffa17024b115d5274d085ba1e2851685b778908 Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Fri, 27 Sep 2024 20:44:58 +0800 Subject: [PATCH 643/778] Add Swapper Task String --- Roles/Crewmate/Swapper.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Roles/Crewmate/Swapper.cs b/Roles/Crewmate/Swapper.cs index 6f2d843b8d..51a40e1753 100644 --- a/Roles/Crewmate/Swapper.cs +++ b/Roles/Crewmate/Swapper.cs @@ -4,8 +4,10 @@ using TOHE.Modules.ChatManager; using TOHE.Roles.Core; using UnityEngine; +using System.Text; using static TOHE.Translator; using static TOHE.CheckForEndVotingPatch; +using static TOHE.Utils; namespace TOHE.Roles.Crewmate; @@ -49,7 +51,21 @@ public override void Add(byte playerId) AbilityLimit = SwapMax.GetInt(); } public override bool OnCheckStartMeeting(PlayerControl reporter) => OptCanStartMeeting.GetBool(); - public override string GetProgressText(byte PlayerId, bool comms) => Utils.ColorString((AbilityLimit > 0) ? Utils.GetRoleColor(CustomRoles.Swapper).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override string GetProgressText(byte playerId, bool comms) + { + + var ProgressText = new StringBuilder(); + var taskState8 = Main.PlayerStates?[playerId].TaskState; + Color TextColor8; + var TaskCompleteColor8 = Color.green; + var NonCompleteColor8 = Color.yellow; + var NormalColor8 = taskState8.IsTaskFinished ? TaskCompleteColor8 : NonCompleteColor8; + TextColor8 = comms ? Color.gray : NormalColor8; + string Completed8 = comms ? "?" : $"{taskState8.CompletedTasksCount}"; + ProgressText.Append(ColorString(TextColor8, $"({Completed8}/{taskState8.AllTasksCount})" + " ")); + ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Swapper).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})")); + return ProgressText.ToString(); + } public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) => IsForMeeting && seer.IsAlive() && target.IsAlive() ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Swapper), target.PlayerId.ToString()) + " " + TargetPlayerName : string.Empty; From 0ab7f78606f08ac99f6b0537c75c21f9e06196c1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 21:04:59 +0800 Subject: [PATCH 644/778] Check AntiBlackout --- Patches/PlayerControlPatch.cs | 2 +- Roles/Neutral/Shroud.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 47ad489ab9..f2ae205813 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1112,7 +1112,7 @@ public static Task DoPostfix(PlayerControl __instance) } - if (GameStates.IsInTask) + if (GameStates.IsInTask && !AntiBlackout.SkipTasks) { CustomRoleManager.OnFixedUpdate(player, lowLoad, Utils.GetTimeStamp()); diff --git a/Roles/Neutral/Shroud.cs b/Roles/Neutral/Shroud.cs index d8d6641aa7..8863610b72 100644 --- a/Roles/Neutral/Shroud.cs +++ b/Roles/Neutral/Shroud.cs @@ -148,27 +148,28 @@ public override void OnPlayerExiled(PlayerControl shroud, NetworkedPlayerInfo ex { ShroudList.Clear(); SendRPC(byte.MaxValue, byte.MaxValue, 0); - return; } + } + public override void AfterMeetingTasks() + { + if (_Player == null || !_Player.IsAlive()) return; foreach (var shroudedId in ShroudList.Keys) { - PlayerControl shrouded = Utils.GetPlayerById(shroudedId); + PlayerControl shrouded = shroudedId.GetPlayer(); if (!shrouded.IsAlive()) continue; shrouded.SetDeathReason(PlayerState.DeathReason.Shrouded); shrouded.RpcMurderPlayer(shrouded); - shrouded.SetRealKiller(shroud); + shrouded.SetRealKiller(_Player); - ShroudList.Remove(shrouded.PlayerId); SendRPC(byte.MaxValue, shrouded.PlayerId, 2); + ShroudList.Remove(shrouded.PlayerId); } } public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isMeeting = false) => isMeeting && ShroudList.ContainsKey(target.PlayerId) ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Shroud), "◈") : string.Empty; - - public override void SetAbilityButtonText(HudManager hud, byte playerId) { From 28795d2faadd08d70457ad48c9ff92e19eb43fe4 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 21:18:05 +0800 Subject: [PATCH 645/778] Check Game Is Ended --- Modules/ExtendedPlayerControl.cs | 2 +- Modules/GameState.cs | 2 +- Modules/Utils.cs | 4 ++-- Patches/CheckGameEndPatch.cs | 6 +++--- Patches/ControlPatch.cs | 2 +- Patches/ExilePatch.cs | 4 ++++ Patches/IntroPatch.cs | 36 +++++++++++++++----------------- Patches/onGameStartedPatch.cs | 4 ++-- 8 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e4a5a2568b..997d704442 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1220,7 +1220,7 @@ public static bool KnowLivingTeam(this PlayerControl seer, PlayerControl target) private readonly static LogHandler logger = Logger.Handler("KnowRoleTarget"); public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target) { - if (Options.CurrentGameMode == CustomGameMode.FFA || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) return true; + if (Options.CurrentGameMode == CustomGameMode.FFA || GameEndCheckerForNormal.GameIsEnded) return true; else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || (PlayerControl.LocalPlayer.PlayerId == seer.PlayerId && Main.GodMode.Value)) return true; else if (Options.SeeEjectedRolesInMeeting.GetBool() && Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote) return true; else if (Altruist.HasEnabled && seer.IsMurderedThisRound()) return false; diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 1bbc40dde1..15cf691d8f 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -435,7 +435,7 @@ public static class GameStates public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Joined; public static bool IsCoStartGame => !InGame && !DestroyableSingleton.InstanceExists; public static bool IsInGame => InGame; - public static bool IsEnded => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Ended; + public static bool IsEnded => AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.GameIsEnded; public static bool IsNotJoined => AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.NotJoined; public static bool IsOnlineGame => AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame; public static bool IsVanillaServer diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 7de9d90ad7..c23791b940 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1878,7 +1878,7 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon if (Main.AllPlayerControls == null) return; //Do not update NotifyRoles during meetings - if (GameStates.IsMeeting && !GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) return; + if (GameStates.IsMeeting && !GameEndCheckerForNormal.GameIsEnded) return; //var caller = new System.Diagnostics.StackFrame(1, false); //var callerMethod = caller.GetMethod(); @@ -1895,7 +1895,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl if (Main.AllPlayerControls == null) return Task.CompletedTask; //Do not update NotifyRoles during meetings - if (GameStates.IsMeeting && !GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) return Task.CompletedTask; + if (GameStates.IsMeeting && !GameEndCheckerForNormal.GameIsEnded) return Task.CompletedTask; //var logger = Logger.Handler("DoNotifyRoles"); diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 6a542a7362..af594d868e 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -38,7 +38,7 @@ public static bool Prefix(ref bool __result) class GameEndCheckerForNormal { public static GameEndPredicate predicate; - public static bool ShowAllRolesWhenGameEnd = false; + public static bool GameIsEnded = false; public static bool ShouldNotCheck = false; public static bool Prefix() @@ -49,7 +49,7 @@ public static bool Prefix() if (Options.NoGameEnd.GetBool() && WinnerTeam is not CustomWinner.Draw and not CustomWinner.Error) return false; - ShowAllRolesWhenGameEnd = false; + GameIsEnded = false; var reason = GameOverReason.ImpostorByKill; predicate.CheckForEndGame(out reason); @@ -75,7 +75,7 @@ public static bool Prefix() Main.AllPlayerControls.Do(pc => Camouflage.RpcSetSkin(pc, ForceRevert: true, RevertToDefault: true, GameEnd: true)); // Show all roles - ShowAllRolesWhenGameEnd = true; + GameIsEnded = true; // Update all Notify Roles Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index b70fbf0168..16b2863567 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -209,7 +209,7 @@ public static void Postfix(/*ControllerManager __instance*/) Utils.DoNotifyRoles(ForceLoop: true); CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); GameManager.Instance.LogicFlow.CheckEndCriteria(); - GameEndCheckerForNormal.ShowAllRolesWhenGameEnd = true; + GameEndCheckerForNormal.GameIsEnded = true; if (GameStates.IsHideNSeek) { GameEndCheckerForNormal.StartEndGame(GameOverReason.ImpostorDisconnect); diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index c19bffaa43..60491525ed 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -135,6 +135,8 @@ private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { _ = new LateTask(() => { + if (GameStates.IsEnded) return; + exiled = AntiBlackout_LastExiled; AntiBlackout.SendGameData(); AntiBlackout.SetRealPlayerRoles(); @@ -149,6 +151,8 @@ private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) _ = new LateTask(() => { + if (GameStates.IsEnded) return; + Main.AfterMeetingDeathPlayers.Do(x => { var player = x.Key.GetPlayer(); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 11e1e4a016..37677d97b0 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -23,16 +23,15 @@ public static void Prefix() _ = new LateTask(() => { - if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) - { - StartGameHostPatch.RpcSetDisconnected(disconnected: false, forced: true); + if (GameStates.IsEnded) return; - DestroyableSingleton.Instance.SetHudActive(true); + StartGameHostPatch.RpcSetDisconnected(disconnected: false, forced: true); - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - pc.SetCustomIntro(); - } + DestroyableSingleton.Instance.SetHudActive(true); + + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + pc.SetCustomIntro(); } }, 0.6f, "Set Disconnected"); @@ -40,23 +39,22 @@ public static void Prefix() { try { - if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd)) - { - // Assign tasks after assign all roles, as it should be - ShipStatus.Instance.Begin(); + if (GameStates.IsEnded) return; - GameOptionsSender.AllSenders.Clear(); - foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) - { - GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); - } + // Assign tasks after assign all roles, as it should be + ShipStatus.Instance.Begin(); - Utils.SyncAllSettings(); + GameOptionsSender.AllSenders.Clear(); + foreach (var pc in PlayerControl.AllPlayerControls.GetFastEnumerator()) + { + GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); } + + Utils.SyncAllSettings(); } catch { - Logger.Warn($"Game ended? {AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd}", "ShipStatus.Begin"); + Logger.Warn($"Game ended? {GameStates.IsEnded}", "ShipStatus.Begin"); } }, 4f, "Assing Task For All"); } diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 359d248b9b..2e6743b891 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -86,7 +86,7 @@ public static void Postfix(AmongUsClient __instance) Main.IntroDestroyed = false; GameEndCheckerForNormal.ShouldNotCheck = false; GameEndCheckerForNormal.ForEndGame = false; - GameEndCheckerForNormal.ShowAllRolesWhenGameEnd = false; + GameEndCheckerForNormal.GameIsEnded = false; GameStartManagerPatch.GameStartManagerUpdatePatch.AlredyBegin = false; VentSystemDeterioratePatch.LastClosestVent.Clear(); @@ -357,7 +357,7 @@ public static System.Collections.IEnumerator StartGameHost() public static System.Collections.IEnumerator AssignRoles() { - if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndCheckerForNormal.ShowAllRolesWhenGameEnd) yield break; + if (GameStates.IsEnded) yield break; try { From 02d1bf3cffa7693c976a3bf200f77c3fb4243811 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 21:21:07 +0800 Subject: [PATCH 646/778] Hm --- Roles/Crewmate/Swapper.cs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Roles/Crewmate/Swapper.cs b/Roles/Crewmate/Swapper.cs index 51a40e1753..3998fb993d 100644 --- a/Roles/Crewmate/Swapper.cs +++ b/Roles/Crewmate/Swapper.cs @@ -53,18 +53,17 @@ public override void Add(byte playerId) public override bool OnCheckStartMeeting(PlayerControl reporter) => OptCanStartMeeting.GetBool(); public override string GetProgressText(byte playerId, bool comms) { - - var ProgressText = new StringBuilder(); - var taskState8 = Main.PlayerStates?[playerId].TaskState; - Color TextColor8; - var TaskCompleteColor8 = Color.green; - var NonCompleteColor8 = Color.yellow; - var NormalColor8 = taskState8.IsTaskFinished ? TaskCompleteColor8 : NonCompleteColor8; - TextColor8 = comms ? Color.gray : NormalColor8; - string Completed8 = comms ? "?" : $"{taskState8.CompletedTasksCount}"; - ProgressText.Append(ColorString(TextColor8, $"({Completed8}/{taskState8.AllTasksCount})" + " ")); - ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Swapper).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})")); - return ProgressText.ToString(); + var ProgressText = new StringBuilder(); + var taskState8 = Main.PlayerStates?[playerId].TaskState; + Color TextColor8; + var TaskCompleteColor8 = Color.green; + var NonCompleteColor8 = Color.yellow; + var NormalColor8 = taskState8.IsTaskFinished ? TaskCompleteColor8 : NonCompleteColor8; + TextColor8 = comms ? Color.gray : NormalColor8; + string Completed8 = comms ? "?" : $"{taskState8.CompletedTasksCount}"; + ProgressText.Append(ColorString(TextColor8, $"({Completed8}/{taskState8.AllTasksCount}) ")); + ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Swapper).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})")); + return ProgressText.ToString(); } public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) From 5c0c496916f3d35d5e9c2baac47261abf1356d01 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 21:33:36 +0800 Subject: [PATCH 647/778] Check Camouflage in RpcRevive --- Modules/ExtendedPlayerControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 997d704442..86578442d6 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -87,6 +87,9 @@ public static void RpcRevive(this PlayerControl player) player.GetRoleClass().Add(player.PlayerId); } + if (Camouflage.IsCamouflage) + Camouflage.RpcSetSkin(player); + var customRole = player.GetCustomRole(); Main.PlayerStates[player.PlayerId].IsDead = false; Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; From e2d4082a1dc3943679545b85ef430bb1b21f281a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 27 Sep 2024 21:38:29 +0800 Subject: [PATCH 648/778] Alpha 15 Hotfix 1 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 5d64df0bd6..c969ef861a 100644 --- a/main.cs +++ b/main.cs @@ -42,12 +42,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0926.210.00150"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 15"; + public const string PluginVersion = "2024.0927.210.00151"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 15 Hotfix 1"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 14 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 15 Hotfix 1 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From a5670928c47871d37d0860f541c2e2985b777e81 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:09:51 +0800 Subject: [PATCH 649/778] Add more green bean kick --- Modules/DelayNetworkedData.cs | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index a27e99b566..ad5ff70e06 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -156,6 +156,46 @@ public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjP } */ + if (netObjParent is NetworkedPlayerInfo playerinfo) + { + _ = new LateTask(() => + { + if (playerinfo != null && AmongUsClient.Instance.AmConnected) + { + var client = AmongUsClient.Instance.GetClient(playerinfo.ClientId); + if (client != null && !client.IsDisconnected()) + { + if (playerinfo.IsIncomplete) + { + Logger.Info($"Disconnecting Client [{client.Id}]{client.PlayerName} {client.FriendCode} for playerinfo timeout", "DelayedNetworkedData"); + AmongUsClient.Instance.SendLateRejection(client.Id, DisconnectReasons.ClientTimeout); + __instance.OnPlayerLeft(client, DisconnectReasons.ClientTimeout); + } + } + } + }, 5f, "PlayerInfo Green Bean Kick", false); + } + + if (netObjParent is PlayerControl player) + { + _ = new LateTask(() => + { + if (player != null && !player.notRealPlayer && !player.isDummy && AmongUsClient.Instance.AmConnected) + { + var client = AmongUsClient.Instance.GetClient(player.OwnerId); + if (client != null && !client.IsDisconnected()) + { + if (player.Data == null || player.Data.IsIncomplete) + { + Logger.Info($"Disconnecting Client [{client.Id}]{client.PlayerName} {client.FriendCode} for playercontrol timeout", "DelayedNetworkedData"); + AmongUsClient.Instance.SendLateRejection(client.Id, DisconnectReasons.ClientTimeout); + __instance.OnPlayerLeft(client, DisconnectReasons.ClientTimeout); + } + } + } + }, 5.5f, "PlayerControl Green Bean Kick", false); + } + AmongUsClient.Instance.SendOrDisconnect(msg); } From eddbe77597cde5569e3ae02bc173de8f1619708b Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:58:08 +0800 Subject: [PATCH 650/778] Change certain delay --- Patches/PlayerJoinAndLeftPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index d6dd8b2c3f..a20f4ee3b9 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -225,7 +225,7 @@ public static void Postfix(/*AmongUsClient __instance,*/ [HarmonyArgument(0)] Cl } } catch { } - }, 3f, "green bean kick late task", false); + }, 4.5f, "green bean kick late task", false); if (AmongUsClient.Instance.AmHost && HasInvalidFriendCode(client.FriendCode) && Options.KickPlayerFriendCodeInvalid.GetBool() && !GameStates.IsLocalGame) From e3969b443b1abe44273b0cb144e8535d2efeb3cb Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 28 Sep 2024 16:32:31 +0800 Subject: [PATCH 651/778] Check null --- Modules/AntiBlackout.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 6519afd3d0..f797c359e0 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -100,6 +100,7 @@ private static void RevivePlayersAndSetDummyImp() if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; PlayerControl dummyImp = Main.AllAlivePlayerControls.FirstOrDefault(x => x.PlayerId != ExilePlayerId); + if (dummyImp == null) return; foreach (var seer in Main.AllPlayerControls) { From 2f464b93bfcc5d08b5662a896f8d68da1bd2c3cd Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 28 Sep 2024 16:43:18 +0800 Subject: [PATCH 652/778] Hotfix 2 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index c969ef861a..4caf0d7f27 100644 --- a/main.cs +++ b/main.cs @@ -42,12 +42,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0927.210.00151"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 15 Hotfix 1"; + public const string PluginVersion = "2024.0928.210.00152"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 15 Hotfix 2"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 15 Hotfix 1 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 15 Hotfix 2 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From de5a7c77e5bb577ea1fa41f8b4dc369310b1bf67 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 28 Sep 2024 17:48:35 +0800 Subject: [PATCH 653/778] Change --- Modules/RPC.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 9ee49a7b20..c03755f33c 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -18,7 +18,6 @@ enum CustomRPC : byte // 185/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 - BetterCheck = 150, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! VersionCheck = 80, RequestRetryVersionCheck = 81, SyncCustomSettings = 100, // AUM use 101 rpc @@ -74,7 +73,11 @@ enum CustomRPC : byte // 185/255 USED SetDrawPlayer, SetCrewpostorTasksDone, SetCurrentDrawTarget, - RpcPassBomb = 151, // BetterCheck used 150 + + // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! + BetterCheck = 150, + + RpcPassBomb, SyncRomanticTarget, SyncVengefulRomanticTarget, SetJailerTarget, From e2b3cfccfd5adf129ef2fdb5db915004008f50ec Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Sat, 28 Sep 2024 18:51:29 +0800 Subject: [PATCH 654/778] Add Option Judge Max Trials Per Game --- Patches/ChatCommandPatch.cs | 4 ++-- Resources/Lang/en_US.json | 1 + Roles/Crewmate/Judge.cs | 31 +++++++++++++++++++++++++------ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index ecf950b329..eb70458108 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -59,7 +59,7 @@ public static bool Prefix(ChatController __instance) if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Judge.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; @@ -1892,7 +1892,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; // if (SpamManager.CheckSpam(player, text)) return; if (GuessManager.GuesserMsg(player, text)) { canceled = true; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } - if (Judge.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3d599cdba8..3230e0d493 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1789,6 +1789,7 @@ "JudgeCanTrialContagious": "Can trial Contagious", "JudgeTryHideMsg": "Hide Judge's commands", "JudgeTrialLimitPerMeeting": "Max Trials per Meeting", + "JudgeTrialLimitPerGame": "Max Trials per Game", "JudgeCanTrialMadmate": "Can trial Madmates", "JudgeCanTrialCharmed": "Can trial Charmed players", "JudgeDead": "Sorry, you can't trial players after death.", diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 4e00af3a3e..2035bb6f84 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -6,6 +6,8 @@ using UnityEngine; using static TOHE.Utils; using static TOHE.Translator; +using TOHE.Roles.Core; +using System.Text; namespace TOHE.Roles.Crewmate; @@ -21,6 +23,7 @@ internal class Judge : RoleBase //==================================================================\\ public static OptionItem TrialLimitPerMeeting; + private static OptionItem TrialLimitPerGame; private static OptionItem TryHideMsg; private static OptionItem CanTrialMadmate; private static OptionItem CanTrialCharmed; @@ -41,6 +44,8 @@ public override void SetupCustomOption() Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Judge); TrialLimitPerMeeting = IntegerOptionItem.Create(Id + 10, "JudgeTrialLimitPerMeeting", new(1, 30, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) .SetValueFormat(OptionFormat.Times); + TrialLimitPerGame = IntegerOptionItem.Create(Id + 22, "JudgeTrialLimitPerGame", new(1, 30, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) + .SetValueFormat(OptionFormat.Times); CanTrialMadmate = BooleanOptionItem.Create(Id + 12, "JudgeCanTrialMadmate", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialCharmed = BooleanOptionItem.Create(Id + 16, "JudgeCanTrialCharmed", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialSidekick = BooleanOptionItem.Create(Id + 19, "JudgeCanTrialSidekick", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); @@ -64,6 +69,7 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); TrialLimit.Add(playerId, TrialLimitPerMeeting.GetInt()); + AbilityLimit = TrialLimitPerGame.GetInt(); } public override void Remove(byte playerId) { @@ -77,7 +83,7 @@ public override void OnReportDeadBody(PlayerControl party, NetworkedPlayerInfo d TrialLimit[pid] = TrialLimitPerMeeting.GetInt(); } } - public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) + public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) { var originMsg = msg; @@ -124,7 +130,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) { Logger.Info($"{pc.GetNameWithRole()} try trial {target.GetNameWithRole()}", "Judge"); bool judgeSuicide = true; - if (TrialLimit[pc.PlayerId] < 1) + if (TrialLimit[pc.PlayerId] < 1 || AbilityLimit < 1) { pc.ShowInfoMessage(isUI, GetString("JudgeTrialMax")); return true; @@ -199,7 +205,7 @@ public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) string Name = dp.GetRealName(); TrialLimit[pc.PlayerId]--; - + AbilityLimit--; if (!GameStates.IsProceeding) _ = new LateTask(() => @@ -287,7 +293,7 @@ private static void SendRPC(byte playerId) public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) { int PlayerId = reader.ReadByte(); - TrialMsg(pc, $"/tl {PlayerId}", true); + if (pc.GetRoleClass() is Judge judge) judge.TrialMsg(pc, $"/tl {PlayerId}", true); } private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) @@ -295,7 +301,7 @@ private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) Logger.Msg($"Click: ID {playerId}", "Judge UI"); var pc = GetPlayerById(playerId); if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; - if (AmongUsClient.Instance.AmHost) TrialMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); + if (AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.GetRoleClass() is Judge judge) judge.TrialMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); else SendRPC(playerId); } @@ -303,7 +309,20 @@ public override string NotifyPlayerName(PlayerControl seer, PlayerControl target => IsForMeeting && seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Judge), target.PlayerId.ToString()) + " " + TargetPlayerName : ""; public override string PVANameText(PlayerVoteArea pva, PlayerControl seer, PlayerControl target) => seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Judge), target.PlayerId.ToString()) + " " + pva.NameText.text : ""; - + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + var taskState8 = Main.PlayerStates?[playerId].TaskState; + Color TextColor8; + var TaskCompleteColor16 = Color.green; + var NonCompleteColor16 = Color.yellow; + var NormalColor8 = taskState8.IsTaskFinished ? TaskCompleteColor16 : NonCompleteColor16; + TextColor8 = comms ? Color.gray : NormalColor8; + string Completed8 = comms ? "?" : $"{taskState8.CompletedTasksCount}"; + ProgressText.Append(ColorString(TextColor8, $"({Completed8}/{taskState8.AllTasksCount})" + " ")); + ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Judge).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})" ?? "Invalid")); + return ProgressText.ToString(); + } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] class StartMeetingPatch { From 4376b911552e4aac3d34c33375249a7cbc3f66cb Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Sat, 28 Sep 2024 19:25:09 +0800 Subject: [PATCH 655/778] Fix --- Resources/Lang/en_US.json | 3 ++- Roles/Crewmate/Judge.cs | 24 +++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3230e0d493..df875ad95d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1793,7 +1793,8 @@ "JudgeCanTrialMadmate": "Can trial Madmates", "JudgeCanTrialCharmed": "Can trial Charmed players", "JudgeDead": "Sorry, you can't trial players after death.", - "JudgeTrialMax": "\nNo more trials left!", + "JudgeTrialMaxMeetingMsg": "\nNo more meeting trials left!", + "JudgeTrialMaxGameMsg": "\nNo more trials left!", "Judge_LaughToWhoTrialSelf": "God, I didn't think the Judges would be so blind that they wouldn't even see that they had sentenced themselves.", "Judge_TrialKill": "{0} was judged.", "Judge_TrialKillTitle": "COURT", diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 2035bb6f84..a4a3f24e24 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -37,7 +37,7 @@ internal class Judge : RoleBase private static OptionItem CanTrialNeutralC; private static OptionItem CanTrialNeutralA; - private static readonly Dictionary TrialLimit = []; + private static readonly Dictionary TrialLimitMeeting = []; public override void SetupCustomOption() { @@ -63,24 +63,24 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - TrialLimit.Clear(); + TrialLimitMeeting.Clear(); } public override void Add(byte playerId) { playerIdList.Add(playerId); - TrialLimit.Add(playerId, TrialLimitPerMeeting.GetInt()); + TrialLimitMeeting.Add(playerId, TrialLimitPerMeeting.GetInt()); AbilityLimit = TrialLimitPerGame.GetInt(); } public override void Remove(byte playerId) { playerIdList.Remove(playerId); - TrialLimit.Remove(playerId); + TrialLimitMeeting.Remove(playerId); } public override void OnReportDeadBody(PlayerControl party, NetworkedPlayerInfo dinosaur) { - foreach (var pid in TrialLimit.Keys) + foreach (var pid in TrialLimitMeeting.Keys) { - TrialLimit[pid] = TrialLimitPerMeeting.GetInt(); + TrialLimitMeeting[pid] = TrialLimitPerMeeting.GetInt(); } } public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) @@ -130,11 +130,15 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) { Logger.Info($"{pc.GetNameWithRole()} try trial {target.GetNameWithRole()}", "Judge"); bool judgeSuicide = true; - if (TrialLimit[pc.PlayerId] < 1 || AbilityLimit < 1) + if (TrialLimitMeeting[pc.PlayerId] < 1) { - pc.ShowInfoMessage(isUI, GetString("JudgeTrialMax")); + pc.ShowInfoMessage(isUI, GetString("JudgeTrialMaxMeetingMsg")); return true; } + if (AbilityLimit < 1) + { + pc.ShowInfoMessage(isUI, GetString("JudgeTrialMaxGameMsg")); + } if (Jailer.IsTarget(target.PlayerId)) { pc.ShowInfoMessage(isUI, GetString("CanNotTrialJailed"), ColorString(GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); @@ -204,8 +208,10 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) string Name = dp.GetRealName(); - TrialLimit[pc.PlayerId]--; + TrialLimitMeeting[pc.PlayerId]--; AbilityLimit--; + SendSkillRPC(); + if (!GameStates.IsProceeding) _ = new LateTask(() => From ab8346086fa644ba5e6ea90243d7f41cb59ef241 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sat, 28 Sep 2024 10:52:22 -0400 Subject: [PATCH 656/778] Fix mistakes again --- Resources/Lang/en_US.json | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 05ff18a294..923c349fb7 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -704,7 +704,7 @@ "NotAssignedInfo": "No assigned role", "SunnyboyInfo": "Shine, shine my sunshine!", "BardInfo": "Poem's grace, murder's trace, a rhythmic dance in a dark embrace.", - "RainbowInfo": "Colorful melodies! You don't even know your color.", + "RainbowInfo": "Colorful melodies! You don't even know your own color.", "DollMasterInfo": "Take control of players actions!", "DoubleAgentInfo": "Plant bombs on players in meetings", "SlothInfo": "You're slower", @@ -744,7 +744,7 @@ "RiftMakerInfoLong": "(Impostors):\nAs Rift Maker, you can shapeshift to create a rift. You can teleport from one rift to another by touching the area where the rift was created. Trying to vent will kick you out, therefore destroying all the rifts.\n\nNote: Up to two rifts can be placed at a time; if you try to place a third, it removes the first one.", "EvilTrackerInfoLong": "(Impostors):\nThe Evil Tracker can track other players, and the Evil Tracker can shapeshift into someone to switch the tracking target to the shapeshift target (You will immediately unshift after performing shapeshift). The arrow below the Evil Tracker's name indicates the direction of the target. When the Evil Tracker's teammate kills, the Evil Tracker will see a kill flash.", "EvilHackerInfoLong": "(Impostors):\nThe Evil Hacker can get the last-minute admin information at the meeting beginning.\nUnoccupied rooms are not shown.\nA '★' marks rooms with impostors.\nRooms with dead bodies are marked with the number of bodies.\nExample: ★Cafeteria: 3 (DEAD×1).", - "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If correct, the target dies; if it is wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", + "EvilGuesserInfoLong": "(Impostors):\nThe Evil Guesser can guess the role of a certain player during the meeting. If correct, the target dies. If wrong, the Evil Guesser dies.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are crewmates or neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their kill cooldown with each successful kill of theirs.", "BomberInfoLong": "(Impostors):\nThe Bomber can use the shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", @@ -757,7 +757,7 @@ "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the max of times you can counterattack)", "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can shapeshift to swap places with your target as long as they are not dead, in a vent, swallowed by pelican, or in a similar odd state.", "QuickShooterInfoLong": "(Impostors):\nWhen the kill cooldown is over, Quick Shooter can reset the kill cooldown by shapeshift to store a bullet (when the storage is successful, a shield-animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the kill cooldown; he will kill even if it's still on cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", - "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shape-shifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", + "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its shapeshifting. It's important to note that the skills of communication sabotage camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", "EraserInfoLong": "(Impostors):\nEraser can vote for any crew target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA crew target can only be erased once (include Oiiai)", "ButcherInfoLong": "(Impostors):\nThe Butcher's kills, including passive ones, leave multiple dead bodies on targets, which can be a bit confusing when reporting. Here's the rule: the killed target must repeatedly display the animation of being killed, which cannot be skipped, and they cannot participate in the meeting normally during this period. And if the Butcher kills the Avenger, the Avenger will revenge everyone in anger.", "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", @@ -887,11 +887,11 @@ "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", - "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote or by misguessing.\nYour presence is announced to everyone at the meeting after you transform.", + "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote them out or the Pestilence misguessing.\nYour presence is announced to everyone at the meeting after you transform.", "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", - "DeathInfoLong": "(Apocalypse): \nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", - "BakerInfoLong": "(Apocalypse): \nAs the Baker, you can use your kill button on a player per round to give them bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects and the setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after the meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after the meeting)", - "FamineInfoLong": "(Apocalypse): \nOnce the Baker has the set amount of people with bread alive, they will become Famine. Suppose Famine is not voted out after the meeting. Then they become Famine, and every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", + "DeathInfoLong": "(Apocalypse):\nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", + "BakerInfoLong": "(Apocalypse):\nAs the Baker, you can use your kill button on a player per round to give them bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects and the setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after the meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after the meeting)", + "FamineInfoLong": "(Apocalypse):\nOnce the Baker has a set amount of people with bread alive, they will become Famine. If the Famine does not get voted out after the meeting, then they will become Famine, and every player without bread will starve (excluding other Apocalypse members).\nAfter this starvation of everyone without bread, Famine can use their kill button to starve any remaining players, which will kill those players right before the next meeting.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", "BerserkerInfoLong": "(Apocalypse):\nAs the Berserker, you level up with each kill.\nUpon reaching a certain level defined by the Host, you unlock a new power.\n\nScavenged kills make your kills disappear.\nBombed kills make your kills explode. Be careful when killing, as this can kill your other Apocalypse members if they are near. \nAfter a certain level, you become War.", "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower kill cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill button on someone to start following them and can use the Kill button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", @@ -1005,7 +1005,7 @@ "TrickyInfoLong": "(Add-ons):\nAs the Tricky, your kills will have a random death reason.", "TiredInfoLong": "(Add-ons):\nWhenever Tired kills (or uses kill ability on) someone, alternatively whenever they finish a task, they will temporarily get lower vision & lower speed.", "StatueInfoLong": "(Add-ons):\nWhenever many people are near the Statue, the Statue is completely frozen or slowed down depending on the settings.", - "EvaderInfoLong": "(Add-ons):\nWhen the Evader gets voted out, there is a chance the Evader will not get ejected. (Chance set by the Host.)", + "EvaderInfoLong": "(Add-ons):\nWhen the Evader gets voted out, there is a chance they will not get ejected. (Chance set by the Host.)", "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Imposters, Neutrals, and or Crewmates will know if you die.", "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get madmate, charmed or so.", "OiiaiInfoLong": "(Add-ons):\nAs the Oiiai, when you die, you will make your killer forget their role.\nAdditionally, you may pass Oiiai on to the killer, depending on settings.", @@ -1021,6 +1021,7 @@ "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", + "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", From e489a0c92c194c5860e0dcb4c6dbbd0e8c130129 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 04:42:04 +0800 Subject: [PATCH 657/778] Check null --- Roles/Neutral/SoulCollector.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index c823fff6ae..c5eb8cb157 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -74,6 +74,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { + if (_Player == null) return string.Empty; if (TargetId == target.PlayerId && seer.IsNeutralApocalypse() && seer.PlayerId != _Player.PlayerId) { return Utils.ColorString(Utils.GetRoleColor(CustomRoles.SoulCollector), "♠"); @@ -144,7 +145,7 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i } public override void AfterMeetingTasks() { - if (!_Player.IsAlive()) return; + if (_Player == null || !_Player.IsAlive()) return; TargetId = byte.MaxValue; if (AbilityLimit >= SoulCollectorPointsOpt.GetInt() && !_Player.Is(CustomRoles.Death)) From d90496db4b8a97f4ad10c7f91e258753df71233d Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:53:56 -0400 Subject: [PATCH 658/778] Mayor color now in Vindicator description, plus swapper description fix --- Resources/Lang/en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 923c349fb7..e9569aabb1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -765,7 +765,7 @@ "CrewpostorInfoLong": "(Team Impostor):\nYou kill the nearest player whenever you finish a task.", "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can shapeshift but cannot vent.\nWhen you kill, you temporarily become immune to attacks.", "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear as a crewmate to crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", - "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", + "VindicatorInfoLong": "(Impostors):\nAs the Vindicator, you have extra votes like a Mayor.", "StealthInfoLong": "(Impostors):\nWhen the Stealth kills, players in the same room are blinded for a short time.", "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", @@ -856,7 +856,7 @@ "LighterInfoLong": "(Crewmate):\nAs the Lighter, you can vent to increase your vision temporarily.\nYou have increased vision both when lights are not out and when lights are out.\nUse this power to catch sneaky killers!", "TaskManagerInfoLong": "(Crewmates):\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real-time.", "WitnessInfoLong": "(Crewmates):\nAs the Witness, when you use your kill button on someone, you will know if they killed in the last X seconds or not. (X depends on the settings).", - "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: You cannot swap yourself", + "SwapperInfoLong": "(Crewmates):\nAs the Swapper, you can swap votes in meetings.\n\nTo swap votes, use '/sw [playerID]' twice.\n\nPlayer IDs are displayed next to player names in meetings, but you can also use /id to get a list of all player IDs.\n\nNote: Depending on the Host's settings, you can exchange your own votes.", "NiceMiniInfoLong": "(Crewmates):\nAs a Nice Mini, your survival is crucial. You can't be killed until you grow up, and if you die or are evicted from the meeting before you grow up, everyone loses. This unique role adds a new dynamic to the game, where your survival is not just for your benefit but for the entire Crew's success.", "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their kill button on you (any ability used through the kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the kill button interaction is blocked, the player's cooldown will reset to 10s'", "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their kill cooldown set to 600s\n 4. Randomly avenge a player.", From 37ec93aad6aed1d5794c0d31827831313104bc94 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sat, 28 Sep 2024 18:36:45 -0400 Subject: [PATCH 659/778] Nothing --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e9569aabb1..9e2c70ab70 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -887,7 +887,7 @@ "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", - "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by vote them out or the Pestilence misguessing.\nYour presence is announced to everyone at the meeting after you transform.", + "PestilenceInfoLong": "(Apocalypse):\nAs Pestilence, you're an unstoppable machine.\nAny attack towards you will be reflected towards them.\nIndirect kills don't even kill you.\n\nOnly way to kill Pestilence is by voting them out or the Pestilence misguessing.\nYour presence is announced to everyone at the meeting after you transform.", "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your kill button on a player to predict their death. You will gain a soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of souls, you become Death. If the gain passive souls setting is enabled, you will gain a soul each meeting.", "DeathInfoLong": "(Apocalypse):\nOnce the Soul Collector has collected their needed souls, they become Death. Death kills everyone and wins if Death is not ejected by the end of the next meeting.\nA configurable amount of extra meeting time will be given on the meeting Death transforms to have more discussion to find Death.\n\nYou are invincible, and your presence is announced to everyone at the meeting after you transform.", "BakerInfoLong": "(Apocalypse):\nAs the Baker, you can use your kill button on a player per round to give them bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects and the setting is on, then you can vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Sets the target's kill cooldown to 999 (resets to normal after the meeting)\nBarrier: Gives the target a barrier that is only known to the Baker (barrier is removed after the meeting)", From 650758ed98f7ee0c8a9986a3dc5f0ea46e7aab56 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 12:00:16 +0800 Subject: [PATCH 660/778] Revert DelayNetworkedData --- Modules/DelayNetworkedData.cs | 140 ++++++++++++++-------------------- Patches/IntroPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 32 ++++---- 3 files changed, 70 insertions(+), 104 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index 0eb3982a0c..51997306d7 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -8,13 +8,11 @@ namespace TOHE.Modules.DelayNetworkDataSpawn; [HarmonyPatch(typeof(InnerNetClient))] public class InnerNetClientPatch { - //public static List DelayedSpawnPlayers = []; - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))] [HarmonyPrefix] public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId) { - if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return true; + if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true; // We make sure other stuffs like playercontrol and Lobby behavior is spawned properly // Then we spawn networked data for new clients MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); @@ -47,30 +45,37 @@ public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId __instance.SendOrDisconnect(messageWriter); messageWriter.Recycle(); } - DelayInitialSpawnPlayerInfo(__instance, clientId); + DelaySpawnPlayerInfo(__instance, clientId); return false; } - // InnerSloth vanilla officials send PlayerInfo in spilt reliable packets - private static void DelayInitialSpawnPlayerInfo(InnerNetClient __instance, int clientId) + private static void DelaySpawnPlayerInfo(InnerNetClient __instance, int clientId) { List players = GameData.Instance.AllPlayers.ToArray().ToList(); - foreach (var player in players) + // We send 5 players at a time to prevent too huge packet + while (players.Count > 0) { - if (player != null && player.ClientId != clientId && !player.Disconnected) - { - MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); - messageWriter.StartMessage(6); - messageWriter.Write(__instance.GameId); - messageWriter.WritePacked(clientId); + var batch = players.Take(5).ToList(); - __instance.WriteSpawnMessage(player, player.OwnerId, player.SpawnFlags, messageWriter); - messageWriter.EndMessage(); + MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); + messageWriter.StartMessage(6); + messageWriter.Write(__instance.GameId); + messageWriter.WritePacked(clientId); - __instance.SendOrDisconnect(messageWriter); - messageWriter.Recycle(); + foreach (var player in batch) + { + if (messageWriter.Length > 1600) break; + if (player != null && player.ClientId != clientId && !player.Disconnected) + { + __instance.WriteSpawnMessage(player, player.OwnerId, player.SpawnFlags, messageWriter); + } + players.Remove(player); } + messageWriter.EndMessage(); + // Logger.Info($"send delayed network data to {clientId} , size is {messageWriter.Length}", "SendInitialDataPrefix"); + __instance.SendOrDisconnect(messageWriter); + messageWriter.Recycle(); } } @@ -129,33 +134,14 @@ public static bool SendAllStreamedObjectsPrefix(InnerNetClient __instance, ref b } return false; } - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] - [HarmonyPrefix] - public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjParent, int ownerId = -2, SpawnFlags flags = SpawnFlags.None) + [HarmonyPostfix] + public static void Spawn_Postfix(InnerNetClient __instance, InnerNetObject netObjParent, int ownerId = -2, SpawnFlags flags = SpawnFlags.None) { - if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true; + if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (__instance.AmHost) { - ownerId = ((ownerId == -3) ? __instance.ClientId : ownerId); - MessageWriter msg = MessageWriter.Get(SendOption.Reliable); - msg.StartMessage(5); - msg.Write(__instance.GameId); - __instance.WriteSpawnMessage(netObjParent, ownerId, flags, msg); - msg.EndMessage(); - - //For unknow reason delaying playerinfo spawn here will make beans much easier to appear. - //Especially when spawning lots of players on game end - //Leaving these codes for further use. - /* - if (netObjParent is NetworkedPlayerInfo) - { - DelayedSpawnPlayers.Add(msg); - return false; - } - */ - if (netObjParent is NetworkedPlayerInfo playerinfo) { _ = new LateTask(() => @@ -185,7 +171,7 @@ public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjP var client = AmongUsClient.Instance.GetClient(player.OwnerId); if (client != null && !client.IsDisconnected()) { - if (player.Data == null || player.Data.IsIncomplete) + if (player.Data == null || player.Data.IsIncomplete) { Logger.Info($"Disconnecting Client [{client.Id}]{client.PlayerName} {client.FriendCode} for playercontrol timeout", "DelayedNetworkedData"); AmongUsClient.Instance.SendLateRejection(client.Id, DisconnectReasons.ClientTimeout); @@ -195,75 +181,61 @@ public static bool SpawnPrefix(InnerNetClient __instance, InnerNetObject netObjP } }, 5.5f, "PlayerControl Green Bean Kick", false); } - - AmongUsClient.Instance.SendOrDisconnect(msg); } if (__instance.AmClient) { Debug.LogError("Tried to spawn while not host:" + (netObjParent?.ToString())); } - return false; + return; } + + private static byte timer = 0; [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))] [HarmonyPostfix] public static void FixedUpdatePostfix(InnerNetClient __instance) { - // Just send with None calls. Who cares? - if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance == null || __instance.NetworkMode != NetworkModes.OnlineGame) return; + // Send a networked data pre 2 fixed update should be a good practice? + if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return; if (!__instance.AmHost || __instance.Streams == null) return; - /* - var delayedPlayers = DelayedSpawnPlayers.Take(2); - foreach (var msg in delayedPlayers) + if (timer == 0) { - AmongUsClient.Instance.SendOrDisconnect(msg); - msg.Recycle(); - DelayedSpawnPlayers.Remove(msg); + timer = 1; + return; } - if (DelayedSpawnPlayers.Count >= 2) return; - */ - - // We are serializing 2 Networked playerinfo maxium per fixed update - var players = GameData.Instance.AllPlayers.ToArray() - .Where(x => x.IsDirty) - .Take(2) - .ToList(); - - if (players != null) + var player = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(x => x.IsDirty); + if (player != null) { - foreach (var player in players) + timer = 0; + MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); + messageWriter.StartMessage(5); + messageWriter.Write(__instance.GameId); + messageWriter.StartMessage(1); + messageWriter.WritePacked(player.NetId); + try { - MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable); - messageWriter.StartMessage(5); - messageWriter.Write(__instance.GameId); - messageWriter.StartMessage(1); - messageWriter.WritePacked(player.NetId); - try + if (player.Serialize(messageWriter, false)) { - if (player.Serialize(messageWriter, false)) - { - messageWriter.EndMessage(); - } - else - { - messageWriter.CancelMessage(); - player.ClearDirtyBits(); - continue; - } messageWriter.EndMessage(); - __instance.SendOrDisconnect(messageWriter); - messageWriter.Recycle(); } - catch (Exception ex) + else { - Logger.Exception(ex, "FixedUpdatePostfix"); messageWriter.CancelMessage(); player.ClearDirtyBits(); - continue; + return; } + messageWriter.EndMessage(); + __instance.SendOrDisconnect(messageWriter); + messageWriter.Recycle(); + } + catch (Exception ex) + { + Logger.Exception(ex, "FixedUpdatePostfix"); + messageWriter.CancelMessage(); + player.ClearDirtyBits(); } } } @@ -280,4 +252,4 @@ public static bool Prefix() { return false; } -} +} \ No newline at end of file diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 37677d97b0..514eb9a7d6 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -25,7 +25,7 @@ public static void Prefix() { if (GameStates.IsEnded) return; - StartGameHostPatch.RpcSetDisconnected(disconnected: false, forced: true); + StartGameHostPatch.RpcSetDisconnected(disconnected: false); DestroyableSingleton.Instance.SetHudActive(true); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 2e6743b891..4e87c7f3fe 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -519,7 +519,7 @@ public static System.Collections.IEnumerator AssignRoles() Logger.Info("Send rpc disconnected for all", "AssignRoleTypes"); DataDisconnected.Clear(); - RpcSetDisconnected(disconnected: true, forced: false); + RpcSetDisconnected(disconnected: true); yield return new WaitForSeconds(4f); @@ -626,7 +626,7 @@ private static void SetRoleSelf(PlayerControl target) } private static readonly Dictionary DataDisconnected = []; - public static void RpcSetDisconnected(bool disconnected, bool forced) + public static void RpcSetDisconnected(bool disconnected) { foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) { @@ -645,26 +645,20 @@ public static void RpcSetDisconnected(bool disconnected, bool forced) playerInfo.IsDead = data; } - if (forced) + var stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(5); + stream.Write(AmongUsClient.Instance.GameId); { - var stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(5); - stream.Write(AmongUsClient.Instance.GameId); - { - stream.StartMessage(1); - stream.WritePacked(playerInfo.NetId); - playerInfo.Serialize(stream, false); - stream.EndMessage(); - } + stream.StartMessage(1); + stream.WritePacked(playerInfo.NetId); + playerInfo.Serialize(stream, false); stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); - } - else - { - // Let Delayed Networked Data send with delay. - playerInfo.SetDirtyBit(uint.MaxValue); } + stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); + + playerInfo.SetDirtyBit(uint.MaxValue); } } From 1b74771ef4dc48009b874d25327c20245c9b1154 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 12:17:50 +0800 Subject: [PATCH 661/778] Fix modded client not reset limit for Judge --- Roles/Crewmate/Judge.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index a4a3f24e24..6d9b656fd1 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -81,6 +81,7 @@ public override void OnReportDeadBody(PlayerControl party, NetworkedPlayerInfo d foreach (var pid in TrialLimitMeeting.Keys) { TrialLimitMeeting[pid] = TrialLimitPerMeeting.GetInt(); + SendRPC(pid, true); } } public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) @@ -211,6 +212,7 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) TrialLimitMeeting[pc.PlayerId]--; AbilityLimit--; SendSkillRPC(); + SendRPC(pc.PlayerId, true); if (!GameStates.IsProceeding) @@ -290,16 +292,28 @@ public static bool CheckCommond(ref string msg, string command, bool exact = tru return false; } - private static void SendRPC(byte playerId) + private static void SendRPC(byte playerId, bool syncLimit = false) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Judge, SendOption.Reliable, -1); writer.Write(playerId); + writer.Write(syncLimit); + writer.WritePacked(TrialLimitMeeting[playerId]); AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) { - int PlayerId = reader.ReadByte(); - if (pc.GetRoleClass() is Judge judge) judge.TrialMsg(pc, $"/tl {PlayerId}", true); + byte PlayerId = reader.ReadByte(); + var syncLimit = reader.ReadBoolean(); + + if (syncLimit) + { + var trialLimit = reader.ReadPackedInt32(); + TrialLimitMeeting[PlayerId] = trialLimit; + } + else if (pc.GetRoleClass() is Judge judge) + { + judge.TrialMsg(pc, $"/tl {PlayerId}", true); + } } private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) @@ -308,7 +322,7 @@ private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) var pc = GetPlayerById(playerId); if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; if (AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.GetRoleClass() is Judge judge) judge.TrialMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); - else SendRPC(playerId); + else SendRPC(playerId, false); } public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) From 020ceced63de96ccf0274885fa8d91fc52920c98 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 12:35:56 +0800 Subject: [PATCH 662/778] Alpha 16 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 4caf0d7f27..c941a96f2e 100644 --- a/main.cs +++ b/main.cs @@ -42,12 +42,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0928.210.00152"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 15 Hotfix 2"; + public const string PluginVersion = "2024.0929.210.00160"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 16"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 15 Hotfix 2 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 16 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 7bc0f721b2b7308742aef7c303f8590f3844aaa9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 14:27:31 +0800 Subject: [PATCH 663/778] Add GetProgressText for Troller --- Roles/Neutral/Troller.cs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 9d447670a8..4939649344 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -1,8 +1,11 @@ using AmongUs.GameOptions; +using UnityEngine; +using System.Text; using TOHE.Modules; using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; +using static TOHE.Utils; namespace TOHE.Roles.Neutral; @@ -59,7 +62,7 @@ public override void Add(byte playerId) AllEvents = [.. EnumHelper.GetAllValues()]; - if (Utils.GetActiveMapName() is not (MapNames.Airship or MapNames.Polus or MapNames.Fungle)) + if (GetActiveMapName() is not (MapNames.Airship or MapNames.Polus or MapNames.Fungle)) { AllEvents.Remove(Events.AllDoorsOpen); AllEvents.Remove(Events.AllDoorsClose); @@ -89,12 +92,12 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun AbilityLimit--; - if (Utils.IsActive(SystemTypes.MushroomMixupSabotage) || Utils.IsActive(SystemTypes.Electrical)) + if (IsActive(SystemTypes.MushroomMixupSabotage) || IsActive(SystemTypes.Electrical)) { AllEvents.Remove(Events.SabotageActivated); AllEvents.Remove(Events.SabotageDisabled); } - else if (Utils.AnySabotageIsActive()) + else if (AnySabotageIsActive()) { AllEvents.Remove(Events.SabotageActivated); } @@ -119,7 +122,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun Main.AllPlayerSpeed[pcSpeed.PlayerId] = newSpeed; pcSpeed.Notify(GetString("Troller_ChangesSpeed")); } - Utils.MarkEveryoneDirtySettings(); + MarkEveryoneDirtySettings(); _ = new LateTask(() => { @@ -128,13 +131,13 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun Main.AllPlayerSpeed[pcSpeed.PlayerId] = Main.AllPlayerSpeed[pcSpeed.PlayerId] - newSpeed + tempSpeed[pcSpeed.PlayerId]; pcSpeed.Notify(GetString("Troller_SpeedOut")); } - Utils.MarkEveryoneDirtySettings(); + MarkEveryoneDirtySettings(); }, 10f, "Troller: Set Speed to default"); break; case Events.SabotageActivated: var shipStatusActivated = ShipStatus.Instance; List allSabotage = []; - switch ((MapNames)Utils.GetActiveMapId()) + switch (GetActiveMapName()) { case MapNames.Skeld: case MapNames.Dleks: @@ -186,7 +189,7 @@ public override bool OnTaskComplete(PlayerControl troller, int completedTaskCoun shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 67); break; case SystemTypes.Comms: - var mapId = Utils.GetActiveMapId(); + var mapId = GetActiveMapId(); shipStatusDisabled.RpcUpdateSystem(CurrentActiveSabotage, 16); if (mapId is 1 or 5) // Mira HQ or The Fungle @@ -297,4 +300,16 @@ SystemTypes.LifeSupp or CurrentActiveSabotage = systemType; } } + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + var taskState8 = Main.PlayerStates?[playerId].TaskState; + Color TextColor8; + var NonCompleteColor8 = Color.white; + TextColor8 = comms ? Color.gray : NonCompleteColor8; + string Completed8 = comms ? "?" : $"{taskState8.CompletedTasksCount}"; + ProgressText.Append(ColorString(TextColor8, $"({Completed8}/{taskState8.AllTasksCount}) ")); + ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Troller).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})")); + return ProgressText.ToString(); + } } From 76eb3547e059c704e0d9b9a24c1278b3946cc389 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 15:02:28 +0800 Subject: [PATCH 664/778] Change --- Roles/Neutral/Troller.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/Troller.cs b/Roles/Neutral/Troller.cs index 4939649344..da62d2bbcd 100644 --- a/Roles/Neutral/Troller.cs +++ b/Roles/Neutral/Troller.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using UnityEngine; +using System; using System.Text; using TOHE.Modules; using TOHE.Roles.Core; @@ -309,7 +310,7 @@ public override string GetProgressText(byte playerId, bool comms) TextColor8 = comms ? Color.gray : NonCompleteColor8; string Completed8 = comms ? "?" : $"{taskState8.CompletedTasksCount}"; ProgressText.Append(ColorString(TextColor8, $"({Completed8}/{taskState8.AllTasksCount}) ")); - ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Troller).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})")); + ProgressText.Append(ColorString((AbilityLimit > 0) ? GetRoleColor(CustomRoles.Troller).ShadeColor(0.25f) : Color.gray, $" - {Math.Round(AbilityLimit, 1)}")); return ProgressText.ToString(); } } From e07a9791c94f2d5c8e4b334f5716232c7847081f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 16:47:11 +0800 Subject: [PATCH 665/778] Fix duplicate id --- Roles/Crewmate/Judge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 6d9b656fd1..fb3e3112ac 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -44,7 +44,7 @@ public override void SetupCustomOption() Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Judge); TrialLimitPerMeeting = IntegerOptionItem.Create(Id + 10, "JudgeTrialLimitPerMeeting", new(1, 30, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) .SetValueFormat(OptionFormat.Times); - TrialLimitPerGame = IntegerOptionItem.Create(Id + 22, "JudgeTrialLimitPerGame", new(1, 30, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) + TrialLimitPerGame = IntegerOptionItem.Create(Id + 25, "JudgeTrialLimitPerGame", new(1, 30, 1), 1, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) .SetValueFormat(OptionFormat.Times); CanTrialMadmate = BooleanOptionItem.Create(Id + 12, "JudgeCanTrialMadmate", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialCharmed = BooleanOptionItem.Create(Id + 16, "JudgeCanTrialCharmed", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); From b86530111cebfa8378977c68e0af3d4096c1de7d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 18:29:38 +0800 Subject: [PATCH 666/778] Remove --- Patches/onGameStartedPatch.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 4e87c7f3fe..c8104caf70 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -657,8 +657,6 @@ public static void RpcSetDisconnected(bool disconnected) stream.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(stream); stream.Recycle(); - - playerInfo.SetDirtyBit(uint.MaxValue); } } From 6e3f8ea322f216ea47ea4d27581caf15f5fbf7e5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 19:27:48 +0800 Subject: [PATCH 667/778] Fix Jailer --- Roles/Crewmate/Jailer.cs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index bc9feb1633..9cbe2a4ab0 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -187,30 +187,29 @@ private static bool CanBeExecuted(CustomRoles role) (role.IsImpostorTeamV3())); } - public override void AfterMeetingTasks() + public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo exiled) { - foreach (var pid in JailerHasExe.Keys) + var playerId = player.PlayerId; + if (!JailerTarget.TryGetValue(playerId, out var targetId)) return; + + if (targetId != byte.MaxValue && JailerHasExe[playerId]) { - var targetId = JailerTarget[pid]; - if (targetId != byte.MaxValue && JailerHasExe[pid]) + var tpc = targetId.GetPlayer(); + if (tpc.IsAlive()) + { + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Execution, targetId); + tpc.SetRealKiller(player); + } + if (!CanBeExecuted(tpc.GetCustomRole())) { - var tpc = Utils.GetPlayerById(targetId); - if (tpc.IsAlive()) - { - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Execution, targetId); - tpc.SetRealKiller(Utils.GetPlayerById(pid)); - } - if (!CanBeExecuted(tpc.GetCustomRole())) - { - JailerExeLimit[pid] = 0; - SendRPC(pid, setTarget: false); - } + JailerExeLimit[playerId] = 0; + SendRPC(playerId, setTarget: false); } - JailerHasExe[pid] = false; - JailerTarget[pid] = byte.MaxValue; - JailerDidVote[pid] = false; - SendRPC(pid, byte.MaxValue, setTarget: true); } + JailerHasExe[playerId] = false; + JailerTarget[playerId] = byte.MaxValue; + JailerDidVote[playerId] = false; + SendRPC(playerId, byte.MaxValue, setTarget: true); } public override void SetAbilityButtonText(HudManager hud, byte id) { From 93c418e7f7ba6d1a779c135ec5f280762792b977 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 19:56:35 +0800 Subject: [PATCH 668/778] Check --- Roles/Crewmate/Jailer.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 9cbe2a4ab0..57016ed653 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -195,15 +195,18 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex if (targetId != byte.MaxValue && JailerHasExe[playerId]) { var tpc = targetId.GetPlayer(); - if (tpc.IsAlive()) + if (tpc != null) { - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Execution, targetId); - tpc.SetRealKiller(player); - } - if (!CanBeExecuted(tpc.GetCustomRole())) - { - JailerExeLimit[playerId] = 0; - SendRPC(playerId, setTarget: false); + if (tpc.IsAlive()) + { + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Execution, targetId); + tpc.SetRealKiller(player); + } + if (!CanBeExecuted(tpc.GetCustomRole())) + { + JailerExeLimit[playerId] = 0; + SendRPC(playerId, setTarget: false); + } } } JailerHasExe[playerId] = false; From 6d45ae659909258ca00fa9fef730ed83c26921f1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 29 Sep 2024 20:46:27 +0800 Subject: [PATCH 669/778] Hotfix 1 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index c941a96f2e..f712f3a36a 100644 --- a/main.cs +++ b/main.cs @@ -42,12 +42,12 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0929.210.00160"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 16"; + public const string PluginVersion = "2024.0929.210.00161"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Alpha 16 Hotfix 1"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 16 + public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 16 Hotfix 1 public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 public static readonly bool fullRelease = false; // Latest: V2.0.3 From 9a169450e370f1db78e336b0883c896ca7ab4475 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:07:11 -0400 Subject: [PATCH 670/778] Jester can't get Susceptible --- Modules/CustomRolesHelper.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index c2e89bd780..8bbc60e8a6 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1059,6 +1059,11 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Sloth)) return false; break; + + case CustomRoles.Susceptible: + if (pc.Is(CustomRoles.Jester)) + return false; + break; case CustomRoles.Sloth: if (pc.Is(CustomRoles.Swooper) From de565a54fd2cf750eefcd079100e583f4a75af73 Mon Sep 17 00:00:00 2001 From: This Dude <144048885+TheDiamondStar@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:52:50 -0400 Subject: [PATCH 671/778] Jester Ejection Message Option My very first ever pr, and let me say I've never done c# EVER. feel free to roast me if I make some huge mistake or something lol From here https://discord.com/channels/1094344790910455908/1287291177464365129 --- Roles/Neutral/Jester.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index a26858d438..c86fab60ee 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -20,6 +20,7 @@ internal class Jester : RoleBase private static OptionItem MeetingsNeededForJesterWin; private static OptionItem HideJesterVote; public static OptionItem SunnyboyChance; + public static OptionItem RevealJesterUponEjection; public override void SetupCustomOption() { @@ -38,6 +39,8 @@ public override void SetupCustomOption() SunnyboyChance = IntegerOptionItem.Create(Id + 7, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Percent); + RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, GeneralOption.RevealUponEject, false, TabGroup.NeutralRoles, true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) } public override void Init() { @@ -63,7 +66,11 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn { if (isMeetingHud) { - name = string.Format(Translator.GetString("ExiledJester"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + if (RevealJesterUponEjection) = false + { + name = string.Format(Translator.GetString("ExiledJester"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + DecidedWinner = true; + } DecidedWinner = true; } else From bf2938d7ea7d8f863c0a1357ad4b1a22e254ad9a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 15:54:00 +0800 Subject: [PATCH 672/778] Add dealy timer in loading Fix Antidote not spaw Double Agent add some rpc Fix players names not updates --- Modules/CustomRolesHelper.cs | 2 -- Modules/ExtendedPlayerControl.cs | 14 ++++++++------ Modules/Utils.cs | 10 +++++----- Patches/IntroPatch.cs | 5 +++-- Patches/PlayerJoinAndLeftPatch.cs | 2 +- Patches/onGameStartedPatch.cs | 12 ++++++------ Roles/AddOns/Common/Antidote.cs | 3 --- Roles/Crewmate/Admirer.cs | 2 +- Roles/Impostor/DoubleAgent.cs | 31 +++++++++++++++++++++---------- 9 files changed, 45 insertions(+), 36 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index c2e89bd780..fbbe830d71 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -644,8 +644,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c case CustomRoles.Antidote: if (pc.Is(CustomRoles.Diseased) || pc.Is(CustomRoles.Solsticer)) return false; - if ((pc.GetCustomRole().IsCrewmate() && !Antidote.CrewCanBeAntidote.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Antidote.NeutralCanBeAntidote.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Antidote.ImpCanBeAntidote.GetBool())) - return false; break; case CustomRoles.Diseased: diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 86578442d6..01ad6bf2b0 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -355,14 +355,16 @@ public static void RpcSetNamePrivate(this PlayerControl player, string name, Pla if (seer == null || player == null) return; + var leftPlayer = OnPlayerLeftPatch.LeftPlayerId; + if (seer.PlayerId == leftPlayer || player.PlayerId == leftPlayer) return; + var clientId = seer.GetClientId(); + if (clientId == -1) return; - var sender = CustomRpcSender.Create(name: $"SetNamePrivate"); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName, clientId) - .Write(seer.Data.NetId) - .Write(name) - .EndRpc(); - sender.SendMessage(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, clientId); + writer.Write(player.Data.NetId); + writer.Write(name); + AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void RpcEnterVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) { diff --git a/Modules/Utils.cs b/Modules/Utils.cs index c23791b940..a9c495c636 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -44,7 +44,7 @@ public static void ErrorEnd(string text) _ = new LateTask(() => { Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame")); - }, 6f, "Anti-Black Msg SendInGame Error During Loading"); + }, 8f, "Anti-Black Msg SendInGame Error During Loading"); if (GameStates.IsShip || !GameStates.IsLobby || GameStates.IsCoStartGame) { @@ -53,7 +53,7 @@ public static void ErrorEnd(string text) CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); GameManager.Instance.LogicFlow.CheckEndCriteria(); RPC.ForceEndGame(CustomWinner.Error); - }, 9f, "Anti-Black End Game As Critical Error"); + }, 11f, "Anti-Black End Game As Critical Error"); } else if (GameStartManager.Instance != null) { @@ -80,14 +80,14 @@ public static void ErrorEnd(string text) _ = new LateTask(() => { Logger.SendInGame(GetString("AntiBlackOutRequestHostToForceEnd")); - }, 6f, "Anti-Black Msg SendInGame Non-Host Modded Has Error During Loading"); + }, 8f, "Anti-Black Msg SendInGame Non-Host Modded Has Error During Loading"); } else { _ = new LateTask(() => { Logger.SendInGame(GetString("AntiBlackOutHostRejectForceEnd")); - }, 6f, "Anti-Black Msg SendInGame Host Reject Force End"); + }, 8f, "Anti-Black Msg SendInGame Host Reject Force End"); _ = new LateTask(() => { @@ -96,7 +96,7 @@ public static void ErrorEnd(string text) AmongUsClient.Instance.ExitGame(DisconnectReasons.Custom); Logger.Fatal($"Error: {text} - Disconnected from the game due critical error", "Anti-black"); } - }, 11f, "Anti-Black Exit Game Due Critical Error"); + }, 13f, "Anti-Black Exit Game Due Critical Error"); } } } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 514eb9a7d6..65c005e80a 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -155,6 +155,8 @@ public static void Postfix(IntroCutscene __instance) var realName = Main.AllPlayerNames[PlayerControl.LocalPlayer.PlayerId]; // Don't use RpcSetName because the modded client needs to set the name locally PlayerControl.LocalPlayer.SetName(realName); + + Utils.DoNotifyRoles(NoCache: true); }, 1f, "Reset Name For Modded Players"); } private static byte[] EncryptDES(byte[] data, string key) @@ -586,8 +588,6 @@ public static void Prefix() Main.GameIsLoaded = true; }, 3f, "Set UnShapeShift Button"); } - - Utils.DoNotifyRoles(NoCache: true); } } public static void Postfix() @@ -673,6 +673,7 @@ public static void Postfix() Utils.CheckAndSetVentInteractions(); } + Utils.DoNotifyRoles(NoCache: true); Logger.Info("OnDestroy", "IntroCutscene"); } } diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 25a4cb8f92..eea8825540 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -303,7 +303,7 @@ public static void Postfix(/*AmongUsClient __instance,*/ [HarmonyArgument(0)] Cl class OnPlayerLeftPatch { public static bool StartingProcessing = false; - public static byte LeftPlayerId; + public static byte LeftPlayerId = byte.MaxValue; static void Prefix([HarmonyArgument(0)] ClientData data) { StartingProcessing = true; diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index c8104caf70..de8d5d19bd 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -88,7 +88,7 @@ public static void Postfix(AmongUsClient __instance) GameEndCheckerForNormal.ForEndGame = false; GameEndCheckerForNormal.GameIsEnded = false; GameStartManagerPatch.GameStartManagerUpdatePatch.AlredyBegin = false; - + OnPlayerLeftPatch.LeftPlayerId = byte.MaxValue; VentSystemDeterioratePatch.LastClosestVent.Clear(); ChatManager.ResetHistory(); @@ -312,11 +312,11 @@ public static System.Collections.IEnumerator StartGameHost() thiz.Spawn(ShipStatus.Instance, -2, SpawnFlags.None); } float timer = 0f; - for (; ; ) + while (true) { bool stopWaiting = true; int maxTimer = 10; - if (GameOptionsManager.Instance.CurrentGameOptions.MapId == 5 || GameOptionsManager.Instance.CurrentGameOptions.MapId == 4) + if (GameOptionsManager.Instance.CurrentGameOptions.MapId is 4 or 5) { maxTimer = 15; } @@ -503,7 +503,7 @@ public static System.Collections.IEnumerator AssignRoles() } EAC.LogAllRoles(); - Utils.CountAlivePlayers(sendLog: true, checkGameEnd: false); + //Utils.CountAlivePlayers(sendLog: true, checkGameEnd: false); Logger.Msg("Ended", "AssignRoles"); } @@ -515,13 +515,13 @@ public static System.Collections.IEnumerator AssignRoles() } Logger.Info("Others assign finished", "AssignRoleTypes"); - yield return new WaitForSeconds(1f); + yield return new WaitForSeconds(GameStates.IsLocalGame ? 1f : 2f); Logger.Info("Send rpc disconnected for all", "AssignRoleTypes"); DataDisconnected.Clear(); RpcSetDisconnected(disconnected: true); - yield return new WaitForSeconds(4f); + yield return new WaitForSeconds(GameStates.IsLocalGame ? 2f : 4f); Logger.Info("Assign self", "AssignRoleTypes"); SetRoleSelf(); diff --git a/Roles/AddOns/Common/Antidote.cs b/Roles/AddOns/Common/Antidote.cs index 129eb40811..932e1ae8a2 100644 --- a/Roles/AddOns/Common/Antidote.cs +++ b/Roles/AddOns/Common/Antidote.cs @@ -8,9 +8,6 @@ public class Antidote : IAddon public static bool IsEnable = false; public AddonTypes Type => AddonTypes.Mixed; - public static OptionItem ImpCanBeAntidote; - public static OptionItem CrewCanBeAntidote; - public static OptionItem NeutralCanBeAntidote; private static OptionItem AntidoteCDOpt; private static OptionItem AntidoteCDReset; diff --git a/Roles/Crewmate/Admirer.cs b/Roles/Crewmate/Admirer.cs index 280d5d4eaf..f6a3a6cf1b 100644 --- a/Roles/Crewmate/Admirer.cs +++ b/Roles/Crewmate/Admirer.cs @@ -42,7 +42,7 @@ public override void Init() public override void Add(byte playerId) { AbilityLimit = SkillLimit.GetInt(); - AdmiredList.Add(playerId, []); + AdmiredList[playerId] = []; } public override void Remove(byte playerId) { diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 00fd955ea7..ce704523be 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -23,7 +23,7 @@ internal class DoubleAgent : RoleBase //==================================================================\\ private static readonly List createdButtonsList = []; private static readonly HashSet CurrentBombedPlayers = []; - private static float CurrentBombedTime = float.MaxValue; + private static float CurrentBombedTime; public static bool BombIsActive = false; public static bool CanBombInMeeting = true; public static bool StartedWithMoreThanOneImp = false; @@ -70,7 +70,7 @@ public override void Init() ClearBomb(); playerIdList.Clear(); CurrentBombedPlayers.Clear(); - CurrentBombedTime = float.MaxValue; + CurrentBombedTime = -1; BombIsActive = false; StartedWithMoreThanOneImp = false; CanBombInMeeting = true; @@ -84,11 +84,12 @@ public override void Add(byte playerId) StartedWithMoreThanOneImp = true; } - public static void ClearBomb() + private void ClearBomb() { CurrentBombedPlayers.Clear(); - CurrentBombedTime = 999f; + CurrentBombedTime = -1; BombIsActive = false; + SendRPC(); } // On vent diffuse Bastion & Agitator Bomb if DoubleAgentCanDiffuseBombs is enabled. @@ -129,10 +130,10 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) if (!BombIsActive) { - if (target.GetCustomRole().GetCustomRoleTeam() == Custom_Team.Impostor) return false; + if (target.Is(Custom_Team.Impostor)) return false; if (voter == target) return false; - CurrentBombedTime = 999f; + CurrentBombedTime = -1; CurrentBombedPlayers.Add(target.PlayerId); BombIsActive = true; SendMessage(GetString("VoteHasReturned"), voter.PlayerId, title: ColorString(GetRoleColor(CustomRoles.DoubleAgent), string.Format(GetString("VoteAbilityUsed"), GetString("DoubleAgent")))); @@ -155,7 +156,7 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf if (ClearBombedOnMeetingCall.GetBool() && !CanUseAbilityInCalledMeeting.GetBool()) CanBombInMeeting = false; } else - CurrentBombedTime = 999f; + CurrentBombedTime = -1; } // If bomb is active set timer after meeting. @@ -163,6 +164,7 @@ public override void AfterMeetingTasks() { CurrentBombedTime = BombExplosionTimer.GetFloat() + 1f; CanBombInMeeting = true; + SendRPC(); } // Active bomb timer update and check. @@ -281,10 +283,12 @@ public override string GetLowerText(PlayerControl player, PlayerControl seen, bo } // Send bomb timer to Modded Clients when active. - private void SendRPC() + private void SendRPC(bool addData = false, byte targetId = byte.MaxValue) { var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.None, -1); writer.WriteNetObject(_Player); + writer.Write(addData); + writer.Write(targetId); writer.WritePacked((int)CurrentBombedTime); AmongUsClient.Instance.FinishRpcImmediately(writer); } @@ -292,7 +296,14 @@ private void SendRPC() // Receive and set bomb timer from Host when active. public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - CurrentBombedTime = reader.ReadPackedInt32(); + bool addData = reader.ReadBoolean(); + byte targetId = reader.ReadByte(); + var timer = reader.ReadPackedInt32(); + + if (addData) + CurrentBombedPlayers.Add(targetId); + + CurrentBombedTime = timer; } // Use button for Modded! @@ -331,7 +342,7 @@ private static void PlantBombOnClick(byte targetId /*, MeetingHud __instance*/) { if (BombIsActive) return; - CurrentBombedTime = 999f; + CurrentBombedTime = -1; CurrentBombedPlayers.Add(targetId); BombIsActive = true; } From d3ea6be5e84a0a810e308bbaae227da314d5e984 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 16:02:46 +0800 Subject: [PATCH 673/778] Fix errors --- Patches/ControlPatch.cs | 5 ----- Roles/Impostor/DoubleAgent.cs | 1 - 2 files changed, 6 deletions(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 16b2863567..0d80d79a14 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -174,11 +174,6 @@ public static void Postfix(/*ControllerManager __instance*/) { Utils.CopyCurrentSettings(); } - //Open the game directory - if (GetKeysDown(KeyCode.F10)) - { - System.Diagnostics.Process.Start(Environment.CurrentDirectory); - } // Show chat if (GetKeysDown(KeyCode.Return, KeyCode.C, KeyCode.LeftShift)) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index ce704523be..7fab68f270 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -67,7 +67,6 @@ public override void SetupCustomOption() } public override void Init() { - ClearBomb(); playerIdList.Clear(); CurrentBombedPlayers.Clear(); CurrentBombedTime = -1; From bf198630b6855da1ce6e54522fb38374c210b4ad Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 18:12:47 +0800 Subject: [PATCH 674/778] Fix Stalker - Sometimes Stalker can't win - Electrical Sabotage not calls correctly - Stalker always was count as NK --- Modules/CustomRolesHelper.cs | 2 +- Modules/RPC.cs | 4 -- Patches/CheckGameEndPatch.cs | 3 +- Roles/Neutral/Stalker.cs | 75 +++++++++++++----------------------- 4 files changed, 30 insertions(+), 54 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index cf6fa3181a..6572fe179b 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1213,7 +1213,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Cultist => CountTypes.Cultist, CustomRoles.HexMaster => CountTypes.HexMaster, CustomRoles.Necromancer => CountTypes.Necromancer, - CustomRoles.Stalker => !Stalker.SnatchesWin.GetBool() ? CountTypes.Stalker : CountTypes.Crew, + CustomRoles.Stalker => Stalker.SnatchesWins ? CountTypes.Crew : CountTypes.Stalker, CustomRoles.Arsonist => Arsonist.CanIgniteAnytime() ? CountTypes.Arsonist : CountTypes.Crew, CustomRoles.Shroud => CountTypes.Shroud, CustomRoles.Werewolf => CountTypes.Werewolf, diff --git a/Modules/RPC.cs b/Modules/RPC.cs index c03755f33c..b9d0a8b612 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -109,7 +109,6 @@ enum CustomRPC : byte // 185/255 USED SyncAdmiredList, SyncAdmiredAbility, SetImitateLimit, - SetStalkerrKillCount, //FFA SyncFFAPlayer, SyncFFANameNotify, @@ -544,9 +543,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.LightningSetGhostPlayer: Lightning.ReceiveRPC(reader); break; - case CustomRPC.SetStalkerrKillCount: - Stalker.ReceiveRPC(reader); - break; case CustomRPC.SetGreedy: Greedy.ReceiveRPC(reader); break; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index af594d868e..289ce8e94c 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -178,9 +178,10 @@ public static bool Prefix() switch (pc.GetCustomRole()) { case CustomRoles.Stalker when pc.IsAlive() && ((WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorBySabotage)) || WinnerTeam == CustomWinner.Stalker - || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && Stalker.IsWinKill[pc.PlayerId] == true && Stalker.SnatchesWin.GetBool())): + || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && Stalker.IsWinKill[pc.PlayerId] && Stalker.SnatchesWins)): if (!CheckForConvertedWinner(pc.PlayerId)) { + reason = GameOverReason.ImpostorByKill; ResetAndSetWinner(CustomWinner.Stalker); WinnerIds.Add(pc.PlayerId); } diff --git a/Roles/Neutral/Stalker.cs b/Roles/Neutral/Stalker.cs index 4318fbadbf..e847636c72 100644 --- a/Roles/Neutral/Stalker.cs +++ b/Roles/Neutral/Stalker.cs @@ -1,6 +1,5 @@ using AmongUs.GameOptions; -using Hazel; -using InnerNet; +using TOHE.Roles.Core; namespace TOHE.Roles.Neutral; @@ -13,16 +12,16 @@ internal class Stalker : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => SnatchesWins ? Custom_RoleType.NeutralEvil : Custom_RoleType.NeutralKilling; //==================================================================\\ private static OptionItem KillCooldown; private static OptionItem HasImpostorVision; private static OptionItem CanVent; private static OptionItem CanCountNeutralKiller; - public static OptionItem SnatchesWin; + private static OptionItem SnatchesWin; - private static readonly Dictionary CurrentKillCooldown = []; + public static bool SnatchesWins = false; public static readonly Dictionary IsWinKill = []; public override void SetupCustomOption() @@ -39,66 +38,46 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - CurrentKillCooldown.Clear(); IsWinKill.Clear(); + SnatchesWins = SnatchesWin.GetBool(); } public override void Add(byte playerId) { playerIdList.Add(playerId); - CurrentKillCooldown.Add(playerId, KillCooldown.GetFloat()); IsWinKill[playerId] = false; - DRpcSetKillCount(Utils.GetPlayerById(playerId)); + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } - public static void ReceiveRPC(MessageReader msg) - { - byte StalkerrId = msg.ReadByte(); - bool IsKillerKill = msg.ReadBoolean(); - if (IsWinKill.ContainsKey(StalkerrId)) - IsWinKill[StalkerrId] = IsKillerKill; - else - IsWinKill.Add(StalkerrId, false); - Logger.Info($"Player{StalkerrId}:ReceiveRPC", "Stalker"); - } - private static void DRpcSetKillCount(PlayerControl player) - { - if (!AmongUsClient.Instance.AmHost) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetStalkerrKillCount, Hazel.SendOption.Reliable, -1); - writer.Write(player.PlayerId); - writer.Write(IsWinKill[player.PlayerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = CurrentKillCooldown[id]; + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl player) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl Ktarget) + public override void OnMurderPlayerAsKiller(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { - var targetRole = Ktarget.GetCustomRole(); - var succeeded = targetRole.IsImpostor(); - if (CanCountNeutralKiller.GetBool() && !Ktarget.Is(CustomRoles.Arsonist) && !Ktarget.Is(CustomRoles.Revolutionist)) + if (Utils.IsActive(SystemTypes.Electrical) || inMeeting || isSuicide) return; + + // Code from AU: SabotageSystemType.UpdateSystem switch SystemTypes.Electrical + byte switchId = 4; + for (int index = 0; index < 5; ++index) { - succeeded = succeeded || Ktarget.IsNeutralKiller(); + if (BoolRange.Next()) + switchId |= (byte)(1 << index); } - if (succeeded && SnatchesWin.GetBool()) - IsWinKill[killer.PlayerId] = true; - - DRpcSetKillCount(killer); - MessageWriter SabotageFixWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, killer.GetClientId()); - SabotageFixWriter.Write((byte)SystemTypes.Electrical); - MessageExtensions.WriteNetObject(SabotageFixWriter, killer); - AmongUsClient.Instance.FinishRpcImmediately(SabotageFixWriter); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Electrical, (byte)(switchId | 128U)); + } + private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) + { + if (_Player == null || !SnatchesWins) return; - foreach (var target in Main.AllPlayerControls) + var stalkerId = _Player.PlayerId; + var targetRole = target.GetCustomRole(); + var succeeded = targetRole.IsImpostor(); + if (CanCountNeutralKiller.GetBool() && !target.Is(CustomRoles.Arsonist) && !target.Is(CustomRoles.Revolutionist)) { - if (target.PlayerId == killer.PlayerId || target.Data.Disconnected) continue; - SabotageFixWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, target.GetClientId()); - SabotageFixWriter.Write((byte)SystemTypes.Electrical); - MessageExtensions.WriteNetObject(SabotageFixWriter, target); - AmongUsClient.Instance.FinishRpcImmediately(SabotageFixWriter); + succeeded = succeeded || target.IsNeutralKiller(); } - return true; + + if (succeeded) IsWinKill[stalkerId] = true; } } From 2104a151d949c680c22a2b0cd7a9ea587380f544 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 18:19:38 +0800 Subject: [PATCH 675/778] Monarch and Fragile --- Modules/CustomRolesHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 6572fe179b..c7c34e0c0f 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -619,6 +619,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Necromancer) || pc.Is(CustomRoles.Demon) || pc.Is(CustomRoles.Shaman) + || pc.Is(CustomRoles.Monarch) || pc.Is(CustomRoles.Opportunist) && Opportunist.OppoImmuneToAttacksWhenTasksDone.GetBool()) return false; break; From 628f418aa8cba7924ded7d4b085102c64b4311e2 Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Tue, 1 Oct 2024 18:48:25 +0800 Subject: [PATCH 676/778] Improve Unreported body --- Roles/Crewmate/Coroner.cs | 7 ++----- Roles/Impostor/Cleaner.cs | 7 +------ Roles/Neutral/Vulture.cs | 11 +++-------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/Roles/Crewmate/Coroner.cs b/Roles/Crewmate/Coroner.cs index 7c8fecd9a8..73231ef2ae 100644 --- a/Roles/Crewmate/Coroner.cs +++ b/Roles/Crewmate/Coroner.cs @@ -19,7 +19,6 @@ internal class Coroner : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ - private static readonly HashSet UnreportablePlayers = []; private static readonly Dictionary> CoronerTargets = []; private static OptionItem ArrowsPointingToDeadBody; @@ -42,7 +41,6 @@ public override void SetupCustomOption() } public override void Init() { - UnreportablePlayers.Clear(); CoronerTargets.Clear(); } public override void Add(byte playerId) @@ -81,7 +79,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) byte tid = reader.ReadByte(); if (!CoronerTargets.ContainsKey(pid)) CoronerTargets[pid] = []; CoronerTargets[pid].Add(tid); - if (opt == 1) UnreportablePlayers.Add(tid); + if (opt == 1) Main.UnreportableBodies.Add(tid); } } @@ -97,7 +95,6 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.ReportButton.OverrideText(GetString("CoronerReportButtonText")); public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { - if (UnreportablePlayers.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Coroner)) { @@ -133,7 +130,7 @@ private bool FindKiller(PlayerControl pc, NetworkedPlayerInfo deadBody, PlayerCo int operate = 0; if (LeaveDeadBodyUnreportable.GetBool()) { - UnreportablePlayers.Add(deadBody.PlayerId); + Main.UnreportableBodies.Add(deadBody.PlayerId); operate = 1; } SendRPCLimit(pc.PlayerId, operate, targetId: deadBody.PlayerId); diff --git a/Roles/Impostor/Cleaner.cs b/Roles/Impostor/Cleaner.cs index 65b9ad9441..341d5f0142 100644 --- a/Roles/Impostor/Cleaner.cs +++ b/Roles/Impostor/Cleaner.cs @@ -18,8 +18,6 @@ internal class Cleaner : RoleBase private static OptionItem KillCooldown; private static OptionItem KillCooldownAfterCleaning; - private static readonly HashSet CleanerBodies = []; - public override void SetupCustomOption() { Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Cleaner); @@ -32,7 +30,6 @@ public override void SetupCustomOption() } public override void Init() { - CleanerBodies.Clear(); Playerids.Clear(); } public override void Add(byte playerId) @@ -44,12 +41,10 @@ public override void Add(byte playerId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { - if (CleanerBodies.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Cleaner)) { - CleanerBodies.Remove(deadBody.PlayerId); - CleanerBodies.Add(deadBody.PlayerId); + Main.UnreportableBodies.Add(deadBody.PlayerId); reporter.Notify(Translator.GetString("CleanerCleanBody")); reporter.SetKillCooldownV3(KillCooldownAfterCleaning.GetFloat(), forceAnime: true); diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index fdf743476d..cbc6e1899e 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -25,8 +25,7 @@ internal class Vulture : RoleBase private static OptionItem VultureReportCD; private static OptionItem MaxEaten; private static OptionItem HasImpVision; - - private static readonly HashSet UnreportablePlayers = []; + private static readonly Dictionary BodyReportCount = []; private static readonly Dictionary AbilityLeftInRound = []; private static readonly Dictionary LastReport = []; @@ -45,7 +44,6 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - UnreportablePlayers.Clear(); BodyReportCount.Clear(); AbilityLeftInRound.Clear(); LastReport.Clear(); @@ -115,9 +113,6 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { - // Vulture was eat body - if (UnreportablePlayers.Contains(deadBody.PlayerId)) return false; - if (reporter.Is(CustomRoles.Vulture)) { var reporterId = reporter.PlayerId; @@ -169,8 +164,8 @@ private static void OnEatDeadBody(PlayerControl pc, NetworkedPlayerInfo target) } SendBodyRPC(pc.PlayerId); pc.Notify(GetString("VultureBodyReported")); - UnreportablePlayers.Remove(target.PlayerId); - UnreportablePlayers.Add(target.PlayerId); + Main.UnreportableBodies.Remove(target.PlayerId); + Main.UnreportableBodies.Add(target.PlayerId); } public override void AfterMeetingTasks() { From e532ac66d2ee9da95c7b0ba9f704f91205802e3a Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Tue, 1 Oct 2024 18:49:14 +0800 Subject: [PATCH 677/778] Fix Altrurist may revive some unreported body --- Roles/Crewmate/Altruist.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 6286891bb8..d4bd77d8d7 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -2,6 +2,8 @@ using Hazel; using InnerNet; using TOHE.Roles.Core; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; namespace TOHE.Roles.Crewmate; @@ -77,7 +79,7 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { if (deadBody == null || deadBody.Object == null) return true; - + if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) { if (!IsRevivingMode) return true; From 6c165552f8869e6bb2878c1d433a0adfe9999760 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 19:16:59 +0800 Subject: [PATCH 678/778] Fix Madmate Impostor Vision --- Modules/OptionHolder.cs | 1 - Roles/AddOns/Impostor/Madmate.cs | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index edc3e409b4..93036fc9b3 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -952,7 +952,6 @@ private static System.Collections.IEnumerator CoLoadOptions() foreach (var addon in addonType.Value) { - addon.SetupCustomOption(); } diff --git a/Roles/AddOns/Impostor/Madmate.cs b/Roles/AddOns/Impostor/Madmate.cs index 4aafcaee3b..f368b568da 100644 --- a/Roles/AddOns/Impostor/Madmate.cs +++ b/Roles/AddOns/Impostor/Madmate.cs @@ -66,7 +66,22 @@ public static void SetupCustomMenuOptions() JudgeCanBeMadmate = BooleanOptionItem.Create(Id2 + 13, "JudgeCanBeMadmate", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); } - public static void ApplyGameOptions(IGameOptions opt) => opt.SetVision(MadmateHasImpostorVision.GetBool()); + public static void ApplyGameOptions(IGameOptions opt) + { + if (MadmateHasImpostorVision.GetBool()) + { + var impVision = Main.RealOptionsData.GetFloat(FloatOptionNames.ImpostorLightMod); + if (Utils.IsActive(SystemTypes.Electrical)) + { + opt.SetFloat(FloatOptionNames.CrewLightMod, impVision * 5); + } + else + { + opt.SetFloat(FloatOptionNames.CrewLightMod, impVision); + } + opt.SetFloat(FloatOptionNames.ImpostorLightMod, impVision); + } + } private static readonly string[] madmateSpawnMode = [ From dac778adc307494a34c822748adbd14e79d9dd50 Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Tue, 1 Oct 2024 19:17:04 +0800 Subject: [PATCH 679/778] remove --- Roles/Crewmate/Altruist.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index d4bd77d8d7..47592aff0d 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -2,8 +2,6 @@ using Hazel; using InnerNet; using TOHE.Roles.Core; -using TOHE.Roles.Impostor; -using TOHE.Roles.Neutral; namespace TOHE.Roles.Crewmate; From be968b4ef3cf3d9b43564ff8c47f785c0ce70cac Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 19:22:05 +0800 Subject: [PATCH 680/778] Fix --- Roles/Impostor/Trapster.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Impostor/Trapster.cs b/Roles/Impostor/Trapster.cs index 7717390826..100645ca12 100644 --- a/Roles/Impostor/Trapster.cs +++ b/Roles/Impostor/Trapster.cs @@ -76,7 +76,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay // if reporter try reported trap body if (BoobyTrapBody.Contains(deadBody.PlayerId) && reporter.IsAlive() - && !reporter.IsTransformedNeutralApocalypse() && _Player.RpcCheckAndMurder(reporter, true)) + && !reporter.IsTransformedNeutralApocalypse() && (reporter.Is(CustomRoles.Veteran) || _Player.RpcCheckAndMurder(reporter, true))) { var killerId = deadBody.PlayerId; From 400351a2e2f73e5a051e52b6d4e01c532e2b5dff Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 19:33:57 +0800 Subject: [PATCH 681/778] Add Check Unreportable Bodies For Medusa --- Roles/Neutral/Medusa.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Roles/Neutral/Medusa.cs b/Roles/Neutral/Medusa.cs index 2770da576b..a954908499 100644 --- a/Roles/Neutral/Medusa.cs +++ b/Roles/Neutral/Medusa.cs @@ -43,15 +43,17 @@ public override void Add(byte playerId) public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) + public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { + if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; + if (reporter.Is(CustomRoles.Medusa)) { - Main.UnreportableBodies.Add(target.PlayerId); + Main.UnreportableBodies.Add(deadBody.PlayerId); reporter.Notify(GetString("MedusaStoneBody")); reporter.SetKillCooldownV3(KillCooldownAfterStoneGazing.GetFloat(), forceAnime: true); - Logger.Info($"{reporter.GetRealName()} stoned {target.PlayerName} body", "Medusa"); + Logger.Info($"{reporter.GetRealName()} stoned {deadBody.PlayerName} body", "Medusa"); return false; } return true; From dbe03b8511a9de9afe76c36df9046a53364af52e Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Tue, 1 Oct 2024 19:49:10 +0800 Subject: [PATCH 682/778] fix --- Roles/Crewmate/Altruist.cs | 2 ++ Roles/Crewmate/Coroner.cs | 1 + Roles/Impostor/Cleaner.cs | 1 + 3 files changed, 4 insertions(+) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 47592aff0d..72782cf457 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -2,6 +2,7 @@ using Hazel; using InnerNet; using TOHE.Roles.Core; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Crewmate; @@ -77,6 +78,7 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { if (deadBody == null || deadBody.Object == null) return true; + if (deadBody.Object.Is(CustomRoles.Unreportable)) return false; if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) { diff --git a/Roles/Crewmate/Coroner.cs b/Roles/Crewmate/Coroner.cs index 73231ef2ae..2dd952338d 100644 --- a/Roles/Crewmate/Coroner.cs +++ b/Roles/Crewmate/Coroner.cs @@ -95,6 +95,7 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.ReportButton.OverrideText(GetString("CoronerReportButtonText")); public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { + if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Coroner)) { diff --git a/Roles/Impostor/Cleaner.cs b/Roles/Impostor/Cleaner.cs index 341d5f0142..17748f9f3b 100644 --- a/Roles/Impostor/Cleaner.cs +++ b/Roles/Impostor/Cleaner.cs @@ -41,6 +41,7 @@ public override void Add(byte playerId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { + if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Cleaner)) { From 154bda2f10794641cd94aef9732a8607c98d27f0 Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Tue, 1 Oct 2024 19:54:20 +0800 Subject: [PATCH 683/778] fix ... --- Roles/Crewmate/Altruist.cs | 1 - Roles/Neutral/Medusa.cs | 2 ++ Roles/Neutral/Vulture.cs | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 72782cf457..472dc4a30e 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -78,7 +78,6 @@ public override void OnCoEnterVent(PlayerPhysics physics, int ventId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { if (deadBody == null || deadBody.Object == null) return true; - if (deadBody.Object.Is(CustomRoles.Unreportable)) return false; if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; if (reporter.Is(CustomRoles.Altruist) && _Player?.PlayerId == reporter.PlayerId) { diff --git a/Roles/Neutral/Medusa.cs b/Roles/Neutral/Medusa.cs index 2770da576b..a209be78bf 100644 --- a/Roles/Neutral/Medusa.cs +++ b/Roles/Neutral/Medusa.cs @@ -45,6 +45,8 @@ public override void Add(byte playerId) public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) { + if (Main.UnreportableBodies.Contains(target.PlayerId)) return false; + if (reporter.Is(CustomRoles.Medusa)) { Main.UnreportableBodies.Add(target.PlayerId); diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index cbc6e1899e..62752f20cf 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -5,6 +5,7 @@ using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Neutral; @@ -113,6 +114,8 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo deadBody, PlayerControl killer) { + if (Main.UnreportableBodies.Contains(deadBody.PlayerId)) return false; + if (reporter.Is(CustomRoles.Vulture)) { var reporterId = reporter.PlayerId; From b973c377e03a435cec99d1817cf86ca6aeb2323c Mon Sep 17 00:00:00 2001 From: This Dude <144048885+TheDiamondStar@users.noreply.github.com> Date: Tue, 1 Oct 2024 07:54:21 -0400 Subject: [PATCH 684/778] Fix mistakes --- Roles/Neutral/Jester.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index c86fab60ee..481ade5126 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -39,8 +39,8 @@ public override void SetupCustomOption() SunnyboyChance = IntegerOptionItem.Create(Id + 7, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Percent); - RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, GeneralOption.RevealUponEject, false, TabGroup.NeutralRoles, true) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) + RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, GeneralOption.RevealUponEject, true, TabGroup.NeutralRoles, true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); } public override void Init() { @@ -66,10 +66,9 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn { if (isMeetingHud) { - if (RevealJesterUponEjection) = false + if (RevealJesterUponEjection.GetBool()) = false { name = string.Format(Translator.GetString("ExiledJester"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); - DecidedWinner = true; } DecidedWinner = true; } From 8aeaa1b42d36ccdf67c388661a61ddbc3319f4c8 Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Tue, 1 Oct 2024 19:55:39 +0800 Subject: [PATCH 685/778] fix........ --- Roles/Crewmate/Altruist.cs | 1 - Roles/Neutral/Vulture.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 472dc4a30e..47592aff0d 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -2,7 +2,6 @@ using Hazel; using InnerNet; using TOHE.Roles.Core; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Crewmate; diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index 62752f20cf..7650fc97e8 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -5,7 +5,6 @@ using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Neutral; From 46ca7d96ec061574f4e9ca42a9b092224b27a4b7 Mon Sep 17 00:00:00 2001 From: TommyXL <104814436+Tommy-XL@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:17:00 +0800 Subject: [PATCH 686/778] Add Jester_RevealUponEject --- Resources/Lang/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d58b2eabb5..a4fb7e7b88 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1551,6 +1551,7 @@ "MayorHasPortableButton": "Mayor has a Mobile Emergency Button", "MayorNumOfUseButton": "Max Number of Mobile Emergency Buttons", "MeetingsNeededForWin": "Meetings needed to win", + "Jester_RevealUponEject": "Reveal Upon Eject", "CannotVoteWhenDead": "Cannot cast a vote while dead", "EnableVote": "Enable /vote command", "ShouldVoteSpam": "Try to hide /vote command", From 60403987f0c5ffd90fb2a3b219f9a45c9ca0b398 Mon Sep 17 00:00:00 2001 From: TommyXL <104814436+Tommy-XL@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:17:42 +0800 Subject: [PATCH 687/778] Add Jester_RevealUponEject in Jester.cs --- Roles/Neutral/Jester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 481ade5126..6963634625 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -39,7 +39,7 @@ public override void SetupCustomOption() SunnyboyChance = IntegerOptionItem.Create(Id + 7, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Percent); - RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, GeneralOption.RevealUponEject, true, TabGroup.NeutralRoles, true) + RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, "Jester_RevealUponEject", true, TabGroup.NeutralRoles, true) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); } public override void Init() From 1afae1382d57013c6dec12905eb0cae816e8b3f6 Mon Sep 17 00:00:00 2001 From: TommyXL <104814436+Tommy-XL@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:18:50 +0800 Subject: [PATCH 688/778] Remove = false --- Roles/Neutral/Jester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 6963634625..b15f34f4cc 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -66,7 +66,7 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn { if (isMeetingHud) { - if (RevealJesterUponEjection.GetBool()) = false + if (RevealJesterUponEjection.GetBool()) { name = string.Format(Translator.GetString("ExiledJester"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); } From 3d1a6ba6c6ac86d88501b1f1a11dccb8974a5566 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 20:23:59 +0800 Subject: [PATCH 689/778] Set private & Fix bug --- Roles/Neutral/Jester.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index 49a68c792f..0554c88806 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -21,7 +21,7 @@ internal class Jester : RoleBase private static OptionItem MeetingsNeededForWin; private static OptionItem HideJesterVote; public static OptionItem SunnyboyChance; - public static OptionItem RevealJesterUponEjection; + private static OptionItem RevealJesterUponEjection; private readonly HashSet RememberBlockedVents = []; @@ -41,11 +41,11 @@ public override void SetupCustomOption() MeetingsNeededForWin = IntegerOptionItem.Create(Id + 6, "MeetingsNeededForWin", new(0, 10, 1), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Times); + RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, "Jester_RevealUponEject", true, TabGroup.NeutralRoles, true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); SunnyboyChance = IntegerOptionItem.Create(Id + 7, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) .SetValueFormat(OptionFormat.Percent); - RevealJesterUponEjection = BooleanOptionItem.Create(Id + 8, "Jester_RevealUponEject", true, TabGroup.NeutralRoles, true) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); } public override void Init() { @@ -104,8 +104,8 @@ public override void CheckExile(NetworkedPlayerInfo exiled, ref bool DecidedWinn if (RevealJesterUponEjection.GetBool()) { name = string.Format(Translator.GetString("ExiledJester"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + DecidedWinner = true; } - DecidedWinner = true; } else { From 7e17a7d9efcba3308298d0fdd8e1c408d75b8827 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 1 Oct 2024 20:57:26 +0800 Subject: [PATCH 690/778] Add Client Options in logs --- Patches/IntroPatch.cs | 17 +++++++++++++++++ Roles/Core/AssignManager/RoleAssign.cs | 1 + 2 files changed, 18 insertions(+) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 65c005e80a..e48a5e7873 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -179,6 +179,23 @@ private static System.Collections.IEnumerator CoLoggerGameInfo() var allPlayerControlsArray = Main.AllPlayerControls; var sb = new StringBuilder(); + sb.Append("------------Client Options------------\n"); + sb.Append($"Game Master: {Main.EnableGM.Value}\n"); + sb.Append($"UnlockFPS: {Main.UnlockFPS.Value}\n"); + sb.Append($"Show FPS: {Main.ShowFPS.Value}\n"); + sb.Append($"Auto Start: {Main.AutoStart.Value}\n"); + sb.Append($"Dark Theme: {Main.DarkTheme.Value}\n"); + sb.Append($"Disable Lobby Music: {Main.DisableLobbyMusic.Value}\n"); + sb.Append($"Show Text Overlay: {Main.ShowTextOverlay.Value}\n"); + sb.Append($"Horse Mode: {Main.HorseMode.Value}\n"); + sb.Append($"Enable Custom Button: {Main.EnableCustomButton.Value}\n"); + sb.Append($"Enable Custom Sound Effect: {Main.EnableCustomSoundEffect.Value}\n"); + sb.Append($"Force Own Language: {Main.ForceOwnLanguage.Value}\n"); + sb.Append($"Force Own Language Role Name: {Main.ForceOwnLanguageRoleName.Value}\n"); + sb.Append($"Version Cheat: {Main.VersionCheat.Value}\n"); + sb.Append($"God Mode: {Main.GodMode.Value}\n"); + sb.Append($"Auto Rehost: {Main.AutoRehost.Value}\n"); + sb.Append("------------Player Names------------\n"); foreach (var pc in allPlayerControlsArray) { diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index a517e56189..db1c51e072 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -186,6 +186,7 @@ public static void StartSelect() // Players on the EAC banned list will be assigned as GM when opening rooms if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid())) { + Logger.Warn("Host presets in BanManager.CheckEACList", "EAC"); Main.EnableGM.Value = true; RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; AllPlayers.Remove(PlayerControl.LocalPlayer); From 5012b2ad57fe3c2dd3da3ea9f463f9dc73e94d68 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 2 Oct 2024 21:01:15 +0800 Subject: [PATCH 691/778] Move PR #1244 (By @TheDiamondStar) --- Resources/Lang/en_US.json | 3 ++- Roles/Neutral/Executioner.cs | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index a4fb7e7b88..27a3f4acd9 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1021,7 +1021,7 @@ "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", - + "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", @@ -1563,6 +1563,7 @@ "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", + "Executioner_RevealTargetUponEject": "Reveal Target Upon Ejection", "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", "LawyerCanTargetImpostor": "Can Target Impostors", "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 28ca39c7e5..a2e484bb60 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -24,6 +24,7 @@ internal class Executioner : RoleBase private static OptionItem CanTargetNeutralApocalypse; private static OptionItem KnowTargetRole; private static OptionItem ChangeRolesAfterTargetKilled; + private static OptionItem RevealExeTargetUponEjection; public static HashSet TargetList = []; private byte TargetId; @@ -62,6 +63,7 @@ public override void SetupCustomOption() CanTargetNeutralApocalypse = BooleanOptionItem.Create(Id + 17, "ExecutionerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); KnowTargetRole = BooleanOptionItem.Create(Id + 13, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); ChangeRolesAfterTargetKilled = StringOptionItem.Create(Id + 11, "ExecutionerChangeRolesAfterTargetKilled", EnumHelper.GetAllNames(), 1, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + RevealExeTargetUponEjection = BooleanOptionItem.Create(Id + 18, "Executioner_RevealTargetUponEject", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); } public override void Init() { @@ -204,13 +206,17 @@ public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool Decid if (isMeetingHud) { - name = string.Format(Translator.GetString("ExiledExeTarget"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + if (RevealExeTargetUponEjection.GetBool()) + { + name = string.Format(Translator.GetString("ExiledExeTarget"), Main.LastVotedPlayer, Utils.GetDisplayRoleAndSubName(exiled.PlayerId, exiled.PlayerId, true)); + DecidedWinner = true; + } } else { ExeWin(_Player.PlayerId, DecidedWinner); + DecidedWinner = true; } - DecidedWinner = true; } private static void ExeWin(byte executionerId, bool DecidedWinner) { From 9ae543908585590fa6e3a823e6d4c24ea5e093e2 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 2 Oct 2024 21:25:13 +0800 Subject: [PATCH 692/778] Change --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index f712f3a36a..5db8926e24 100644 --- a/main.cs +++ b/main.cs @@ -42,13 +42,13 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.0929.210.00161"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Alpha 16 Hotfix 1"; + public const string PluginVersion = "2024.1002.210.010000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Beta 1"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 16 Hotfix 1 - public static readonly bool canaryRelease = false; // Latest: V2.0.0 Canary 12 + public static readonly bool canaryRelease = false; // Latest: V2.1.0 Beta 1 public static readonly bool fullRelease = false; // Latest: V2.0.3 public static bool hasAccess = true; From 6c848b476729b53fbb90c85ba389a757dce9b2d1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 2 Oct 2024 23:42:42 +0800 Subject: [PATCH 693/778] Forgot --- main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cs b/main.cs index 5db8926e24..a6f22e4030 100644 --- a/main.cs +++ b/main.cs @@ -47,8 +47,8 @@ public class Main : BasePlugin public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ - public static readonly bool devRelease = true; // Latest: V2.1.0 Alpha 16 Hotfix 1 - public static readonly bool canaryRelease = false; // Latest: V2.1.0 Beta 1 + public static readonly bool devRelease = false; // Latest: V2.1.0 Alpha 16 Hotfix 1 + public static readonly bool canaryRelease = true; // Latest: V2.1.0 Beta 1 public static readonly bool fullRelease = false; // Latest: V2.0.3 public static bool hasAccess = true; From c32e7e9d9642b87c5090c0db473c756b541727fc Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 3 Oct 2024 16:39:13 +0800 Subject: [PATCH 694/778] Some fix --- Modules/ExtendedPlayerControl.cs | 2 +- Modules/GuessManager.cs | 2 +- Patches/MeetingHudPatch.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 01ad6bf2b0..961e99eaee 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -362,7 +362,7 @@ public static void RpcSetNamePrivate(this PlayerControl player, string name, Pla if (clientId == -1) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, clientId); - writer.Write(player.Data.NetId); + writer.Write(seer.Data.NetId); writer.Write(name); AmongUsClient.Instance.FinishRpcImmediately(writer); } diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index f9d118f8e5..a713b89422 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -378,7 +378,7 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) public static TextMeshPro NameText(this PlayerControl p) => p.cosmetics.nameText; public static TextMeshPro NameText(this PoolablePlayer p) => p.cosmetics.nameText; - public static void RpcGuesserMurderPlayer(this PlayerControl pc) //ゲッサー用の殺し方 + public static void RpcGuesserMurderPlayer(this PlayerControl pc) { try { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 091d22a480..237d30ecb1 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1259,7 +1259,8 @@ public static void Postfix(MeetingHud __instance) bufferTime = 10; var myRole = PlayerControl.LocalPlayer.GetCustomRole(); - __instance.playerStates.Where(x => (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead) && !x.AmDead).Do(x => x.SetDead(x.DidReport, true)); + //__instance.playerStates.Where(x => !x.TargetPlayerId.GetPlayer().IsAlive() && !x.AmDead) + // .Do(x => x.SetDead(x.DidReport, true, x.GAIcon)); if (myRole is CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Doomsayer or CustomRoles.Judge or CustomRoles.Councillor or CustomRoles.Guesser or CustomRoles.Swapper && !PlayerControl.LocalPlayer.IsAlive()) ClearShootButton(__instance, true); From 5f3e57eb4ff72fe3c225d85df3404ae0d5013897 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 3 Oct 2024 16:40:57 +0800 Subject: [PATCH 695/778] Change --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index a6f22e4030..062dac3120 100644 --- a/main.cs +++ b/main.cs @@ -42,7 +42,7 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1002.210.010000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginVersion = "2024.1003.210.010000"; // YEAR.MMDD.VERSION.CANARYDEV public const string PluginDisplayVersion = "2.1.0 Beta 1"; public const string SupportedVersionAU = "2024.8.13"; From 36c62c382e247a5b25017a16f4d1bc6cfa14fc4f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 3 Oct 2024 16:47:03 +0800 Subject: [PATCH 696/778] Fix bug --- Roles/Crewmate/CopyCat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index d930802362..c13d44ab65 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -69,7 +69,7 @@ public static void UnAfterMeetingTasks() //////////// /*remove the settings for current role*/ ///////////////////// var pcRole = pc.GetCustomRole(); - if (pcRole != CustomRoles.Sidekick || pcRole != CustomRoles.Retributionist) + if (pcRole != CustomRoles.Sidekick && pcRole != CustomRoles.Retributionist) { if (pcRole != CustomRoles.CopyCat) { From f6e7c7e573392ed49f8a90969996881bd843a5a0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 3 Oct 2024 16:47:45 +0800 Subject: [PATCH 697/778] Change --- Roles/Crewmate/CopyCat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index c13d44ab65..650ea274b6 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -69,7 +69,7 @@ public static void UnAfterMeetingTasks() //////////// /*remove the settings for current role*/ ///////////////////// var pcRole = pc.GetCustomRole(); - if (pcRole != CustomRoles.Sidekick && pcRole != CustomRoles.Retributionist) + if (pcRole is not CustomRoles.Sidekick and not CustomRoles.Retributionist) { if (pcRole != CustomRoles.CopyCat) { From 5c5a429fc963f50da330336b3d91cf9d662d55d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B5=F0=9D=92=90=F0=9D=92=8F=F0=9D=92=82?= =?UTF-8?q?=F0=9D=92=8D=F0=9D=92=96=F0=9D=92=94=F0=9F=8D=A5?= <134705775+Reborn5537@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:28:17 +0800 Subject: [PATCH 698/778] Add Simplified Chinese Command Hope for a nice work --- Patches/ChatCommandPatch.cs | 6598 ++++++++++++++++++----------------- 1 file changed, 3414 insertions(+), 3184 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index eb70458108..75d5207b28 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1,3184 +1,3414 @@ -using Assets.CoreScripts; -using Hazel; -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using TOHE.Modules; -using TOHE.Modules.ChatManager; -using TOHE.Roles.Core; -using TOHE.Roles.Core.AssignManager; -using TOHE.Roles.Crewmate; -using TOHE.Roles.Impostor; -using TOHE.Roles.Neutral; -using UnityEngine; -using static TOHE.Translator; - - -namespace TOHE; - -[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] -internal class ChatCommands -{ - private static readonly string modLogFiles = @"./TOHE-DATA/ModLogs.txt"; - private static readonly string modTagsFiles = @"./TOHE-DATA/Tags/MOD_TAGS"; - private static readonly string sponsorTagsFiles = @"./TOHE-DATA/Tags/SPONSOR_TAGS"; - private static readonly string vipTagsFiles = @"./TOHE-DATA/Tags/VIP_TAGS"; - - private static readonly Dictionary Pollvotes = []; - private static readonly Dictionary PollQuestions = []; - private static readonly List PollVoted = []; - private static float Polltimer = 120f; - private static string PollMSG = ""; - - public const string Csize = "85%"; // CustomRole Settings Font-Size - public const string Asize = "75%"; // All Appended Addons Font-Size - - public static List ChatHistory = []; - - public static bool Prefix(ChatController __instance) - { - if (__instance.quickChatField.visible == false && __instance.freeChatField.textArea.text == "") return false; - if (!GameStates.IsModHost && !AmongUsClient.Instance.AmHost) return true; - __instance.timeSinceLastMessage = 3f; - var text = __instance.freeChatField.textArea.text; - if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); - ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; - string[] args = text.Split(' '); - string subArgs = ""; - string subArgs2 = ""; - var canceled = false; - var cancelVal = ""; - Main.isChatCommand = true; - Logger.Info(text, "SendChat"); - if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && AmongUsClient.Instance.AmHost) // Blackmailer.ForBlackmailer.Contains(PlayerControl.LocalPlayer.PlayerId)) && PlayerControl.LocalPlayer.IsAlive()) - { - ChatManager.SendMessage(PlayerControl.LocalPlayer, text); - } - //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn" && text[..3] != "/rs") args[0] = "/r"; - if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; - - if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - Directory.CreateDirectory(modTagsFiles); - Directory.CreateDirectory(vipTagsFiles); - Directory.CreateDirectory(sponsorTagsFiles); - - if (Blackmailer.CheckBlackmaile(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()) - { - goto Canceled; - } - switch (args[0]) - { - case "/dump": - Utils.DumpLog(); - break; - case "/v": - case "/version": - case "/versão": - canceled = true; - string version_text = ""; - var player = PlayerControl.LocalPlayer; - var title = "" + GetString("DefaultSystemMessageTitle") + ""; - var name = player?.Data?.PlayerName; - try - { - foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key).ToArray()) - { - var pc = Utils.GetClientById(kvp.Key)?.Character; - version_text += $"{kvp.Key}/{(pc?.PlayerId != null ? pc.PlayerId.ToString() : "null")}:{pc?.GetRealName(clientData: true) ?? "null"}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; - } - if (version_text != "") - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, version_text); - player.SetName(name); - } - } - catch (Exception e) - { - Logger.Error(e.Message, "/version"); - version_text = "Error while getting version : " + e.Message; - if (version_text != "") - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, version_text); - player.SetName(name); - } - } - break; - - default: - Main.isChatCommand = false; - break; - } - if (AmongUsClient.Instance.AmHost) - { - Main.isChatCommand = true; - switch (args[0]) - { - case "/ans": - case "/asw": - case "/answer": - Quizmaster.AnswerByChat(PlayerControl.LocalPlayer, args); - break; - - case "/qmquiz": - Quizmaster.ShowQuestion(PlayerControl.LocalPlayer); - break; - - case "/win": - case "/winner": - case "/vencedor": - canceled = true; - if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists")); - else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList)); - break; - - case "/l": - case "/lastresult": - case "/fimdejogo": - canceled = true; - Utils.ShowKillLog(); - Utils.ShowLastRoles(); - Utils.ShowLastResult(); - break; - - case "/gr": - case "/gameresults": - case "/resultados": - canceled = true; - Utils.ShowLastResult(); - break; - - case "/kh": - case "/killlog": - canceled = true; - Utils.ShowKillLog(); - break; - - case "/rs": - case "/sum": - case "/rolesummary": - case "/sumario": - case "/sumário": - case "/summary": - case "/результат": - canceled = true; - Utils.ShowLastRoles(); - break; - - case "/ghostinfo": - canceled = true; - Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/apocinfo": - case "/apocalypseinfo": - canceled = true; - Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); - break; - - - case "/rn": - case "/rename": - case "/renomear": - case "/переименовать": - canceled = true; - if (args.Length < 1) break; - if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) { - Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else Main.HostRealName = args.Skip(1).Join(delimiter: " "); - Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/hn": - case "/hidename": - case "/semnome": - canceled = true; - Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); - GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = - ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) - ? $"{Main.HideName.Value}" - : $"{Main.HideName.Value}"; - break; - - case "/level": - case "/nível": - case "/nivel": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), PlayerControl.LocalPlayer.PlayerId); - _ = int.TryParse(subArgs, out int input); - if (input is < 1 or > 999) - { - Utils.SendMessage(GetString("Message.AllowLevelRange"), PlayerControl.LocalPlayer.PlayerId); - break; - } - var number = Convert.ToUInt32(input); - PlayerControl.LocalPlayer.RpcSetLevel(number - 1); - break; - - case "/n": - case "/now": - case "/atual": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "r": - case "roles": - case "funções": - Utils.ShowActiveRoles(); - break; - case "a": - case "all": - case "tudo": - Utils.ShowAllActiveSettings(); - break; - default: - Utils.ShowActiveSettings(); - break; - } - break; - - case "/dis": - case "/disconnect": - case "/desconectar": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "crew": - case "tripulante": - GameManager.Instance.enabled = false; - Utils.NotifyGameEnding(); - GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); - break; - - case "imp": - case "impostor": - GameManager.Instance.enabled = false; - Utils.NotifyGameEnding(); - GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); - break; - - default: - __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); - if (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Brazilian) - { - __instance.AddChat(PlayerControl.LocalPlayer, "tripulante | impostor"); - } - cancelVal = "/dis"; - break; - } - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); - break; - - case "/r": - case "/role": - case "/р": - case "/роль": - canceled = true; - if (text.Contains("/role") || text.Contains("/роль")) - subArgs = text.Remove(0, 5); - else - subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); - break; - - case "/up": - canceled = true; - subArgs = text.Remove(0, 3); - if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp){ - Utils.SendMessage($"{GetString("InvalidPermissionCMD")}", PlayerControl.LocalPlayer.PlayerId); - break; - } - if (!Options.EnableUpMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); - break; - - //case "/setbasic": - // canceled = true; - // if (GameStates.IsLobby) - // { - // break; - // } - // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); - // break; - - case "/setplayers": - case "/maxjogadores": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - Utils.SendMessage(GetString("Message.MaxPlayers") + subArgs); - var numbereer = Convert.ToByte(subArgs); - if (GameStates.IsNormalGame) - GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = numbereer; - - else if (GameStates.IsHideNSeek) - GameOptionsManager.Instance.currentHideNSeekGameOptions.MaxPlayers = numbereer; - break; - - case "/h": - case "/help": - case "/ajuda": - case "/хелп": - case "/хэлп": - case "/помощь": - canceled = true; - Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); - break; - - case "/icon": - case "/icons": - { - Utils.SendMessage(GetString("Command.icons"), PlayerControl.LocalPlayer.PlayerId, GetString("IconsTitle")); - break; - } - - case "/iconhelp": - { - Utils.SendMessage(GetString("Command.icons"), title: GetString("IconsTitle")); - break; - } - - case "/kc": - case "/kcount": - case "/количество": - case "/убийцы": - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; - - var allAlivePlayers = Main.AllAlivePlayerControls; - int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); - int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); - int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); - - var sub = new StringBuilder(); - sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); - - if (Options.ShowMadmatesInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); - - if (Options.ShowApocalypseInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); - - sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); - - Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); - break; - case "/vote": - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int arg)) - break; - var plr = Utils.GetPlayerById(arg); - - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (!Options.EnableVoteCommand.GetBool()) - { - Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) - { - canceled = true; - } - - if (arg != 253) // skip - { - if (plr == null || !plr.IsAlive()) - { - Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); - break; - } - } - if (!PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (GameStates.IsMeeting) - { - PlayerControl.LocalPlayer.RpcCastVote((byte)arg); - } - break; - - case "/d": - case "/death": - case "/morto": - case "/умер": - case "/причина": - canceled = true; - Logger.Info($"PlayerControl.LocalPlayer.PlayerId: {PlayerControl.LocalPlayer.PlayerId}", "/death command"); - if (GameStates.IsLobby) - { - Logger.Info("IsLobby", "/death command"); - Utils.SendMessage(text: GetString("Message.CanNotUseInLobby"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (PlayerControl.LocalPlayer.IsAlive()) - { - Logger.Info("IsAlive", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.HeyPlayer") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - Logger.Info("DeathReason.Vote", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) - { - Logger.Info("DeathReason.Shrouded", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) - { - Logger.Info("DeathReason.FollowingSuicide", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - Logger.Info("GetRealKiller()", "/death command"); - var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); - string killerName = killer == null ? "N/A" : killer.GetRealName(); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); - - break; - } - - - case "/m": - case "/myrole": - case "/minhafunção": - case "/м": - case "/мояроль": - canceled = true; - var role = PlayerControl.LocalPlayer.GetCustomRole(); - if (GameStates.IsInGame) - { - var lp = PlayerControl.LocalPlayer; - var Des = lp.GetRoleInfo(true); - var title = $"" + role.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); - var SubTitle = $"" + GetString("YourAddon") + "\n"; - - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - - foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.ToArray()) - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); - - if (Sub.ToString() != string.Empty) - { - var ACleared = Sub.ToString().Remove(0, 2); - ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; - Sub.Clear().Append(ACleared); - } - - Utils.SendMessage(Des, lp.PlayerId, title, noReplay: true); - Utils.SendMessage("", lp.PlayerId, Conf.ToString(), noReplay: true); - if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), lp.PlayerId, SubTitle, noReplay: true); - } - else - Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/me": - canceled = true; - subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); - string Devbox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; - string UpBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; - string ColorBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; - - if (string.IsNullOrEmpty(subArgs)) - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); - } - else - { - if (byte.TryParse(subArgs, out byte meid)) - { - if (meid != PlayerControl.LocalPlayer.PlayerId) - { - var targetplayer = Utils.GetPlayerById(meid); - if (targetplayer != null && targetplayer.GetClient() != null) - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}"); - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); - } - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); - } - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); - } - } - break; - - case "/t": - case "/template": - case "/шаблон": - case "/пример": - canceled = true; - if (args.Length > 1) TemplateManager.SendTemplate(args[1]); - else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", PlayerControl.LocalPlayer.PlayerId); - break; - - case "/mw": - case "/messagewait": - canceled = true; - if (args.Length > 1 && int.TryParse(args[1], out int sec)) - { - Main.MessageWait.Value = sec; - Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); - } - else Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); - break; - - case "/tpout": - canceled = true; - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcTeleport(new Vector2(0.1f, 3.8f)); - break; - case "/tpin": - canceled = true; - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcTeleport(new Vector2(-0.2f, 1.3f)); - break; - - case "/say": - case "/s": - case "/с": - case "/сказать": - canceled = true; - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); - break; - - case "/mid": - canceled = true; - string msgText1 = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText1, PlayerControl.LocalPlayer.PlayerId); - break; - - case "/ban": - case "/banir": - case "/бан": - case "/забанить": - canceled = true; - - string banReason = ""; - if (args.Length < 3) - { - Utils.SendMessage(GetString("BanCommandNoReason"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - subArgs = args[1]; - banReason = string.Join(" ", args.Skip(2)); - } - //subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (banPlayerId == 0) - { - Utils.SendMessage(GetString("BanCommandBanHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var bannedPlayer = Utils.GetPlayerById(banPlayerId); - if (bannedPlayer == null) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // Ban the specified player - AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); - string bannedPlayerName = bannedPlayer.GetRealName(); - string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{PlayerControl.LocalPlayer.name} \nReason: {banReason}\n"; - if (GameStates.IsInGame) - { - textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend1); - //string moderatorName = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; - //moderatorName = moderatorName.Substring(startIndex); - //string extractedString = - string moderatorFriendCode = PlayerControl.LocalPlayer.FriendCode.ToString(); - string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); - string modLogname = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n1) ? n1 : ""; - string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; - string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{banlogname} Reason: {banReason}"; - File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); - break; - - case "/warn": - case "/aviso": - case "/варн": - case "/пред": - case "/предупредить": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (warnPlayerId == 0) - { - Utils.SendMessage(GetString("WarnCommandWarnHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var warnedPlayer = Utils.GetPlayerById(warnPlayerId); - if (warnedPlayer == null) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // warn the specified player - string textToSend2 = ""; - string warnReason = "Reason : Not specified\n"; - string warnedPlayerName = warnedPlayer.GetRealName(); - //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; - if (args.Length > 2) - { - warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - } - else - { - Utils.SendMessage(GetString("WarnExample"), PlayerControl.LocalPlayer.PlayerId); - } - textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{PlayerControl.LocalPlayer.name}"; - Utils.SendMessage(textToSend2); - //string moderatorName1 = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; - //moderatorName1 = moderatorName1.Substring(startIndex1); - string modLogname1 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n2) ? n2 : ""; - string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; - - string moderatorFriendCode1 = PlayerControl.LocalPlayer.FriendCode.ToString(); - string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); - string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); - string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; - File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); - - break; - - case "/kick": - case "/expulsar": - case "/кик": - case "/кикнуть": - case "/выгнать": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (kickPlayerId == 0) - { - Utils.SendMessage(GetString("KickCommandKickHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var kickedPlayer = Utils.GetPlayerById(kickPlayerId); - if (kickedPlayer == null) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); - string kickedPlayerName = kickedPlayer.GetRealName(); - string kickReason = "Reason : Not specified\n"; - if (args.Length > 2) - kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - else - { - Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", PlayerControl.LocalPlayer.PlayerId); - } - string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {PlayerControl.LocalPlayer.name} \n {kickReason}"; - - if (GameStates.IsInGame) - { - textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend); - //string moderatorName2 = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; - //moderatorName2 = moderatorName2.Substring(startIndex2); - - string modLogname2 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n3) ? n3 : ""; - string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; - - string moderatorFriendCode2 = PlayerControl.LocalPlayer.FriendCode.ToString(); - string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); - string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); - string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; - File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); - - break; - - case "/tagcolor": - case "/tagcolour": - canceled = true; - string name = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n) ? n : ""; - if (name == "") break; - if (!name.Contains('\r') && PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag()) - { - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "tagcolor"); - Utils.SendMessage(GetString("TagColorInvalidHexCode"), PlayerControl.LocalPlayer.PlayerId); - break; - } - string tagColorFilePath = $"{sponsorTagsFiles}/{PlayerControl.LocalPlayer.FriendCode}.txt"; - if (!File.Exists(tagColorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); - File.Create(tagColorFilePath).Close(); - } - File.WriteAllText(tagColorFilePath, $"{subArgs}"); - } - break; - - case "/exe": - case "/уничтожить": - case "/повесить": - case "/казнить": - case "/казнь": - case "/мут": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (args.Length < 2 || !int.TryParse(args[1], out int id)) break; - var player = Utils.GetPlayerById(id); - if (player != null) - { - player.Data.IsDead = true; - player.SetDeathReason(PlayerState.DeathReason.etc); - player.SetRealKiller(PlayerControl.LocalPlayer); - Main.PlayerStates[player.PlayerId].SetDead(); - player.RpcExileV2(); - MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); - - if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); - } - break; - - case "/kill": - case "/matar": - case "/убить": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (args.Length < 2 || !int.TryParse(args[1], out int id2)) break; - var target = Utils.GetPlayerById(id2); - if (target != null) - { - target.RpcMurderPlayer(target); - if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); - - _ = new LateTask(() => - { - Utils.NotifyRoles(NoCache: true); - - }, 0.2f, "Update NotifyRoles players after /kill"); - } - break; - - case "/colour": - case "/color": - case "/cor": - case "/цвет": - canceled = true; - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - var color = Utils.MsgToColor(subArgs, true); - if (color == byte.MaxValue) - { - Utils.SendMessage(GetString("IllegalColor"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcSetColor(color); - Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/quit": - case "/qt": - case "/sair": - canceled = true; - Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/xf": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - foreach (var pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) continue; - - pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); - } - ChatUpdatePatch.DoBlockChat = false; - //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); - Utils.SendMessage(GetString("Message.TryFixName"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/id": - case "/айди": - canceled = true; - string msgText = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText, PlayerControl.LocalPlayer.PlayerId); - break; - - /* - case "/qq": - canceled = true; - if (Main.newLobby) Cloud.ShareLobby(true); - else Utils.SendMessage("很抱歉,每个房间车队姬只会发一次", PlayerControl.LocalPlayer.PlayerId); - break; - */ - - case "/setrole": - canceled = true; - subArgs = text.Remove(0, 8); - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); - break; - - case "/changerole": - case "/mudarfunção": - canceled = true; - if (GameStates.IsHideNSeek) break; - if (!(DebugModeManager.AmDebugger && GameStates.IsInGame)) break; - if (GameStates.IsOnlineGame && !PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) break; - subArgs = text.Remove(0, 11); - var setRole = FixRoleNameInput(subArgs).ToLower().Trim().Replace(" ", string.Empty); - Logger.Info(setRole, "changerole Input"); - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); - //Logger.Info(roleName, "2"); - if (setRole == roleName) - { - PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); - PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes(), true); - PlayerControl.LocalPlayer.RpcSetCustomRole(rl); - PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); - Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); - Utils.NotifyRoles(NoCache: true); - Utils.MarkEveryoneDirtySettings(); - break; - } - } - break; - - case "/end": - case "/encerrar": - case "/завершить": - canceled = true; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - break; - case "/cosid": - canceled = true; - var of = PlayerControl.LocalPlayer.Data.DefaultOutfit; - Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); - Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); - Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); - Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); - Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); - Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); - break; - - case "/mt": - case "/hy": - canceled = true; - if (GameStates.IsMeeting) - { - MeetingHud.Instance.RpcClose(); - } - else - { - PlayerControl.LocalPlayer.NoCheckStartMeeting(null, force: true); - } - break; - - case "/cs": - canceled = true; - subArgs = text.Remove(0, 3); - PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); - break; - - case "/sd": - canceled = true; - subArgs = text.Remove(0, 3); - if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) break; - RPC.PlaySoundRPC(PlayerControl.LocalPlayer.PlayerId, (Sounds)sound1); - break; - - case "/poll": - canceled = true; - - - if (args.Length == 2 && args[1] == GetString("Replay") && Pollvotes.Any() && PollMSG != string.Empty) - { - Utils.SendMessage(PollMSG); - break; - } - - PollMSG = string.Empty; - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - Polltimer = 120f; - - static System.Collections.IEnumerator StartPollCountdown() - { - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - bool playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); - - - while (playervoted && Polltimer > 0f) - { - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); - Polltimer -= Time.deltaTime; - yield return null; - } - - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - - Logger.Info($"FINNISHED!! playervote?: {!playervoted} polltime?: {Polltimer <= 0}", "/poll - StartPollCountdown"); - - DetermineResults(); - } - - static void DetermineResults() - { - int basenum = Pollvotes.Values.Max(); - var winners = Pollvotes.Where(x => x.Value == basenum); - - string msg = ""; - - Color32 clr = new(47, 234, 45, 255); //Main.PlayerColors.First(x => x.Key == PlayerControl.LocalPlayer.PlayerId).Value; - var tytul = Utils.ColorString(clr, GetString("PollResultTitle")); - - if (winners.Count() == 1) - { - var losers = Pollvotes.Where(x => x.Key != winners.First().Key); - msg = string.Format(GetString("Poll.Result"), $"{winners.First().Key}{PollQuestions[winners.First().Key]}", winners.First().Value); - - for (int i = 0; i < losers.Count(); i++) - { - msg += $"\n{losers.ElementAt(i).Key} / {losers.ElementAt(i).Value} {PollQuestions[losers.ElementAt(i).Key]}"; - - } - msg += ""; - - - Utils.SendMessage(msg, title: tytul); - } - else - { - var tienum = Pollvotes.Values.Max(); - var tied = Pollvotes.Where(x => x.Value == tienum); - - for (int i = 0; i < (tied.Count() - 1); i++) - { - msg += "\n" + tied.ElementAt(i).Key + PollQuestions[tied.ElementAt(i).Key] + " & "; - } - msg += "\n" + tied.Last().Key + PollQuestions[tied.Last().Key]; - - Utils.SendMessage(string.Format(GetString("Poll.Tied"), msg, tienum), title: tytul); - } - - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - } - - - if (Main.AllPlayerControls.Length < 3) - { - Utils.SendMessage(GetString("Poll.MissingPlayers"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Poll.OnlyInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (args.SkipWhile(x => !x.Contains('?')).ToArray().Length < 3 || !args.Any(x => x.Contains('?'))) - { - Utils.SendMessage(GetString("PollUsage"), PlayerControl.LocalPlayer.PlayerId); - break; - } - var resultat = args.TakeWhile(x => !x.Contains('?')).Concat(args.SkipWhile(x => !x.Contains('?')).Take(1)); - - string tytul = string.Join(" ", resultat.Skip(1)); - bool Longtitle = tytul.Length > 30; - tytul = Utils.ColorString(Palette.PlayerColors[PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId], tytul); - var altTitle = Utils.ColorString(new Color32(151, 198, 230, 255), GetString("PollTitle")); - - var ClearTIT = args.ToList(); - ClearTIT.RemoveRange(0, resultat.ToArray().Length); - - var Questions = ClearTIT.ToArray(); - string msg = ""; - - - if (Longtitle) msg += "" + tytul + "\n\n"; - for (int i = 0; i < Math.Clamp(Questions.Length, 2, 5); i++) - { - msg += Utils.ColorString(RndCLR(), $"{char.ToUpper((char)(i + 65))}) {Questions[i]}\n"); - Pollvotes[char.ToUpper((char)(i + 65))] = 0; - PollQuestions[char.ToUpper((char)(i + 65))] = $"〖 {Questions[i]} 〗"; - } - msg += $"\n{GetString("Poll.Begin")}"; - msg += $"\n{GetString("Poll.TimeInfo")}"; - PollMSG = !Longtitle ? "" + tytul + "\n\n" + msg : msg; - - Logger.Info($"Poll message: {msg}", "MEssapoll"); - - Utils.SendMessage(msg, title: !Longtitle ? tytul: altTitle); - - Main.Instance.StartCoroutine(StartPollCountdown()); - - - static Color32 RndCLR() - { - byte r, g, b; - - r = (byte)IRandom.Instance.Next(45, 185); - g = (byte)IRandom.Instance.Next(45, 185); - b = (byte)IRandom.Instance.Next(45, 185); - - return new Color32(r, g, b, 255); - } - - break; - - case "/rps": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - subArgs = args.Length != 2 ? "" : args[1]; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (playerChoice < 0 || playerChoice > 2) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(0, 3); - var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; - if (botChoice == playerChoice) - { - Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - else if ((botChoice == 0 && playerChoice == 2) || - (botChoice == 1 && playerChoice == 0) || - (botChoice == 2 && playerChoice == 1)) - { - Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - else - { - Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - break; - } - case "/coinflip": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("CoinFlipCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(1, 101); - var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); - Utils.SendMessage(string.Format(GetString("CoinFlipResult"),coinSide), PlayerControl.LocalPlayer.PlayerId); - break; - } - case "/gno": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo < 0 || guessedNo > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - int targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - } - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]--; - if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; - //targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; - break; - } - - } - case "/rand": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - subArgs = args.Length != 3 ? "" : args[1]; - subArgs2 = args.Length != 3 ? "" : args[2]; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) - { - Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botResult = rand.Next(playerChoice1, playerChoice2 + 1); - Utils.SendMessage(string.Format(GetString("RandResult"), botResult), PlayerControl.LocalPlayer.PlayerId); - break; - } - - case "/8ball": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - var rando = IRandom.Instance; - int result = rando.Next(0, 16); - string str = ""; - switch (result) - { - case 0: - str = GetString("8BallYes"); - break; - case 1: - str = GetString("8BallNo"); - break; - case 2: - str = GetString("8BallMaybe"); - break; - case 3: - str = GetString("8BallTryAgainLater"); - break; - case 4: - str = GetString("8BallCertain"); - break; - case 5: - str = GetString("8BallNotLikely"); - break; - case 6: - str = GetString("8BallLikely"); - break; - case 7: - str = GetString("8BallDontCount"); - break; - case 8: - str = GetString("8BallStop"); - break; - case 9: - str = GetString("8BallPossibly"); - break; - case 10: - str = GetString("8BallProbably"); - break; - case 11: - str = GetString("8BallProbablyNot"); - break; - case 12: - str = GetString("8BallBetterNotTell"); - break; - case 13: - str = GetString("8BallCantPredict"); - break; - case 14: - str = GetString("8BallWithoutDoubt"); - break; - case 15: - str = GetString("8BallWithDoubt"); - break; - } - Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); - break; - - default: - Main.isChatCommand = false; - break; - } - } - goto Skip; - Canceled: - Main.isChatCommand = false; - canceled = true; - Skip: - if (canceled) - { - Logger.Info("Command Canceled", "ChatCommand"); - __instance.freeChatField.textArea.Clear(); - __instance.freeChatField.textArea.SetText(cancelVal); - - __instance.quickChatMenu.Clear(); - __instance.quickChatField.Clear(); - } - return !canceled; - } - - public static string FixRoleNameInput(string text) - { - text = text.Replace("着", "者").Trim().ToLower(); - return text switch - { - // Because of partial translation conflicts (zh-cn and zh-tw) - // Need to wait for follow-up finishing - - /* - // GM - "GM(遊戲大師)" or "管理员" or "管理" or "gm" or "GM" => GetString("GM"), - - // 原版职业 - "船員" or "船员" or "白板" or "天选之子" => GetString("CrewmateTOHE"), - "工程師" or "工程师" => GetString("EngineerTOHE"), - "科學家" or "科学家" => GetString("ScientistTOHE"), - "守護天使" or "守护天使" => GetString("GuardianAngelTOHE"), - "偽裝者" or "内鬼" => GetString("ImpostorTOHE"), - "變形者" or "变形者" => GetString("ShapeshifterTOHE"), - - // 隱藏職業 and 隐藏职业 - "陽光開朗大男孩" or "阳光开朗大男孩" => GetString("Sunnyboy"), - "吟遊詩人" or "吟游诗人" => GetString("Bard"), - "核爆者" or "核武器" => GetString("Nuker"), - - // 偽裝者陣營職業 and 内鬼阵营职业 - "賞金獵人" or "赏金猎人" or "赏金" => GetString("BountyHunter"), - "煙火工匠" or "烟花商人" or "烟花爆破者" or "烟花" => GetString("Fireworker"), - "嗜血殺手" or "嗜血杀手" or "嗜血" => GetString("Mercenary"), - "百变怪" or "千面鬼" or "千面" => GetString("ShapeMaster"), - "吸血鬼" or "吸血" => GetString("Vampire"), - "吸血鬼之王" or "吸血鬼女王" => GetString("Vampiress"), - "術士" or "术士" => GetString("Warlock"), - "刺客" or "忍者" => GetString("Ninja"), - "僵屍" or "僵尸" or"殭屍" or "丧尸" => GetString("Zombie"), - "駭客" or "骇客" or "黑客" => GetString("Anonymous"), - "礦工" or "矿工" => GetString("Miner"), - "殺人機器" or "杀戮机器" or "杀戮" or "机器" or "杀戮兵器" => GetString("KillingMachine"), - "通緝犯" or "逃逸者" or "逃逸" => GetString("Escapist"), - "女巫" => GetString("Witch"), - "傀儡師" or "傀儡师" or "傀儡" => GetString("Puppeteer"), - "主謀" or "策划者" => GetString("Mastermind"), - "時間竊賊" or "蚀时者" or "蚀时" or "偷时" => GetString("TimeThief"), - "狙擊手" or "狙击手" or "狙击" => GetString("Sniper"), - "送葬者" or "暗杀者" => GetString("Undertaker"), - "裂縫製造者" or "裂缝制造者" => GetString("RiftMaker"), - "邪惡的追踪者" or "邪恶追踪者" or "邪恶的追踪者" => GetString("EvilTracker"), - "邪惡賭怪" or "邪恶赌怪" or "坏赌" or "恶赌" or "邪恶赌怪" => GetString("EvilGuesser"), - "監管者" or "监管者" or "监管" => GetString("AntiAdminer"), - "狂妄殺手" or "狂妄杀手" => GetString("Arrogance"), - "自爆兵" or "自爆" => GetString("Bomber"), - "清道夫" or "清道" => GetString("Scavenger"), - "陷阱師" or "诡雷" => GetString("Trapster"), - "歹徒" => GetString("Gangster"), - "清潔工" or "清理工" or "清洁工" => GetString("Cleaner"), - "球狀閃電" or "球状闪电" => GetString("Lightning"), - "貪婪者" or "贪婪者" or "贪婪" => GetString("Greedy"), - "被詛咒的狼" or "呪狼" => GetString("CursedWolf"), - "換魂師" or "夺魂者" or "夺魂" => GetString("SoulCatcher"), - "快槍手" or "快枪手" or "快枪" => GetString("QuickShooter"), - "隱蔽者" or "隐蔽者" or "小黑人" => GetString("Camouflager"), - "抹除者" or "抹除" => GetString("Eraser"), - "肢解者" or "肢解" => GetString("Butcher"), - "劊子手" or "刽子手" => GetString("Hangman"), - "隱身人" or "隐匿者" or "隐匿" or "隐身" => GetString("Swooper"), - "船鬼" => GetString("Crewpostor"), - "野人" => GetString("Wildling"), - "騙術師" or "骗术师" => GetString("Trickster"), - "衛道士" or "卫道士" or "内鬼市长" => GetString("Vindicator"), - "寄生蟲" or "寄生虫" => GetString("Parasite"), - "分散者" or "分散" => GetString("Disperser"), - "抑鬱者" or "抑郁者" or "抑郁" => GetString("Inhibitor"), - "破壞者" or "破坏者" or "破坏" => GetString("Saboteur"), - "議員" or "邪恶法官" or "议员" or "邪恶审判" => GetString("Councillor"), - "眩暈者" or "眩晕者" or "眩晕" => GetString("Dazzler"), - "簽約人" or "死亡契约" or "死亡" or "锲约" => GetString("Deathpact"), - "吞噬者" or "吞噬" => GetString("Devourer"), - "軍師" or "军师" => GetString("Consigliere"), - "化型者" or "化形者" => GetString("Morphling"), - "躁動者" or "龙卷风" => GetString("Twister"), - "策畫者" or "潜伏者" or "潜伏" => GetString("Lurker"), - "罪犯" => GetString("Convict"), - "幻想家" or "幻想" => GetString("Visionary"), - "逃亡者" or "逃亡" => GetString("Refugee"), - "潛伏者" or "失败者" or "失败的man" or "失败" => GetString("Underdog"), - "賭博者" or "速度者" or "速度" => GetString("Ludopath"), - "懸賞者" or "教父" => GetString("Godfather"), - "天文學家" or "天文学家" or "天文家" or "天文学" => GetString("Chronomancer"), - "設陷者" or "设陷者" or "设陷" => GetString("Pitfall"), - "狂戰士" or "狂战士" or "升级者" or "狂战士" => GetString("Berserker"), - "壞迷你船員" or "坏迷你船员" or "坏小孩" or "坏迷你" => GetString("EvilMini"), - "勒索者" or "勒索" => GetString("Blackmailer"), - "教唆者" or "教唆" => GetString("Instigator"), - - // 船員陣營職業 and 船员阵营职业 - "擺爛人" or "摆烂人" or "摆烂" => GetString("Needy"), - "大明星" or "明星" => GetString("SuperStar"), - "網紅" or "网红" => GetString("Celebrity"), - "清洗者" or "清洗" => GetString("Cleanser"), - "守衛者" or "守卫者" => GetString("Keeper"), - "俠客" or "侠客" or "正义使者" => GetString("Knight"), - "市長" or "市长" => GetString("Mayor"), - "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), - "愚者" => GetString("Psychic"), - "修理工" or "修理" or "修理大师" => GetString("Mechanic"), - "警長" or "警长" => GetString("Sheriff"), - "義警" or "义务警员" or "警员" => GetString("Vigilante"), - "監禁者" or "狱警" or "狱卒" => GetString("Jailer"), - "模仿者" or "模仿猫" or "模仿" => GetString("CopyCat"), - "告密者" => GetString("Snitch"), - "展現者" or "展现者" or "展现" => GetString("Marshall"), - "增速師" or "增速者" or "增速" => GetString("SpeedBooster"), - "法醫" or "法医" => GetString("Doctor"), - "獨裁主義者" or "独裁者" or "独裁" => GetString("Dictator"), - "偵探" or "侦探" => GetString("Detective"), - "正義賭怪" or "正义赌怪" or "好赌" or "正义的赌怪" => GetString("NiceGuesser"), - "賭場管理員" or "竞猜大师" or "竞猜" => GetString("GuessMaster"), - "傳送師" or "传送师" => GetString("Transporter"), - "時間大師" or "时间操控者" or "时间操控" => GetString("TimeManager"), - "老兵" => GetString("Veteran"), - "埋雷兵" => GetString("Bastion"), - "保鑣" or "保镖" => GetString("Bodyguard"), - "贗品商" or "赝品商" => GetString("Deceiver"), - "擲彈兵" or "掷雷兵" => GetString("Grenadier"), - "軍醫" or "医生" => GetString("Medic"), - "占卜師" or "调查员" or "占卜师" => GetString("FortuneTeller"), - "法官" or "正义法官" or "正义审判" => GetString("Judge"), - "殯葬師" or "入殓师" => GetString("Mortician"), - "通靈師" or "通灵师" => GetString("Mediumshiper"), - "和平之鴿" or "和平之鸽" => GetString("Pacifist"), - "窺視者" or "观察者" or "观察" => GetString("Observer"), - "君主" => GetString("Monarch"), - "預言家" or "预言家" or "预言" => GetString("Overseer"), - "驗屍官" or "验尸官" or "验尸" => GetString("Coroner"), - "正義的追蹤者" or "正义追踪者" or "正义的追踪者" => GetString("Tracker"), - "商人" => GetString("Merchant"), - "總統" or "总统" => GetString("President"), - "獵鷹" or "猎鹰" => GetString("Hawk"), - "捕快" or "下属" => GetString("Deputy"), - "算命師" or "研究者" => GetString("Investigator"), - "守護者" or "守护者" or "守护" => GetString("Guardian"), - "賢者" or "瘾君子" or "醉酒" => GetString("Addict"), - "鼹鼠" => GetString("Mole"), - "藥劑師" or "炼金术士" or "药剂" => GetString("Alchemist"), - "尋跡者" or "寻迹者" or "寻迹" or "寻找鸡腿" => GetString("Tracefinder"), - "先知" or "神谕" or "神谕者" => GetString("Oracle"), - "靈魂論者" or "灵魂论者" => GetString("Spiritualist"), - "變色龍" or "变色龙" or "变色" => GetString("Chameleon"), - "檢查員" or "检查员" or "检查" => GetString("Inspector"), - "仰慕者" or "仰慕" => GetString("Admirer"), - "時間之主" or "时间之主" or "回溯时间" => GetString("TimeMaster"), - "十字軍" or "十字军" => GetString("Crusader"), - "遐想者" or "遐想" => GetString("Reverie"), - "瞭望者" or "瞭望员" => GetString("Lookout"), - "通訊員" or "通信员" => GetString("Telecommunication"), - "執燈人" or "执灯人" or "执灯" or "灯人" or "小灯人" => GetString("Lighter"), - "任務管理員" or "任务管理者" => GetString("TaskManager"), - "目擊者" or "目击者" or "目击" => GetString("Witness"), - "換票師" or "换票师" => GetString("Swapper"), - "警察局長" or "警察局长" => GetString("ChiefOfPolice"), - "好迷你船員" or "好迷你船员" or "好迷你" or "好小孩" => GetString("NiceMini"), - "間諜" or "间谍" => GetString("Spy"), - "隨機者" or "萧暮" or "暮" or "萧暮不姓萧" => GetString("Randomizer"), - "猜想者" or "猜想" or "谜团" => GetString("Enigma"), - "船長" or "舰长" or "船长" => GetString("Captain"), - "慈善家" or "恩人" => GetString("Benefactor"), - - // 中立陣營職業 and 中立阵营职业 - "小丑" or "丑皇" => GetString("Jester"), - "縱火犯" or "纵火犯" or "纵火者" or "纵火" => GetString("Arsonist"), - "焚燒狂" or "焚烧狂" or "焚烧" => GetString("Pyromaniac"), - "神風特攻隊" or "神风特攻队" => GetString("Kamikaze"), - "獵人" or "猎人" => GetString("Huntsman"), - "恐怖分子" => GetString("Terrorist"), - "暴民" or "处刑人" or "处刑" or "处刑者" => GetString("Executioner"), - "律師" or "律师" => GetString("Lawyer"), - "投機主義者" or "投机者" or "投机" => GetString("Opportunist"), - "瑪利歐" or "马里奥" => GetString("Vector"), - "豺狼" or "蓝狼" => GetString("Jackal"), - "神" or "上帝" => GetString("God"), - "冤罪師" or "冤罪师" or "冤罪" => GetString("Innocent"), - "暗殺者" or "隐形者" =>GetString("Stealth"), - "企鵝" or "企鹅" =>GetString("Penguin"), - "鵜鶘" or "鹈鹕" => GetString("Pelican"), - "疫醫" or "瘟疫学家" => GetString("PlagueDoctor"), - "革命家" or "革命者" => GetString("Revolutionist"), - "單身狗" => GetString("Hater"), - "柯南" => GetString("Konan"), - "玩家" => GetString("Demon"), - "潛藏者" or "潜藏" => GetString("Stalker"), - "工作狂" => GetString("Workaholic"), - "至日者" or "至日" => GetString("Solsticer"), - "集票者" or "集票" => GetString("Collector"), - "挑釁者" or "自爆卡车" => GetString("Provocateur"), - "嗜血騎士" or "嗜血骑士" => GetString("BloodKnight"), - "瘟疫之源" or "瘟疫使者" => GetString("PlagueBearer"), - "萬疫之神" or "瘟疫" => GetString("Pestilence"), - "故障者" or "缺点者" or "缺点" => GetString("Glitch"), - "跟班" or "跟班小弟" => GetString("Sidekick"), - "追隨者" or "赌徒" or "下注" => GetString("Follower"), - "魅魔" => GetString("Cultist"), - "連環殺手" or "连环杀手" => GetString("SerialKiller"), - "劍聖" or "天启" => GetString("Juggernaut"), - "感染者" or "感染" => GetString("Infectious"), - "病原體" or "病毒" => GetString("Virus"), - "起訴人" or "起诉人" => GetString("Pursuer"), - "怨靈" or "幽灵" => GetString("Phantom"), - "挑戰者" or "决斗者" or "挑战者" => GetString("Pirate"), - "炸彈王" or "炸弹狂" or "煽动者" => GetString("Agitater"), - "獨行者" or "独行者" => GetString("Maverick"), - "被詛咒的靈魂" or "诅咒之人" => GetString("CursedSoul"), - "竊賊" or "小偷" => GetString("Pickpocket"), - "背叛者" or "背叛" => GetString("Traitor"), - "禿鷲" or "秃鹫" => GetString("Vulture"), - "搗蛋鬼" or "任务执行者" => GetString("Taskinator"), - "麵包師" or "面包师" => GetString("Baker"), - "飢荒" or "饥荒" => GetString("Famine"), - "靈魂召喚者" or "灵魂召唤者" => GetString("Spiritcaller"), - "失憶者" or "失忆者" or "失忆" => GetString("Amnesiac"), - "模仿家" or "效仿者" => GetString("Imitator"), - "強盜" => GetString("Bandit"), - "分身者" => GetString("Doppelganger"), - "受虐狂" => GetString("PunchingBag"), - "賭神" or "末日赌怪" => GetString("Doomsayer"), - "裹屍布" or "裹尸布" => GetString("Shroud"), - "月下狼人" or "狼人" => GetString("Werewolf"), - "薩滿" or "萨满" => GetString("Shaman"), - "冒險家" or "探索者" => GetString("Seeker"), - "精靈" or "小精灵" or "精灵" => GetString("Pixie"), - "咒魔" or "神秘者" => GetString("Occultist"), - "靈魂收割者" or "灵魂收集者" or "灵魂收集" or "收集灵魂" => GetString("SoulCollector"), - "薛丁格的貓" or "薛定谔的猫" => GetString("SchrodingersCat"), - "暗戀者" or "浪漫者" => GetString("Romantic"), - "報復者" or "复仇浪漫者" => GetString("VengefulRomantic"), - "絕情者" or "无情浪漫者" => GetString("RuthlessRomantic"), - "毒醫" or "投毒者" => GetString("Poisoner"), - "代碼工程師" or "巫师" => GetString("HexMaster"), - "幻影" or "魅影" => GetString("Wraith"), - "掃把星" or "扫把星" => GetString("Jinx"), - "魔藥師" or "药剂师" => GetString("PotionMaster"), - "死靈法師" or "亡灵巫师" => GetString("Necromancer"), - "測驗者" or "测验长" => GetString("Quizmaster"), - - // 附加職業 and 附加职业 - "絕境者" or "绝境者" => GetString("LastImpostor"), - "超頻" or "超频波" or "超频" => GetString("Overclocked"), - "戀人" or "恋人" => GetString("Lovers"), - "叛徒" => GetString("Madmate"), - "觀察者" or "窥视者" or "觀察" or "窥视" => GetString("Watcher"), - "閃電俠" or "闪电侠" or "閃電" or "闪电" => GetString("Flash"), - "持燈人" or "火炬" or "持燈" => GetString("Torch"), - "靈媒" or "灵媒" or "靈媒" => GetString("Seer"), - "破平者" or "破平" => GetString("Tiebreaker"), - "膽小鬼" or "胆小鬼" or "膽小" or "胆小" => GetString("Oblivious"), - "視障" or "迷幻者" or "視障" or "迷幻" => GetString("Bewilder"), - "墨鏡" or "患者" => GetString("Sunglasses"), - "加班狂" => GetString("Workhorse"), - "蠢蛋" => GetString("Fool"), - "復仇者" or "复仇者" or "復仇" or "复仇" => GetString("Avanger"), - "Youtuber" or "UP主" or "YT" => GetString("Youtuber"), - "利己主義者" or "利己主义者" or "利己主義" or "利己主义" => GetString("Egoist"), - "竊票者" or "窃票者" or "竊票" or "窃票" => GetString("TicketsStealer"), - //"雙重人格" or "双重人格" => GetString("Schizophrenic"), - "保險箱" or "宝箱怪" => GetString("Mimic"), - "賭怪" or "赌怪" => GetString("Guesser"), - "死神" => GetString("Necroview"), - "長槍" or "持枪" => GetString("Reach"), - "魅魔小弟" => GetString("Charmed"), - "乾淨" or "干净" => GetString("Cleansed"), - "誘餌" or "诱饵" => GetString("Bait"), - "陷阱師" or "陷阱师" => GetString("Trapper"), - "被感染" or "感染" => GetString("Infected"), - "防賭" or "不可被赌" => GetString("Onbound"), - "反擊者" or "回弹者" or "回弹" => GetString("Rebound"), - "平凡者" or "平凡" => GetString("Mundane"), - "騎士" or "骑士" => GetString("Knighted"), - "漠視" or "不受重视" or "被漠視的" => GetString("Unreportable"), - "被傳染" or "传染性" => GetString("Contagious"), - "幸運" or "幸运加持" => GetString("Lucky"), - "倒霉" or "倒霉蛋" => GetString("Unlucky"), - "虛無" or "无效投票" => GetString("VoidBallot"), - "敏感" or "意识者" or "意识" => GetString("Aware"), - "嬌嫩" or "脆弱" or "脆弱者" => GetString("Fragile"), - "專業" or "双重猜测" => GetString("DoubleShot"), - "流氓" => GetString("Rascal"), - "無魂" or "没有灵魂" => GetString("Soulless"), - "墓碑" => GetString("Gravestone"), - "懶人" or "懒人" => GetString("Lazy"), - "驗屍" or "尸检" => GetString("Autopsy"), - "忠誠" or "忠诚" => GetString("Loyal"), - "惡靈" or "恶灵" => GetString("EvilSpirit"), - "狼化" or "招募" or "狼化的" or "被招募的" => GetString("Recruit"), - "被仰慕" or "仰慕" => GetString("Admired"), - "發光" or "光辉" => GetString("Glow"), - "病態" or "患病者" or "患病的" or "患病" => GetString("Diseased"), - "健康" or "健康的" or "健康者" => GetString("Antidote"), - "固執者" or "固执者" or "固執" or "固执" => GetString("Stubborn"), - "無影" or "迅捷" => GetString("Swift"), - "反噬" or "食尸鬼" => GetString("Ghoul"), - "嗜血者" => GetString("Bloodthirst"), - "獵夢者" or "梦魇" or "獵夢"=> GetString("Mare"), - "地雷" or "爆破者" or "爆破" => GetString("Burst"), - "偵察員" or "侦察员" or "偵察" or "侦察" => GetString("Sleuth"), - "笨拙" or "笨蛋" => GetString("Clumsy"), - "敏捷" => GetString("Nimble"), - "規避者" or "规避者" or "规避" => GetString("Circumvent"), - "名人" or "网络员" or "网络" => GetString("Cyber"), - "焦急者" or "焦急的" or "焦急" => GetString("Hurried"), - "OIIAI" => GetString("Oiiai"), - "順從者" or "影响者" or "順從" or "影响" => GetString("Influenced"), - "沉默者" or "沉默" => GetString("Silent"), - "易感者" or "易感" => GetString("Susceptible"), - "狡猾" or "棘手者" or "棘手" => GetString("Tricky"), - "彩虹" => GetString("Rainbow"), - "疲勞者" or "疲劳者" or "疲勞" or "疲劳" => GetString("Tired"), - "雕像" => GetString("Statue"), - "没有搜集的繁体中文" or "雷达" => GetString("Radar"), - - // 幽靈職業 and 幽灵职业 - // 偽裝者 and 内鬼 - "爪牙" => GetString("Minion"), - "黑手黨" or "黑手党" or "黑手" => GetString("Nemesis"), - "嗜血之魂" or "血液伯爵" => GetString("Bloodmoon"), - // 船員 and 船员 - "没有搜集的繁体中文" or "鬼怪" => GetString("Ghastly"), - "冤魂" or "典狱长" => GetString("Warden"), - "報應者" or "惩罚者" or "惩罚" or "报仇者" => GetString("Retributionist"), - - // 随机阵营职业 - "迷你船員" or "迷你船员" or "迷你" or "小孩" or "Mini" => GetString("Mini"),*/ - _ => text, - }; - } - - public static bool GetRoleByName(string name, out CustomRoles role) - { - role = new(); - - if (name == "" || name == string.Empty) return false; - - if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) - { - Regex r = new("[\u4e00-\u9fa5]+$"); - MatchCollection mc = r.Matches(name); - string result = string.Empty; - for (int i = 0; i < mc.Count; i++) - { - if (mc[i].ToString() == "是") continue; - result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 - } - name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); - } - else name = name.Trim().ToLower(); - - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", ""); - string nameWithoutId = Regex.Replace(name.Replace(" ", ""), @"^\d+", ""); - if (nameWithoutId == roleName) - { - role = rl; - return true; - } - } - return false; - } - public static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) - { - if (Options.CurrentGameMode == CustomGameMode.FFA) - { - Utils.SendMessage(GetString("ModeDescribe.FFA"), playerId); - return; - } - role = role.Trim().ToLower(); - if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); - if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); - if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); - if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); - if (role.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); - - if (role == "" || role == string.Empty) - { - Utils.ShowActiveRoles(playerId); - return; - } - - role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); - - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()); - if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) - { - string devMark = ""; - if ((isDev || isUp) && GameStates.IsLobby) - { - devMark = "▲"; - if (CustomRolesHelper.IsAdditionRole(rl) || rl is CustomRoles.GM or CustomRoles.Mini || rl.IsGhostRole()) devMark = ""; - if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = ""; - if (isUp) - { - if (devMark == "▲") Utils.SendMessage(string.Format(GetString("Message.YTPlanSelected"), roleName), playerId); - else Utils.SendMessage(string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); - } - if (devMark == "▲") - { - byte pid = playerId == 255 ? (byte)0 : playerId; - GhostRoleAssign.forceRole.Remove(pid); - RoleAssign.SetRoles.Remove(pid); - RoleAssign.SetRoles.Add(pid, rl); - } - if (rl.IsGhostRole() && !rl.IsAdditionRole() && isDev && (rl.GetCount() >= 1 && rl.GetMode() > 0)) - { - byte pid = playerId == 255 ? (byte)0 : playerId; - CustomRoles setrole = rl.GetCustomRoleTeam() switch - { - Custom_Team.Impostor => CustomRoles.ImpostorTOHE, - _ => CustomRoles.CrewmateTOHE - - }; - RoleAssign.SetRoles.Remove(pid); - RoleAssign.SetRoles.Add(pid, setrole); - GhostRoleAssign.forceRole[pid] = rl; - - devMark = "▲"; - } - - if (isUp) return; - } - var Des = rl.GetInfoLong(); - var title = devMark + $"" + rl.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - string rlHex = Utils.GetRoleColorCode(rl); - if (Options.CustomRoleSpawnChances.ContainsKey(rl)) - { - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(rl.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - } - // Show role info - Utils.SendMessage(Des, playerId, title, noReplay: true); - - // Show role settings - Utils.SendMessage("", playerId, Conf.ToString(), noReplay: true); - return; - } - } - if (isUp) Utils.SendMessage(GetString("Message.YTPlanCanNotFindRoleThePlayerEnter"), playerId); - else Utils.SendMessage(GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); - return; - } - public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) - { - canceled = false; - if (!AmongUsClient.Instance.AmHost) return; - - if (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); - - if (text.StartsWith("\n")) text = text[1..]; - //if (!text.StartsWith("/")) return; - string[] args = text.Split(' '); - string subArgs = ""; - string subArgs2 = ""; - - //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; - // if (SpamManager.CheckSpam(player, text)) return; - if (GuessManager.GuesserMsg(player, text)) { canceled = true; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } - if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } - if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } - if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Councillor cl && cl.MurderMsg(player, text)) { canceled = true; Logger.Info($"Is Councillor command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Swapper sw && sw.SwapMsg(player, text)) { canceled = true; Logger.Info($"Is Swapper command", "OnReceiveChat"); return; } - if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } - if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } - if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } - - Directory.CreateDirectory(modTagsFiles); - Directory.CreateDirectory(vipTagsFiles); - Directory.CreateDirectory(sponsorTagsFiles); - - if (Blackmailer.CheckBlackmaile(player) && player.IsAlive()) - { - Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); - ChatManager.SendPreviousMessagesToAll(); - ChatManager.cancel = false; - canceled = true; - return; - } - - switch (args[0]) - { - case "/r": - case "/role": - case "/р": - case "/роль": - Logger.Info($"Command '/r' was activated", "OnReceiveChat"); - if (text.Contains("/role") || text.Contains("/роль")) - subArgs = text.Remove(0, 5); - else - subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); - break; - - case "/m": - case "/myrole": - case "/minhafunção": - case "/м": - case "/мояроль": - Logger.Info($"Command '/m' was activated", "OnReceiveChat"); - var role = player.GetCustomRole(); - if (GameStates.IsInGame) - { - var Des = player.GetRoleInfo(true); - var title = $"" + role.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); - var SubTitle = $"" + GetString("YourAddon") + "\n"; - - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(opt, ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles.ToArray()) - { - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); - - } - if (Sub.ToString() != string.Empty) - { - var ACleared = Sub.ToString().Remove(0, 2); - ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "": ACleared; - Sub.Clear().Append(ACleared); - } - - Utils.SendMessage(Des, player.PlayerId, title, noReplay: true); - Utils.SendMessage("", player.PlayerId, Conf.ToString(), noReplay: true); - if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), player.PlayerId, SubTitle, noReplay: true); - - Logger.Info($"Command '/m' should be send message", "OnReceiveChat"); - } - else - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - - case "/h": - case "/help": - case "/ajuda": - case "/хелп": - case "/хэлп": - case "/помощь": - Utils.ShowHelpToClient(player.PlayerId); - break; - - case "/ans": - case "/asw": - case "/answer": - Quizmaster.AnswerByChat(player, args); - break; - - case "/qmquiz": - Quizmaster.ShowQuestion(player); - break; - - case "/l": - case "/lastresult": - case "/fimdejogo": - Utils.ShowKillLog(player.PlayerId); - Utils.ShowLastRoles(player.PlayerId); - Utils.ShowLastResult(player.PlayerId); - break; - - case "/gr": - case "/gameresults": - case "/resultados": - Utils.ShowLastResult(player.PlayerId); - break; - - case "/kh": - case "/killlog": - Utils.ShowKillLog(player.PlayerId); - break; - - case "/rs": - case "/sum": - case "/rolesummary": - case "/sumario": - case "/sumário": - case "/summary": - case "/результат": - Utils.ShowLastRoles(player.PlayerId); - break; - - case "/ghostinfo": - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); - break; - - case "/apocinfo": - case "/apocalypseinfo": - Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); - break; - - case "/rn": - case "/rename": - case "/renomear": - case "/переименовать": - if (Options.PlayerCanSetName.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().NameCmd || Utils.IsPlayerVIP(player.FriendCode)) - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - if (args.Length < 1) break; - if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) - { - Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); - break; - } - Main.AllPlayerNames[player.PlayerId] = args.Skip(1).Join(delimiter: " "); - Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), player.PlayerId); - break; - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/n": - case "/now": - case "/atual": - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "r": - case "roles": - case "funções": - Utils.ShowActiveRoles(player.PlayerId); - break; - case "a": - case "all": - case "tudo": - Utils.ShowAllActiveSettings(player.PlayerId); - break; - default: - Utils.ShowActiveSettings(player.PlayerId); - break; - } - break; - - case "/up": - _ = text.Remove(0, 3); - if (!Options.EnableUpMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), player.PlayerId); - break; - } - else - { - Utils.SendMessage(GetString("Message.OnlyCanBeUsedByHost"), player.PlayerId); - break; - } - - case "/win": - case "/winner": - case "/vencedor": - if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), player.PlayerId); - else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList), player.PlayerId); - break; - - - case "/pv": - canceled = true; - if (!Pollvotes.Any()) - { - Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); - break; - } - if (PollVoted.Contains(player.PlayerId)) - { - Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); - break; - } - - subArgs = args.Length != 2 ? "" : args[1]; - char vote = ' '; - - if (int.TryParse(subArgs, out int integer) && (Pollvotes.Count - 1) >= integer) - { - vote = char.ToUpper((char)(integer + 65)); - } - else if (!(char.TryParse(subArgs, out vote) && Pollvotes.ContainsKey(char.ToUpper(vote)))) - { - Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); - break; - } - vote = char.ToUpper(vote); - - PollVoted.Add(player.PlayerId); - Pollvotes[vote]++; - Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, Pollvotes[vote]), player.PlayerId); - Logger.Info($"The new value of {vote} is {Pollvotes[vote]}", "TestPV_CHAR"); - - break; - - case "/icon": - case "/icons": - { - Utils.SendMessage(GetString("Command.icons"), player.PlayerId, GetString("IconsTitle")); - break; - } - - case "/kc": - case "/kcount": - case "/количество": - case "/убийцы": - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; - - var allAlivePlayers = Main.AllAlivePlayerControls; - int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); - int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); - int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); - int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - - var sub = new StringBuilder(); - sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); - - if (Options.ShowMadmatesInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); - - if (Options.ShowApocalypseInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); - - sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); - - Utils.SendMessage(sub.ToString(), player.PlayerId); - break; - - case "/d": - case "/death": - case "/morto": - case "/умер": - case "/причина": - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - else if (player.IsAlive()) - { - Utils.SendMessage(GetString("DeathCmd.HeyPlayer") + "" + player.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), player.PlayerId); - break; - } - else - { - var killer = player.GetRealKiller(out var MurderRole); - string killerName = killer == null ? "N/A" : killer.GetRealName(); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); - break; - } - - case "/t": - case "/template": - case "/шаблон": - case "/пример": - if (args.Length > 1) TemplateManager.SendTemplate(args[1], player.PlayerId); - else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); - break; - - case "/colour": - case "/color": - case "/cor": - case "/цвет": - if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd || Utils.IsPlayerVIP(player.FriendCode)) - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - var color = Utils.MsgToColor(subArgs); - if (color == byte.MaxValue) - { - Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); - break; - } - player.RpcSetColor(color); - Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/quit": - case "/qt": - case "/sair": - if (Options.PlayerCanUseQuitCommand.GetBool()) - { - subArgs = args.Length < 2 ? "" : args[1]; - var cid = player.PlayerId.ToString(); - cid = cid.Length != 1 ? cid.Substring(1, 1) : cid; - if (subArgs.Equals(cid)) - { - string name = player.GetRealName(); - Utils.SendMessage(string.Format(GetString("Message.PlayerQuitForever"), name)); - AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); - } - else - { - Utils.SendMessage(string.Format(GetString("SureUse.quit"), cid), player.PlayerId); - } - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/id": - case "/айди": - if ((Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) - && !Options.EnableVoteCommand.GetBool()) break; - - string msgText = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText, player.PlayerId); - break; - - case "/mid": - //canceled = true; - //checking if modlist on or not - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("midCommandDisabled"), player.PlayerId); - break; - } - //checking if player is has necessary privellege or not - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("midCommandNoAccess"), player.PlayerId); - break; - } - string msgText1 = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText1, player.PlayerId); - break; - - case "/ban": - case "/banir": - case "/бан": - case "/забанить": - //canceled = true; - // Check if the ban command is enabled in the settings - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("BanCommandDisabled"), player.PlayerId); - break; - } - - // Check if the player has the necessary privileges to use the command - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("BanCommandNoAccess"), player.PlayerId); - break; - } - string banReason; - if (args.Length < 3) - { - Utils.SendMessage(GetString("BanCommandNoReason"), player.PlayerId); - break; - } - else - { - subArgs = args[1]; - banReason = string.Join(" ", args.Skip(2)); - } - //subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); - break; - } - - if (banPlayerId == 0) - { - Utils.SendMessage(GetString("BanCommandBanHost"), player.PlayerId); - break; - } - - var bannedPlayer = Utils.GetPlayerById(banPlayerId); - if (bannedPlayer == null) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from baning other moderators - if (Utils.IsPlayerModerator(bannedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("BanCommandBanMod"), player.PlayerId); - break; - } - - // Ban the specified player - AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); - string bannedPlayerName = bannedPlayer.GetRealName(); - string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{player.name} \nReason: {banReason}\n"; - if (GameStates.IsInGame) - { - textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend1); - //string moderatorName = player.GetRealName().ToString(); - //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; - //moderatorName = moderatorName.Substring(startIndex); - //string extractedString = - string modLogname = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n1) ? n1 : ""; - string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; - string moderatorFriendCode = player.FriendCode.ToString(); - string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); - string bannedPlayerHashPuid = bannedPlayer.GetClient().GetHashedPuid(); - string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{bannedPlayerHashPuid},{banlogname} Reason: {banReason}"; - File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); - break; - - case "/warn": - case "/aviso": - case "/варн": - case "/пред": - case "/предупредить": - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("WarnCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("WarnCommandNoAccess"), player.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); - break; - } - if (warnPlayerId == 0) - { - Utils.SendMessage(GetString("WarnCommandWarnHost"), player.PlayerId); - break; - } - - var warnedPlayer = Utils.GetPlayerById(warnPlayerId); - if (warnedPlayer == null) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from warning other moderators - if (Utils.IsPlayerModerator(warnedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("WarnCommandWarnMod"), player.PlayerId); - break; - } - // warn the specified player - string warnReason = "Reason : Not specified\n"; - string warnedPlayerName = warnedPlayer.GetRealName(); - //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; - if (args.Length > 2) - { - warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - } - else - { - Utils.SendMessage("Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", player.PlayerId); - } - Utils.SendMessage($" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{player.name}"); - //string moderatorName1 = player.GetRealName().ToString(); - //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; - //moderatorName1 = moderatorName1.Substring(startIndex1); - string modLogname1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n2) ? n2 : ""; - string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; - string moderatorFriendCode1 = player.FriendCode.ToString(); - string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); - string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); - string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; - File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); - - break; - case "/kick": - case "/expulsar": - case "/кик": - case "/кикнуть": - case "/выгнать": - // Check if the kick command is enabled in the settings - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); - break; - } - - // Check if the player has the necessary privileges to use the command - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); - break; - } - - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); - break; - } - - if (kickPlayerId == 0) - { - Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); - break; - } - - var kickedPlayer = Utils.GetPlayerById(kickPlayerId); - if (kickedPlayer == null) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from kicking other moderators - if (Utils.IsPlayerModerator(kickedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); - break; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); - string kickedPlayerName = kickedPlayer.GetRealName(); - string kickReason = "Reason : Not specified\n"; - if (args.Length > 2) - kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - else - { - Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", player.PlayerId); - } - string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {player.name} \n {kickReason}"; - - if (GameStates.IsInGame) - { - textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend); - //string moderatorName2 = player.GetRealName().ToString(); - //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; - //moderatorName2 = moderatorName2.Substring(startIndex2); - string modLogname2 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n3) ? n3 : ""; - string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; - - string moderatorFriendCode2 = player.FriendCode.ToString(); - string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); - string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); - string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; - File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); - - break; - case "/modcolor": - case "/modcolour": - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("ColorCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("ColorCommandNoAccess"), player.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); - break; - } - if (!Options.GradientTagsOpt.GetBool()) - { - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "modcolor"); - Utils.SendMessage(GetString("ColorInvalidHexCode"), player.PlayerId); - break; - } - string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePath)) - { - Logger.Warn($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - File.Create(colorFilePath).Close(); - } - - File.WriteAllText(colorFilePath, $"{subArgs}"); - break; - } - else - { - subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; - Regex regex = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); - if (string.IsNullOrEmpty(subArgs) || !regex.IsMatch(subArgs)) - { - Logger.Msg($"{subArgs}", "modcolor"); - Utils.SendMessage(GetString("ColorInvalidGradientCode"), player.PlayerId); - break; - } - string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - File.Create(colorFilePath).Close(); - } - //Logger.Msg($"File exists, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - //Logger.Msg($"{subArgs}","modcolor"); - File.WriteAllText(colorFilePath, $"{subArgs}"); - break; - } - case "/vipcolor": - case "/vipcolour": - if (Options.ApplyVipList.GetValue() == 0) - { - Utils.SendMessage(GetString("VipColorCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerVIP(player.FriendCode)) - { - Utils.SendMessage(GetString("VipColorCommandNoAccess"), player.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("VipColorCommandNoLobby"), player.PlayerId); - break; - } - if (!Options.GradientTagsOpt.GetBool()) - { - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "vipcolor"); - Utils.SendMessage(GetString("VipColorInvalidHexCode"), player.PlayerId); - break; - } - string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePathh)) - { - Logger.Warn($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - File.Create(colorFilePathh).Close(); - } - - File.WriteAllText(colorFilePathh, $"{subArgs}"); - break; - } - else - { - subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; - Regex regexx = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); - if (string.IsNullOrEmpty(subArgs) || !regexx.IsMatch(subArgs)) - { - Logger.Msg($"{subArgs}", "vipcolor"); - Utils.SendMessage(GetString("VipColorInvalidGradientCode"), player.PlayerId); - break; - } - string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePathh)) - { - Logger.Msg($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - File.Create(colorFilePathh).Close(); - } - //Logger.Msg($"File exists, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - //Logger.Msg($"{subArgs}","modcolor"); - File.WriteAllText(colorFilePathh, $"{subArgs}"); - break; - } - case "/tagcolor": - case "/tagcolour": - string name1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; - if (name1 == "") break; - if (!name1.Contains('\r') && player.FriendCode.GetDevUser().HasTag()) - { - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "tagcolor"); - Utils.SendMessage(GetString("TagColorInvalidHexCode"), player.PlayerId); - break; - } - string tagColorFilePath = $"{sponsorTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(tagColorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); - File.Create(tagColorFilePath).Close(); - } - - File.WriteAllText(tagColorFilePath, $"{subArgs}"); - } - break; - - case "/xf": - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - foreach (var pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) continue; - - pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); - } - ChatUpdatePatch.DoBlockChat = false; - //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); - Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); - break; - - case "/tpout": - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - player.RpcTeleport(new Vector2(0.1f, 3.8f)); - break; - case "/tpin": - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - player.RpcTeleport(new Vector2(-0.2f, 1.3f)); - break; - - case "/vote": - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int arg)) - break; - var plr = Utils.GetPlayerById(arg); - - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - - - if (!Options.EnableVoteCommand.GetBool()) - { - Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); - break; - } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) - { - canceled = true; - ChatManager.SendPreviousMessagesToAll(); - } - - if (arg != 253) // skip - { - if (plr == null || !plr.IsAlive()) - { - Utils.SendMessage(GetString("VoteDead"), player.PlayerId); - break; - } - } - if (!player.IsAlive()) - { - Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); - break; - } - if (GameStates.IsMeeting) - { - player.RpcCastVote((byte)arg); - } - break; - - case "/say": - case "/s": - case "/с": - case "/сказать": - if (player.FriendCode.GetDevUser().IsDev) - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")} ~ {player.GetRealName(clientData: true)}"); - } - else if (player.FriendCode.IsDevUser() && !dbConnect.IsBooster(player.FriendCode)) - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")} ~ {player.GetRealName(clientData: true)}"); - } - else if (Utils.IsPlayerModerator(player.FriendCode)) - { - if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowSayCommand.GetBool() == false) - { - Utils.SendMessage(GetString("SayCommandDisabled"), player.PlayerId); - break; - } - else - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromModerator")} ~ {player.GetRealName(clientData: true)}"); - //string moderatorName3 = player.GetRealName().ToString(); - //int startIndex3 = moderatorName3.IndexOf("♥") + "♥".Length; - //moderatorName3 = moderatorName3.Substring(startIndex3); - string modLogname3 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n4) ? n4 : ""; - - string moderatorFriendCode3 = player.FriendCode.ToString(); - string logMessage3 = $"[{DateTime.Now}] {moderatorFriendCode3},{modLogname3} used /s: {args.Skip(1).Join(delimiter: " ")}"; - File.AppendAllText(modLogFiles, logMessage3 + Environment.NewLine); - - } - } - break; - case "/rps": - //canceled = true; - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - else if (playerChoice < 0 || playerChoice > 2) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(0, 3); - var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; - if (botChoice == playerChoice) - { - Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), player.PlayerId); - } - else if ((botChoice == 0 && playerChoice == 2) || - (botChoice == 1 && playerChoice == 0) || - (botChoice == 2 && playerChoice == 1)) - { - Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), player.PlayerId); - } - else - { - Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), player.PlayerId); - } - break; - } - case "/coinflip": - //canceled = true; - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("CoinflipCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(1,101); - var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); - Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), player.PlayerId); - break; - } - case "/gno": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - //canceled = true; - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - else if (guessedNo < 0 || guessedNo > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - else - { - int targetNumber = Main.GuessNumber[player.PlayerId][0]; - if (Main.GuessNumber[player.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[player.PlayerId][0]; - } - Main.GuessNumber[player.PlayerId][1]--; - if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - //targetNumber = Main.GuessNumber[player.PlayerId][0]; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); - break; - } - else if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - break; - } - else if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - break; - } - else - { - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - break; - } - } - case "/rand": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - subArgs = args.Length != 3 ? "" : args[1]; - subArgs2 = args.Length != 3 ? "" : args[2]; - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); - break; - } - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) - { - Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botResult = rand.Next(playerChoice1, playerChoice2 + 1); - Utils.SendMessage(string.Format(GetString("RandResult"), botResult), player.PlayerId); - break; - } - case "/8ball": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - canceled = true; - var rando = IRandom.Instance; - int result = rando.Next(0, 16); - string str = ""; - switch (result) - { - case 0: - str = GetString("8BallYes"); - break; - case 1: - str = GetString("8BallNo"); - break; - case 2: - str = GetString("8BallMaybe"); - break; - case 3: - str = GetString("8BallTryAgainLater"); - break; - case 4: - str = GetString("8BallCertain"); - break; - case 5: - str = GetString("8BallNotLikely"); - break; - case 6: - str = GetString("8BallLikely"); - break; - case 7: - str = GetString("8BallDontCount"); - break; - case 8: - str = GetString("8BallStop"); - break; - case 9: - str = GetString("8BallPossibly"); - break; - case 10: - str = GetString("8BallProbably"); - break; - case 11: - str = GetString("8BallProbablyNot"); - break; - case 12: - str = GetString("8BallBetterNotTell"); - break; - case 13: - str = GetString("8BallCantPredict"); - break; - case 14: - str = GetString("8BallWithoutDoubt"); - break; - case 15: - str = GetString("8BallWithDoubt"); - break; - } - Utils.SendMessage("" + str + "", player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); - break; - case "/me": - - string Devbox = player.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; - string UpBox = player.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; - string ColorBox = player.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; - - subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); - if (string.IsNullOrEmpty(subArgs)) - { - Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), player.PlayerId, player.GetRealName(clientData: true), player.GetClient().FriendCode, player.GetClient().GetHashedPuid(), player.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); - } - else - { - if (Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("Message.MeCommandNoPermission"), player.PlayerId); - break; - } - - - - if (byte.TryParse(subArgs, out byte meid)) - { - if (meid != player.PlayerId) - { - var targetplayer = Utils.GetPlayerById(meid); - if (targetplayer != null && targetplayer.GetClient() != null) - { - Utils.SendMessage($"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}", player.PlayerId); - } - else - { - Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); - } - } - else - { - Utils.SendMessage($"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); - } - } - else - { - Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); - } - } - break; - - - default: - if (SpamManager.CheckSpam(player, text)) return; - break; - } - } -} -[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] -class ChatUpdatePatch -{ - public static bool DoBlockChat = false; - public static ChatController Instance; - public static void Postfix(ChatController __instance) - { - if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; - if (DoBlockChat) return; - - Instance ??= __instance; - - if (Main.DarkTheme.Value) - { - var chatBubble = __instance.chatBubblePool.Prefab.Cast(); - chatBubble.TextArea.overrideColorTags = false; - chatBubble.TextArea.color = Color.white; - chatBubble.Background.color = Color.black; - } - - var player = PlayerControl.LocalPlayer; - if (GameStates.IsInGame || player.Data.IsDead) - { - player = Main.AllAlivePlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() - ?? Main.AllPlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() - ?? player; - } - //Logger.Info($"player is null? {player == null}", "ChatUpdatePatch"); - if (player == null) return; - - (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; - //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); - - if (sendTo != byte.MaxValue && GameStates.IsLobby) - { - var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); - if (networkedPlayerInfo != null) - { - if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) - { - var delaymessage = Main.MessagesToSend[0]; - Main.MessagesToSend.RemoveAt(0); - Main.MessagesToSend.Add(delaymessage); - return; - } - // green beans color id is -1 - } - // It is impossible to get null player here unless it quits - } - Main.MessagesToSend.RemoveAt(0); - - int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); - var name = player.Data.PlayerName; - - //__instance.freeChatField.textArea.characterLimit = 999; - - if (clientId == -1) - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); - player.SetName(name); - } - - - var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); - writer.StartMessage(clientId); - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(title) - .EndRpc(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) - .Write(msg) - .EndRpc(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(player.Data.PlayerName) - .EndRpc(); - writer.EndMessage(); - writer.SendMessage(); - - __instance.timeSinceLastMessage = 0f; - } -} -[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] -internal class UpdateCharCountPatch -{ - public static void Postfix(FreeChatInputField __instance) - { - int length = __instance.textArea.text.Length; - __instance.charCountText.SetText($"{length}/{__instance.textArea.characterLimit}"); - if (length < (AmongUsClient.Instance.AmHost ? 888 : 250)) - __instance.charCountText.color = Color.black; - else if (length < (AmongUsClient.Instance.AmHost ? 999 : 300)) - __instance.charCountText.color = new Color(1f, 1f, 0f, 1f); - else - __instance.charCountText.color = Color.red; - } -} -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] -class RpcSendChatPatch -{ - public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) - { - if (string.IsNullOrWhiteSpace(chatText)) - { - __result = false; - return false; - } - if (!GameStates.IsModHost) - { - __result = false; - return true; - } - int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); - chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); - if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) - DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); - if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) - DestroyableSingleton.Instance.SendWho(); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); - messageWriter.Write(chatText); - messageWriter.EndMessage(); - __result = true; - return false; - } -} +using Assets.CoreScripts; +using Hazel; +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using TOHE.Modules; +using TOHE.Modules.ChatManager; +using TOHE.Roles.Core; +using TOHE.Roles.Core.AssignManager; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using UnityEngine; +using static TOHE.Translator; + + +namespace TOHE; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] +internal class ChatCommands +{ + private static readonly string modLogFiles = @"./TOHE-DATA/ModLogs.txt"; + private static readonly string modTagsFiles = @"./TOHE-DATA/Tags/MOD_TAGS"; + private static readonly string sponsorTagsFiles = @"./TOHE-DATA/Tags/SPONSOR_TAGS"; + private static readonly string vipTagsFiles = @"./TOHE-DATA/Tags/VIP_TAGS"; + + private static readonly Dictionary Pollvotes = []; + private static readonly Dictionary PollQuestions = []; + private static readonly List PollVoted = []; + private static float Polltimer = 120f; + private static string PollMSG = ""; + + public const string Csize = "85%"; // CustomRole Settings Font-Size + public const string Asize = "75%"; // All Appended Addons Font-Size + + public static List ChatHistory = []; + + public static bool Prefix(ChatController __instance) + { + if (__instance.quickChatField.visible == false && __instance.freeChatField.textArea.text == "") return false; + if (!GameStates.IsModHost && !AmongUsClient.Instance.AmHost) return true; + __instance.timeSinceLastMessage = 3f; + var text = __instance.freeChatField.textArea.text; + if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); + ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; + string[] args = text.Split(' '); + string subArgs = ""; + string subArgs2 = ""; + var canceled = false; + var cancelVal = ""; + Main.isChatCommand = true; + Logger.Info(text, "SendChat"); + if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && AmongUsClient.Instance.AmHost) // Blackmailer.ForBlackmailer.Contains(PlayerControl.LocalPlayer.PlayerId)) && PlayerControl.LocalPlayer.IsAlive()) + { + ChatManager.SendMessage(PlayerControl.LocalPlayer, text); + } + //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn" && text[..3] != "/rs") args[0] = "/r"; + if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; + + if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + Directory.CreateDirectory(modTagsFiles); + Directory.CreateDirectory(vipTagsFiles); + Directory.CreateDirectory(sponsorTagsFiles); + + if (Blackmailer.CheckBlackmaile(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()) + { + goto Canceled; + } + switch (args[0]) + { + case "/dump": + case "/导出日志": + case "/日志": + case "/导出": + Utils.DumpLog(); + break; + case "/v": + case "/version": + case "/versão": + case "/版本": + canceled = true; + string version_text = ""; + var player = PlayerControl.LocalPlayer; + var title = "" + GetString("DefaultSystemMessageTitle") + ""; + var name = player?.Data?.PlayerName; + try + { + foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key).ToArray()) + { + var pc = Utils.GetClientById(kvp.Key)?.Character; + version_text += $"{kvp.Key}/{(pc?.PlayerId != null ? pc.PlayerId.ToString() : "null")}:{pc?.GetRealName(clientData: true) ?? "null"}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; + } + if (version_text != "") + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, version_text); + player.SetName(name); + } + } + catch (Exception e) + { + Logger.Error(e.Message, "/version"); + version_text = "Error while getting version : " + e.Message; + if (version_text != "") + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, version_text); + player.SetName(name); + } + } + break; + + default: + Main.isChatCommand = false; + break; + } + if (AmongUsClient.Instance.AmHost) + { + Main.isChatCommand = true; + switch (args[0]) + { + case "/ans": + case "/asw": + case "/answer": + case "/回答": + Quizmaster.AnswerByChat(PlayerControl.LocalPlayer, args); + break; + + case "/qmquiz": + case "/提问": + Quizmaster.ShowQuestion(PlayerControl.LocalPlayer); + break; + + case "/win": + case "/winner": + case "/vencedor": + case "/胜利": + case "/获胜": + case "/赢": + canceled = true; + if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists")); + else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList)); + break; + + case "/l": + case "/lastresult": + case "/fimdejogo": + case "/上局信息": + case "/信息": + case "/情况": + canceled = true; + Utils.ShowKillLog(); + Utils.ShowLastRoles(); + Utils.ShowLastResult(); + break; + + case "/gr": + case "/gameresults": + case "/resultados": + case "/对局结果": + case "/上局结果": + case "/结果": + canceled = true; + Utils.ShowLastResult(); + break; + + case "/kh": + case "/killlog": + case "/击杀日志": + case "/击杀情况": + canceled = true; + Utils.ShowKillLog(); + break; + + case "/rs": + case "/sum": + case "/rolesummary": + case "/sumario": + case "/sumário": + case "/summary": + case "/результат": + case "/上局职业": + case "/职业信息": + case "/对局职业": + canceled = true; + Utils.ShowLastRoles(); + break; + + case "/ghostinfo": + case "/幽灵职业介绍": + case "/鬼魂职业介绍": + case "/幽灵职业": + case "/鬼魂职业": + canceled = true; + Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/apocinfo": + case "/apocalypseinfo": + case "/末日中立职业介绍": + case "/末日中立介绍": + case "/末日类中立职业介绍": + case "/末日类中立介绍": + canceled = true; + Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + + + case "/rn": + case "/rename": + case "/renomear": + case "/переименовать": + case "/重命名": + case "/命名为": + canceled = true; + if (args.Length < 1) break; + if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) { + Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else Main.HostRealName = args.Skip(1).Join(delimiter: " "); + Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/hn": + case "/hidename": + case "/semnome": + case "/隐藏名字": + case "/藏名": + canceled = true; + Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); + GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = + ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) + ? $"{Main.HideName.Value}" + : $"{Main.HideName.Value}"; + break; + + case "/level": + case "/nível": + case "/nivel": + case "/等级": + case "/等级设置为": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), PlayerControl.LocalPlayer.PlayerId); + _ = int.TryParse(subArgs, out int input); + if (input is < 1 or > 999) + { + Utils.SendMessage(GetString("Message.AllowLevelRange"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var number = Convert.ToUInt32(input); + PlayerControl.LocalPlayer.RpcSetLevel(number - 1); + break; + + case "/n": + case "/now": + case "/atual": + case "/设置": + case "/系统设置": + case "/模组设置": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + case "funções": + // case "职业": + //case "角色": + Utils.ShowActiveRoles(); + break; + case "a": + case "all": + case "tudo": + case "所有": + case "全部": + Utils.ShowAllActiveSettings(); + break; + default: + Utils.ShowActiveSettings(); + break; + } + break; + + case "/dis": + case "/disconnect": + case "/desconectar": + case "/断连": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "crew": + case "tripulante": + case "船员": + GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); + GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); + break; + + case "imp": + case "impostor": + case "内鬼": + case "伪装者": + GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + break; + + default: + __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); + if (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Brazilian) + { + __instance.AddChat(PlayerControl.LocalPlayer, "tripulante | impostor"); + } + cancelVal = "/dis"; + break; + } + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); + break; + + case "/r": + case "/role": + case "/р": + case "/роль": + //case "/职业": + //case "/角色": + canceled = true; + if (text.Contains("/role") || text.Contains("/роль")/* || text.Contains("/角色")*/) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); + break; + + case "/up": + case "/指定": + case "/成为": + canceled = true; + subArgs = text.Remove(0, 3); + if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp){ + Utils.SendMessage($"{GetString("InvalidPermissionCMD")}", PlayerControl.LocalPlayer.PlayerId); + break; + } + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); + break; + + //case "/setbasic": + // canceled = true; + // if (GameStates.IsLobby) + // { + // break; + // } + // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); + // break; + + case "/setplayers": + case "/maxjogadores": + case "/设置最大玩家数": + case "/设置最大玩家数量": + case "/设置玩家数": + case "/设置玩家数量": + case "/玩家数": + case "/玩家数量": + case "/玩家": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + Utils.SendMessage(GetString("Message.MaxPlayers") + subArgs); + var numbereer = Convert.ToByte(subArgs); + if (GameStates.IsNormalGame) + GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = numbereer; + + else if (GameStates.IsHideNSeek) + GameOptionsManager.Instance.currentHideNSeekGameOptions.MaxPlayers = numbereer; + break; + + case "/h": + case "/help": + case "/ajuda": + case "/хелп": + case "/хэлп": + case "/помощь": + case "/帮助": + case "/教程": + canceled = true; + Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); + break; + + case "/icon": + case "/icons": + case "/符号": + case "/标志": + { + Utils.SendMessage(GetString("Command.icons"), PlayerControl.LocalPlayer.PlayerId, GetString("IconsTitle")); + break; + } + + case "/iconhelp": + case "/符号帮助": + case "/标志帮助": + { + Utils.SendMessage(GetString("Command.icons"), title: GetString("IconsTitle")); + break; + } + + case "/kc": + case "/kcount": + case "/количество": + case "/убийцы": + case "/存活阵营": + case "/阵营": + case "/存货阵营信息": + case "/阵营信息": + if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; + + var allAlivePlayers = Main.AllAlivePlayerControls; + int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); + int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + + var sub = new StringBuilder(); + sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); + + if (Options.ShowMadmatesInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); + + Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); + break; + case "/vote": + case "/投票": + case "/票": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + } + + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + } + if (!PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + PlayerControl.LocalPlayer.RpcCastVote((byte)arg); + } + break; + + case "/d": + case "/death": + case "/morto": + case "/умер": + case "/причина": + case "/死亡原因": + case "/死亡": + canceled = true; + Logger.Info($"PlayerControl.LocalPlayer.PlayerId: {PlayerControl.LocalPlayer.PlayerId}", "/death command"); + if (GameStates.IsLobby) + { + Logger.Info("IsLobby", "/death command"); + Utils.SendMessage(text: GetString("Message.CanNotUseInLobby"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (PlayerControl.LocalPlayer.IsAlive()) + { + Logger.Info("IsAlive", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.HeyPlayer") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote) + { + Logger.Info("DeathReason.Vote", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) + { + Logger.Info("DeathReason.Shrouded", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) + { + Logger.Info("DeathReason.FollowingSuicide", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + Logger.Info("GetRealKiller()", "/death command"); + var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); + string killerName = killer == null ? "N/A" : killer.GetRealName(); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); + + break; + } + + + case "/m": + case "/myrole": + case "/minhafunção": + case "/м": + case "/мояроль": + case "/身份": + case "/我": + case "/我的身份": + case "/我的职业": + canceled = true; + var role = PlayerControl.LocalPlayer.GetCustomRole(); + if (GameStates.IsInGame) + { + var lp = PlayerControl.LocalPlayer; + var Des = lp.GetRoleInfo(true); + var title = $"" + role.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + var Sub = new StringBuilder(); + var rlHex = Utils.GetRoleColorCode(role); + var SubTitle = $"" + GetString("YourAddon") + "\n"; + + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + + foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.ToArray()) + Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + + if (Sub.ToString() != string.Empty) + { + var ACleared = Sub.ToString().Remove(0, 2); + ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; + Sub.Clear().Append(ACleared); + } + + Utils.SendMessage(Des, lp.PlayerId, title, noReplay: true); + Utils.SendMessage("", lp.PlayerId, Conf.ToString(), noReplay: true); + if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), lp.PlayerId, SubTitle, noReplay: true); + } + else + Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/me": + case "/我的权限": + case "/权限": + canceled = true; + subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); + string Devbox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; + string UpBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; + string ColorBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; + + if (string.IsNullOrEmpty(subArgs)) + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); + } + else + { + if (byte.TryParse(subArgs, out byte meid)) + { + if (meid != PlayerControl.LocalPlayer.PlayerId) + { + var targetplayer = Utils.GetPlayerById(meid); + if (targetplayer != null && targetplayer.GetClient() != null) + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}"); + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); + } + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); + } + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); + } + } + break; + + case "/t": + case "/template": + case "/шаблон": + case "/пример": + case "/模板": + case "/模板信息": + canceled = true; + if (args.Length > 1) TemplateManager.SendTemplate(args[1]); + else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", PlayerControl.LocalPlayer.PlayerId); + break; + + case "/mw": + case "/messagewait": + case "/消息等待时间": + case "/消息冷却": + canceled = true; + if (args.Length > 1 && int.TryParse(args[1], out int sec)) + { + Main.MessageWait.Value = sec; + Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); + } + else Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); + break; + + case "/tpout": + case "/传送出": + case "/传出": + canceled = true; + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcTeleport(new Vector2(0.1f, 3.8f)); + break; + case "/tpin": + case "/传进": + case "/传送进": + canceled = true; + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcTeleport(new Vector2(-0.2f, 1.3f)); + break; + + case "/say": + case "/s": + case "/с": + case "/сказать": + canceled = true; + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); + break; + + case "/mid": + case "/玩家列表": + case "/玩家信息": + case "/玩家编号列表": + canceled = true; + string msgText1 = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText1, PlayerControl.LocalPlayer.PlayerId); + break; + + case "/ban": + case "/banir": + case "/бан": + case "/забанить": + case "/封禁": + canceled = true; + + string banReason = ""; + if (args.Length < 3) + { + Utils.SendMessage(GetString("BanCommandNoReason"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + subArgs = args[1]; + banReason = string.Join(" ", args.Skip(2)); + } + //subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (banPlayerId == 0) + { + Utils.SendMessage(GetString("BanCommandBanHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var bannedPlayer = Utils.GetPlayerById(banPlayerId); + if (bannedPlayer == null) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Ban the specified player + AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); + string bannedPlayerName = bannedPlayer.GetRealName(); + string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{PlayerControl.LocalPlayer.name} \nReason: {banReason}\n"; + if (GameStates.IsInGame) + { + textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend1); + //string moderatorName = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; + //moderatorName = moderatorName.Substring(startIndex); + //string extractedString = + string moderatorFriendCode = PlayerControl.LocalPlayer.FriendCode.ToString(); + string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); + string modLogname = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n1) ? n1 : ""; + string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; + string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{banlogname} Reason: {banReason}"; + File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); + break; + + case "/warn": + case "/aviso": + case "/варн": + case "/пред": + case "/предупредить": + case "/警告": + case "/提醒": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (warnPlayerId == 0) + { + Utils.SendMessage(GetString("WarnCommandWarnHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var warnedPlayer = Utils.GetPlayerById(warnPlayerId); + if (warnedPlayer == null) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // warn the specified player + string textToSend2 = ""; + string warnReason = "Reason : Not specified\n"; + string warnedPlayerName = warnedPlayer.GetRealName(); + //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; + if (args.Length > 2) + { + warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + } + else + { + Utils.SendMessage(GetString("WarnExample"), PlayerControl.LocalPlayer.PlayerId); + } + textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{PlayerControl.LocalPlayer.name}"; + Utils.SendMessage(textToSend2); + //string moderatorName1 = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; + //moderatorName1 = moderatorName1.Substring(startIndex1); + string modLogname1 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n2) ? n2 : ""; + string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; + + string moderatorFriendCode1 = PlayerControl.LocalPlayer.FriendCode.ToString(); + string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); + string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); + string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; + File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); + + break; + + case "/kick": + case "/expulsar": + case "/кик": + case "/кикнуть": + case "/выгнать": + case "/踢出": + case "/踢": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (kickPlayerId == 0) + { + Utils.SendMessage(GetString("KickCommandKickHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var kickedPlayer = Utils.GetPlayerById(kickPlayerId); + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); + string kickedPlayerName = kickedPlayer.GetRealName(); + string kickReason = "Reason : Not specified\n"; + if (args.Length > 2) + kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + else + { + Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", PlayerControl.LocalPlayer.PlayerId); + } + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {PlayerControl.LocalPlayer.name} \n {kickReason}"; + + if (GameStates.IsInGame) + { + textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + //string moderatorName2 = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; + //moderatorName2 = moderatorName2.Substring(startIndex2); + + string modLogname2 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n3) ? n3 : ""; + string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; + + string moderatorFriendCode2 = PlayerControl.LocalPlayer.FriendCode.ToString(); + string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); + string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); + string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; + File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); + + break; + + case "/tagcolor": + case "/tagcolour": + case "/标签颜色": + case "/附加名称颜色": + canceled = true; + string name = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n) ? n : ""; + if (name == "") break; + if (!name.Contains('\r') && PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag()) + { + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "tagcolor"); + Utils.SendMessage(GetString("TagColorInvalidHexCode"), PlayerControl.LocalPlayer.PlayerId); + break; + } + string tagColorFilePath = $"{sponsorTagsFiles}/{PlayerControl.LocalPlayer.FriendCode}.txt"; + if (!File.Exists(tagColorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); + File.Create(tagColorFilePath).Close(); + } + File.WriteAllText(tagColorFilePath, $"{subArgs}"); + } + break; + + case "/exe": + case "/уничтожить": + case "/повесить": + case "/казнить": + case "/казнь": + case "/мут": + case "/驱逐": + case "/驱赶": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (args.Length < 2 || !int.TryParse(args[1], out int id)) break; + var player = Utils.GetPlayerById(id); + if (player != null) + { + player.Data.IsDead = true; + player.SetDeathReason(PlayerState.DeathReason.etc); + player.SetRealKiller(PlayerControl.LocalPlayer); + Main.PlayerStates[player.PlayerId].SetDead(); + player.RpcExileV2(); + MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); + + if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + } + break; + + case "/kill": + case "/matar": + case "/убить": + case "/击杀": + case "/杀死": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (args.Length < 2 || !int.TryParse(args[1], out int id2)) break; + var target = Utils.GetPlayerById(id2); + if (target != null) + { + target.RpcMurderPlayer(target); + if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); + + _ = new LateTask(() => + { + Utils.NotifyRoles(NoCache: true); + + }, 0.2f, "Update NotifyRoles players after /kill"); + } + break; + + case "/colour": + case "/color": + case "/cor": + case "/цвет": + case "/颜色": + case "/更改颜色": + case "/修改颜色": + case "/换颜色": + canceled = true; + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + var color = Utils.MsgToColor(subArgs, true); + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/quit": + case "/qt": + case "/sair": + case "/退出": + case "/退": + canceled = true; + Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/xf": + case "/修复": + case "/修": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) continue; + + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/id": + case "/айди": + case "/编号": + case "/玩家编号": + canceled = true; + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText, PlayerControl.LocalPlayer.PlayerId); + break; + + /* + case "/qq": + canceled = true; + if (Main.newLobby) Cloud.ShareLobby(true); + else Utils.SendMessage("很抱歉,每个房间车队姬只会发一次", PlayerControl.LocalPlayer.PlayerId); + break; + */ + + case "/setrole": + case "/设置的职业": + case "/指定的职业": + canceled = true; + subArgs = text.Remove(0, 8); + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); + break; + + case "/changerole": + case "/mudarfunção": + case "/改变职业": + case "/修改职业": + canceled = true; + if (GameStates.IsHideNSeek) break; + if (!(DebugModeManager.AmDebugger && GameStates.IsInGame)) break; + if (GameStates.IsOnlineGame && !PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) break; + subArgs = text.Remove(0, 11); + var setRole = FixRoleNameInput(subArgs).ToLower().Trim().Replace(" ", string.Empty); + Logger.Info(setRole, "changerole Input"); + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + //Logger.Info(roleName, "2"); + if (setRole == roleName) + { + PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); + PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes(), true); + PlayerControl.LocalPlayer.RpcSetCustomRole(rl); + PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); + Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); + Utils.NotifyRoles(NoCache: true); + Utils.MarkEveryoneDirtySettings(); + break; + } + } + break; + + case "/end": + case "/encerrar": + case "/завершить": + case "/结束": + case "/结束游戏": + canceled = true; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + break; + case "/cosid": + case "/装扮编号": + case "/衣服编号": + canceled = true; + var of = PlayerControl.LocalPlayer.Data.DefaultOutfit; + Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); + Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); + Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); + Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); + Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); + Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); + break; + + case "/mt": + case "/hy": + case "/强制过会议": + case "/强制跳过会议": + case "/过会议": + case "/结束会议": + case "/强制结束会议": + case "/跳过会议": + canceled = true; + if (GameStates.IsMeeting) + { + MeetingHud.Instance.RpcClose(); + } + else + { + PlayerControl.LocalPlayer.NoCheckStartMeeting(null, force: true); + } + break; + + case "/cs": + case "/播放声音": + case "/播放音效": + canceled = true; + subArgs = text.Remove(0, 3); + PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); + break; + + case "/sd": + case "/播放音效给": + case "/播放声音给": + canceled = true; + subArgs = text.Remove(0, 3); + if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) break; + RPC.PlaySoundRPC(PlayerControl.LocalPlayer.PlayerId, (Sounds)sound1); + break; + + case "/poll": + case "/发起投票": + case "/执行投票": + canceled = true; + + + if (args.Length == 2 && args[1] == GetString("Replay") && Pollvotes.Any() && PollMSG != string.Empty) + { + Utils.SendMessage(PollMSG); + break; + } + + PollMSG = string.Empty; + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + Polltimer = 120f; + + static System.Collections.IEnumerator StartPollCountdown() + { + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + bool playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); + + + while (playervoted && Polltimer > 0f) + { + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); + Polltimer -= Time.deltaTime; + yield return null; + } + + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + + Logger.Info($"FINNISHED!! playervote?: {!playervoted} polltime?: {Polltimer <= 0}", "/poll - StartPollCountdown"); + + DetermineResults(); + } + + static void DetermineResults() + { + int basenum = Pollvotes.Values.Max(); + var winners = Pollvotes.Where(x => x.Value == basenum); + + string msg = ""; + + Color32 clr = new(47, 234, 45, 255); //Main.PlayerColors.First(x => x.Key == PlayerControl.LocalPlayer.PlayerId).Value; + var tytul = Utils.ColorString(clr, GetString("PollResultTitle")); + + if (winners.Count() == 1) + { + var losers = Pollvotes.Where(x => x.Key != winners.First().Key); + msg = string.Format(GetString("Poll.Result"), $"{winners.First().Key}{PollQuestions[winners.First().Key]}", winners.First().Value); + + for (int i = 0; i < losers.Count(); i++) + { + msg += $"\n{losers.ElementAt(i).Key} / {losers.ElementAt(i).Value} {PollQuestions[losers.ElementAt(i).Key]}"; + + } + msg += ""; + + + Utils.SendMessage(msg, title: tytul); + } + else + { + var tienum = Pollvotes.Values.Max(); + var tied = Pollvotes.Where(x => x.Value == tienum); + + for (int i = 0; i < (tied.Count() - 1); i++) + { + msg += "\n" + tied.ElementAt(i).Key + PollQuestions[tied.ElementAt(i).Key] + " & "; + } + msg += "\n" + tied.Last().Key + PollQuestions[tied.Last().Key]; + + Utils.SendMessage(string.Format(GetString("Poll.Tied"), msg, tienum), title: tytul); + } + + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + } + + + if (Main.AllPlayerControls.Length < 3) + { + Utils.SendMessage(GetString("Poll.MissingPlayers"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Poll.OnlyInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (args.SkipWhile(x => !x.Contains('?')).ToArray().Length < 3 || !args.Any(x => x.Contains('?'))) + { + Utils.SendMessage(GetString("PollUsage"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var resultat = args.TakeWhile(x => !x.Contains('?')).Concat(args.SkipWhile(x => !x.Contains('?')).Take(1)); + + string tytul = string.Join(" ", resultat.Skip(1)); + bool Longtitle = tytul.Length > 30; + tytul = Utils.ColorString(Palette.PlayerColors[PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId], tytul); + var altTitle = Utils.ColorString(new Color32(151, 198, 230, 255), GetString("PollTitle")); + + var ClearTIT = args.ToList(); + ClearTIT.RemoveRange(0, resultat.ToArray().Length); + + var Questions = ClearTIT.ToArray(); + string msg = ""; + + + if (Longtitle) msg += "" + tytul + "\n\n"; + for (int i = 0; i < Math.Clamp(Questions.Length, 2, 5); i++) + { + msg += Utils.ColorString(RndCLR(), $"{char.ToUpper((char)(i + 65))}) {Questions[i]}\n"); + Pollvotes[char.ToUpper((char)(i + 65))] = 0; + PollQuestions[char.ToUpper((char)(i + 65))] = $"〖 {Questions[i]} 〗"; + } + msg += $"\n{GetString("Poll.Begin")}"; + msg += $"\n{GetString("Poll.TimeInfo")}"; + PollMSG = !Longtitle ? "" + tytul + "\n\n" + msg : msg; + + Logger.Info($"Poll message: {msg}", "MEssapoll"); + + Utils.SendMessage(msg, title: !Longtitle ? tytul: altTitle); + + Main.Instance.StartCoroutine(StartPollCountdown()); + + + static Color32 RndCLR() + { + byte r, g, b; + + r = (byte)IRandom.Instance.Next(45, 185); + g = (byte)IRandom.Instance.Next(45, 185); + b = (byte)IRandom.Instance.Next(45, 185); + + return new Color32(r, g, b, 255); + } + + break; + + case "/rps": + case "/剪刀石头布": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + subArgs = args.Length != 2 ? "" : args[1]; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (playerChoice < 0 || playerChoice > 2) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(0, 3); + var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; + if (botChoice == playerChoice) + { + Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + else if ((botChoice == 0 && playerChoice == 2) || + (botChoice == 1 && playerChoice == 0) || + (botChoice == 2 && playerChoice == 1)) + { + Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + else + { + Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + break; + } + case "/coinflip": + case "/抛硬币": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CoinFlipCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(1, 101); + var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); + Utils.SendMessage(string.Format(GetString("CoinFlipResult"),coinSide), PlayerControl.LocalPlayer.PlayerId); + break; + } + case "/gno": + case "/猜数字": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo < 0 || guessedNo > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + int targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + } + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]--; + if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] == 0 && guessedNo != targetNumber) + { + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; + //targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo < targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo > targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; + break; + } + + } + case "/rand": + case "/XY数字": + case "/范围游戏": + case "/猜范围": + case "/范围": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + subArgs = args.Length != 3 ? "" : args[1]; + subArgs2 = args.Length != 3 ? "" : args[2]; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) + { + Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botResult = rand.Next(playerChoice1, playerChoice2 + 1); + Utils.SendMessage(string.Format(GetString("RandResult"), botResult), PlayerControl.LocalPlayer.PlayerId); + break; + } + + case "/8ball": + case "/8号球": + case "/幸运球": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + var rando = IRandom.Instance; + int result = rando.Next(0, 16); + string str = ""; + switch (result) + { + case 0: + str = GetString("8BallYes"); + break; + case 1: + str = GetString("8BallNo"); + break; + case 2: + str = GetString("8BallMaybe"); + break; + case 3: + str = GetString("8BallTryAgainLater"); + break; + case 4: + str = GetString("8BallCertain"); + break; + case 5: + str = GetString("8BallNotLikely"); + break; + case 6: + str = GetString("8BallLikely"); + break; + case 7: + str = GetString("8BallDontCount"); + break; + case 8: + str = GetString("8BallStop"); + break; + case 9: + str = GetString("8BallPossibly"); + break; + case 10: + str = GetString("8BallProbably"); + break; + case 11: + str = GetString("8BallProbablyNot"); + break; + case 12: + str = GetString("8BallBetterNotTell"); + break; + case 13: + str = GetString("8BallCantPredict"); + break; + case 14: + str = GetString("8BallWithoutDoubt"); + break; + case 15: + str = GetString("8BallWithDoubt"); + break; + } + Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); + break; + + default: + Main.isChatCommand = false; + break; + } + } + goto Skip; + Canceled: + Main.isChatCommand = false; + canceled = true; + Skip: + if (canceled) + { + Logger.Info("Command Canceled", "ChatCommand"); + __instance.freeChatField.textArea.Clear(); + __instance.freeChatField.textArea.SetText(cancelVal); + + __instance.quickChatMenu.Clear(); + __instance.quickChatField.Clear(); + } + return !canceled; + } + + public static string FixRoleNameInput(string text) + { + text = text.Replace("着", "者").Trim().ToLower(); + return text switch + { + // Because of partial translation conflicts (zh-cn and zh-tw) + // Need to wait for follow-up finishing + + /* + // GM + "GM(遊戲大師)" or "管理员" or "管理" or "gm" or "GM" => GetString("GM"), + + // 原版职业 + "船員" or "船员" or "白板" or "天选之子" => GetString("CrewmateTOHE"), + "工程師" or "工程师" => GetString("EngineerTOHE"), + "科學家" or "科学家" => GetString("ScientistTOHE"), + "守護天使" or "守护天使" => GetString("GuardianAngelTOHE"), + "偽裝者" or "内鬼" => GetString("ImpostorTOHE"), + "變形者" or "变形者" => GetString("ShapeshifterTOHE"), + + // 隱藏職業 and 隐藏职业 + "陽光開朗大男孩" or "阳光开朗大男孩" => GetString("Sunnyboy"), + "吟遊詩人" or "吟游诗人" => GetString("Bard"), + "核爆者" or "核武器" => GetString("Nuker"), + + // 偽裝者陣營職業 and 内鬼阵营职业 + "賞金獵人" or "赏金猎人" or "赏金" => GetString("BountyHunter"), + "煙火工匠" or "烟花商人" or "烟花爆破者" or "烟花" => GetString("Fireworker"), + "嗜血殺手" or "嗜血杀手" or "嗜血" => GetString("Mercenary"), + "百变怪" or "千面鬼" or "千面" => GetString("ShapeMaster"), + "吸血鬼" or "吸血" => GetString("Vampire"), + "吸血鬼之王" or "吸血鬼女王" => GetString("Vampiress"), + "術士" or "术士" => GetString("Warlock"), + "刺客" or "忍者" => GetString("Ninja"), + "僵屍" or "僵尸" or"殭屍" or "丧尸" => GetString("Zombie"), + "駭客" or "骇客" or "黑客" => GetString("Anonymous"), + "礦工" or "矿工" => GetString("Miner"), + "殺人機器" or "杀戮机器" or "杀戮" or "机器" or "杀戮兵器" => GetString("KillingMachine"), + "通緝犯" or "逃逸者" or "逃逸" => GetString("Escapist"), + "女巫" => GetString("Witch"), + "傀儡師" or "傀儡师" or "傀儡" => GetString("Puppeteer"), + "主謀" or "策划者" => GetString("Mastermind"), + "時間竊賊" or "蚀时者" or "蚀时" or "偷时" => GetString("TimeThief"), + "狙擊手" or "狙击手" or "狙击" => GetString("Sniper"), + "送葬者" or "暗杀者" => GetString("Undertaker"), + "裂縫製造者" or "裂缝制造者" => GetString("RiftMaker"), + "邪惡的追踪者" or "邪恶追踪者" or "邪恶的追踪者" => GetString("EvilTracker"), + "邪惡賭怪" or "邪恶赌怪" or "坏赌" or "恶赌" or "邪恶赌怪" => GetString("EvilGuesser"), + "監管者" or "监管者" or "监管" => GetString("AntiAdminer"), + "狂妄殺手" or "狂妄杀手" => GetString("Arrogance"), + "自爆兵" or "自爆" => GetString("Bomber"), + "清道夫" or "清道" => GetString("Scavenger"), + "陷阱師" or "诡雷" => GetString("Trapster"), + "歹徒" => GetString("Gangster"), + "清潔工" or "清理工" or "清洁工" => GetString("Cleaner"), + "球狀閃電" or "球状闪电" => GetString("Lightning"), + "貪婪者" or "贪婪者" or "贪婪" => GetString("Greedy"), + "被詛咒的狼" or "呪狼" => GetString("CursedWolf"), + "換魂師" or "夺魂者" or "夺魂" => GetString("SoulCatcher"), + "快槍手" or "快枪手" or "快枪" => GetString("QuickShooter"), + "隱蔽者" or "隐蔽者" or "小黑人" => GetString("Camouflager"), + "抹除者" or "抹除" => GetString("Eraser"), + "肢解者" or "肢解" => GetString("Butcher"), + "劊子手" or "刽子手" => GetString("Hangman"), + "隱身人" or "隐匿者" or "隐匿" or "隐身" => GetString("Swooper"), + "船鬼" => GetString("Crewpostor"), + "野人" => GetString("Wildling"), + "騙術師" or "骗术师" => GetString("Trickster"), + "衛道士" or "卫道士" or "内鬼市长" => GetString("Vindicator"), + "寄生蟲" or "寄生虫" => GetString("Parasite"), + "分散者" or "分散" => GetString("Disperser"), + "抑鬱者" or "抑郁者" or "抑郁" => GetString("Inhibitor"), + "破壞者" or "破坏者" or "破坏" => GetString("Saboteur"), + "議員" or "邪恶法官" or "议员" or "邪恶审判" => GetString("Councillor"), + "眩暈者" or "眩晕者" or "眩晕" => GetString("Dazzler"), + "簽約人" or "死亡契约" or "死亡" or "锲约" => GetString("Deathpact"), + "吞噬者" or "吞噬" => GetString("Devourer"), + "軍師" or "军师" => GetString("Consigliere"), + "化型者" or "化形者" => GetString("Morphling"), + "躁動者" or "龙卷风" => GetString("Twister"), + "策畫者" or "潜伏者" or "潜伏" => GetString("Lurker"), + "罪犯" => GetString("Convict"), + "幻想家" or "幻想" => GetString("Visionary"), + "逃亡者" or "逃亡" => GetString("Refugee"), + "潛伏者" or "失败者" or "失败的man" or "失败" => GetString("Underdog"), + "賭博者" or "速度者" or "速度" => GetString("Ludopath"), + "懸賞者" or "教父" => GetString("Godfather"), + "天文學家" or "天文学家" or "天文家" or "天文学" => GetString("Chronomancer"), + "設陷者" or "设陷者" or "设陷" => GetString("Pitfall"), + "狂戰士" or "狂战士" or "升级者" or "狂战士" => GetString("Berserker"), + "壞迷你船員" or "坏迷你船员" or "坏小孩" or "坏迷你" => GetString("EvilMini"), + "勒索者" or "勒索" => GetString("Blackmailer"), + "教唆者" or "教唆" => GetString("Instigator"), + + // 船員陣營職業 and 船员阵营职业 + "擺爛人" or "摆烂人" or "摆烂" => GetString("Needy"), + "大明星" or "明星" => GetString("SuperStar"), + "網紅" or "网红" => GetString("Celebrity"), + "清洗者" or "清洗" => GetString("Cleanser"), + "守衛者" or "守卫者" => GetString("Keeper"), + "俠客" or "侠客" or "正义使者" => GetString("Knight"), + "市長" or "市长" => GetString("Mayor"), + "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), + "愚者" => GetString("Psychic"), + "修理工" or "修理" or "修理大师" => GetString("Mechanic"), + "警長" or "警长" => GetString("Sheriff"), + "義警" or "义务警员" or "警员" => GetString("Vigilante"), + "監禁者" or "狱警" or "狱卒" => GetString("Jailer"), + "模仿者" or "模仿猫" or "模仿" => GetString("CopyCat"), + "告密者" => GetString("Snitch"), + "展現者" or "展现者" or "展现" => GetString("Marshall"), + "增速師" or "增速者" or "增速" => GetString("SpeedBooster"), + "法醫" or "法医" => GetString("Doctor"), + "獨裁主義者" or "独裁者" or "独裁" => GetString("Dictator"), + "偵探" or "侦探" => GetString("Detective"), + "正義賭怪" or "正义赌怪" or "好赌" or "正义的赌怪" => GetString("NiceGuesser"), + "賭場管理員" or "竞猜大师" or "竞猜" => GetString("GuessMaster"), + "傳送師" or "传送师" => GetString("Transporter"), + "時間大師" or "时间操控者" or "时间操控" => GetString("TimeManager"), + "老兵" => GetString("Veteran"), + "埋雷兵" => GetString("Bastion"), + "保鑣" or "保镖" => GetString("Bodyguard"), + "贗品商" or "赝品商" => GetString("Deceiver"), + "擲彈兵" or "掷雷兵" => GetString("Grenadier"), + "軍醫" or "医生" => GetString("Medic"), + "占卜師" or "调查员" or "占卜师" => GetString("FortuneTeller"), + "法官" or "正义法官" or "正义审判" => GetString("Judge"), + "殯葬師" or "入殓师" => GetString("Mortician"), + "通靈師" or "通灵师" => GetString("Mediumshiper"), + "和平之鴿" or "和平之鸽" => GetString("Pacifist"), + "窺視者" or "观察者" or "观察" => GetString("Observer"), + "君主" => GetString("Monarch"), + "預言家" or "预言家" or "预言" => GetString("Overseer"), + "驗屍官" or "验尸官" or "验尸" => GetString("Coroner"), + "正義的追蹤者" or "正义追踪者" or "正义的追踪者" => GetString("Tracker"), + "商人" => GetString("Merchant"), + "總統" or "总统" => GetString("President"), + "獵鷹" or "猎鹰" => GetString("Hawk"), + "捕快" or "下属" => GetString("Deputy"), + "算命師" or "研究者" => GetString("Investigator"), + "守護者" or "守护者" or "守护" => GetString("Guardian"), + "賢者" or "瘾君子" or "醉酒" => GetString("Addict"), + "鼹鼠" => GetString("Mole"), + "藥劑師" or "炼金术士" or "药剂" => GetString("Alchemist"), + "尋跡者" or "寻迹者" or "寻迹" or "寻找鸡腿" => GetString("Tracefinder"), + "先知" or "神谕" or "神谕者" => GetString("Oracle"), + "靈魂論者" or "灵魂论者" => GetString("Spiritualist"), + "變色龍" or "变色龙" or "变色" => GetString("Chameleon"), + "檢查員" or "检查员" or "检查" => GetString("Inspector"), + "仰慕者" or "仰慕" => GetString("Admirer"), + "時間之主" or "时间之主" or "回溯时间" => GetString("TimeMaster"), + "十字軍" or "十字军" => GetString("Crusader"), + "遐想者" or "遐想" => GetString("Reverie"), + "瞭望者" or "瞭望员" => GetString("Lookout"), + "通訊員" or "通信员" => GetString("Telecommunication"), + "執燈人" or "执灯人" or "执灯" or "灯人" or "小灯人" => GetString("Lighter"), + "任務管理員" or "任务管理者" => GetString("TaskManager"), + "目擊者" or "目击者" or "目击" => GetString("Witness"), + "換票師" or "换票师" => GetString("Swapper"), + "警察局長" or "警察局长" => GetString("ChiefOfPolice"), + "好迷你船員" or "好迷你船员" or "好迷你" or "好小孩" => GetString("NiceMini"), + "間諜" or "间谍" => GetString("Spy"), + "隨機者" or "萧暮" or "暮" or "萧暮不姓萧" => GetString("Randomizer"), + "猜想者" or "猜想" or "谜团" => GetString("Enigma"), + "船長" or "舰长" or "船长" => GetString("Captain"), + "慈善家" or "恩人" => GetString("Benefactor"), + + // 中立陣營職業 and 中立阵营职业 + "小丑" or "丑皇" => GetString("Jester"), + "縱火犯" or "纵火犯" or "纵火者" or "纵火" => GetString("Arsonist"), + "焚燒狂" or "焚烧狂" or "焚烧" => GetString("Pyromaniac"), + "神風特攻隊" or "神风特攻队" => GetString("Kamikaze"), + "獵人" or "猎人" => GetString("Huntsman"), + "恐怖分子" => GetString("Terrorist"), + "暴民" or "处刑人" or "处刑" or "处刑者" => GetString("Executioner"), + "律師" or "律师" => GetString("Lawyer"), + "投機主義者" or "投机者" or "投机" => GetString("Opportunist"), + "瑪利歐" or "马里奥" => GetString("Vector"), + "豺狼" or "蓝狼" => GetString("Jackal"), + "神" or "上帝" => GetString("God"), + "冤罪師" or "冤罪师" or "冤罪" => GetString("Innocent"), + "暗殺者" or "隐形者" =>GetString("Stealth"), + "企鵝" or "企鹅" =>GetString("Penguin"), + "鵜鶘" or "鹈鹕" => GetString("Pelican"), + "疫醫" or "瘟疫学家" => GetString("PlagueDoctor"), + "革命家" or "革命者" => GetString("Revolutionist"), + "單身狗" => GetString("Hater"), + "柯南" => GetString("Konan"), + "玩家" => GetString("Demon"), + "潛藏者" or "潜藏" => GetString("Stalker"), + "工作狂" => GetString("Workaholic"), + "至日者" or "至日" => GetString("Solsticer"), + "集票者" or "集票" => GetString("Collector"), + "挑釁者" or "自爆卡车" => GetString("Provocateur"), + "嗜血騎士" or "嗜血骑士" => GetString("BloodKnight"), + "瘟疫之源" or "瘟疫使者" => GetString("PlagueBearer"), + "萬疫之神" or "瘟疫" => GetString("Pestilence"), + "故障者" or "缺点者" or "缺点" => GetString("Glitch"), + "跟班" or "跟班小弟" => GetString("Sidekick"), + "追隨者" or "赌徒" or "下注" => GetString("Follower"), + "魅魔" => GetString("Cultist"), + "連環殺手" or "连环杀手" => GetString("SerialKiller"), + "劍聖" or "天启" => GetString("Juggernaut"), + "感染者" or "感染" => GetString("Infectious"), + "病原體" or "病毒" => GetString("Virus"), + "起訴人" or "起诉人" => GetString("Pursuer"), + "怨靈" or "幽灵" => GetString("Phantom"), + "挑戰者" or "决斗者" or "挑战者" => GetString("Pirate"), + "炸彈王" or "炸弹狂" or "煽动者" => GetString("Agitater"), + "獨行者" or "独行者" => GetString("Maverick"), + "被詛咒的靈魂" or "诅咒之人" => GetString("CursedSoul"), + "竊賊" or "小偷" => GetString("Pickpocket"), + "背叛者" or "背叛" => GetString("Traitor"), + "禿鷲" or "秃鹫" => GetString("Vulture"), + "搗蛋鬼" or "任务执行者" => GetString("Taskinator"), + "麵包師" or "面包师" => GetString("Baker"), + "飢荒" or "饥荒" => GetString("Famine"), + "靈魂召喚者" or "灵魂召唤者" => GetString("Spiritcaller"), + "失憶者" or "失忆者" or "失忆" => GetString("Amnesiac"), + "模仿家" or "效仿者" => GetString("Imitator"), + "強盜" => GetString("Bandit"), + "分身者" => GetString("Doppelganger"), + "受虐狂" => GetString("PunchingBag"), + "賭神" or "末日赌怪" => GetString("Doomsayer"), + "裹屍布" or "裹尸布" => GetString("Shroud"), + "月下狼人" or "狼人" => GetString("Werewolf"), + "薩滿" or "萨满" => GetString("Shaman"), + "冒險家" or "探索者" => GetString("Seeker"), + "精靈" or "小精灵" or "精灵" => GetString("Pixie"), + "咒魔" or "神秘者" => GetString("Occultist"), + "靈魂收割者" or "灵魂收集者" or "灵魂收集" or "收集灵魂" => GetString("SoulCollector"), + "薛丁格的貓" or "薛定谔的猫" => GetString("SchrodingersCat"), + "暗戀者" or "浪漫者" => GetString("Romantic"), + "報復者" or "复仇浪漫者" => GetString("VengefulRomantic"), + "絕情者" or "无情浪漫者" => GetString("RuthlessRomantic"), + "毒醫" or "投毒者" => GetString("Poisoner"), + "代碼工程師" or "巫师" => GetString("HexMaster"), + "幻影" or "魅影" => GetString("Wraith"), + "掃把星" or "扫把星" => GetString("Jinx"), + "魔藥師" or "药剂师" => GetString("PotionMaster"), + "死靈法師" or "亡灵巫师" => GetString("Necromancer"), + "測驗者" or "测验长" => GetString("Quizmaster"), + + // 附加職業 and 附加职业 + "絕境者" or "绝境者" => GetString("LastImpostor"), + "超頻" or "超频波" or "超频" => GetString("Overclocked"), + "戀人" or "恋人" => GetString("Lovers"), + "叛徒" => GetString("Madmate"), + "觀察者" or "窥视者" or "觀察" or "窥视" => GetString("Watcher"), + "閃電俠" or "闪电侠" or "閃電" or "闪电" => GetString("Flash"), + "持燈人" or "火炬" or "持燈" => GetString("Torch"), + "靈媒" or "灵媒" or "靈媒" => GetString("Seer"), + "破平者" or "破平" => GetString("Tiebreaker"), + "膽小鬼" or "胆小鬼" or "膽小" or "胆小" => GetString("Oblivious"), + "視障" or "迷幻者" or "視障" or "迷幻" => GetString("Bewilder"), + "墨鏡" or "患者" => GetString("Sunglasses"), + "加班狂" => GetString("Workhorse"), + "蠢蛋" => GetString("Fool"), + "復仇者" or "复仇者" or "復仇" or "复仇" => GetString("Avanger"), + "Youtuber" or "UP主" or "YT" => GetString("Youtuber"), + "利己主義者" or "利己主义者" or "利己主義" or "利己主义" => GetString("Egoist"), + "竊票者" or "窃票者" or "竊票" or "窃票" => GetString("TicketsStealer"), + //"雙重人格" or "双重人格" => GetString("Schizophrenic"), + "保險箱" or "宝箱怪" => GetString("Mimic"), + "賭怪" or "赌怪" => GetString("Guesser"), + "死神" => GetString("Necroview"), + "長槍" or "持枪" => GetString("Reach"), + "魅魔小弟" => GetString("Charmed"), + "乾淨" or "干净" => GetString("Cleansed"), + "誘餌" or "诱饵" => GetString("Bait"), + "陷阱師" or "陷阱师" => GetString("Trapper"), + "被感染" or "感染" => GetString("Infected"), + "防賭" or "不可被赌" => GetString("Onbound"), + "反擊者" or "回弹者" or "回弹" => GetString("Rebound"), + "平凡者" or "平凡" => GetString("Mundane"), + "騎士" or "骑士" => GetString("Knighted"), + "漠視" or "不受重视" or "被漠視的" => GetString("Unreportable"), + "被傳染" or "传染性" => GetString("Contagious"), + "幸運" or "幸运加持" => GetString("Lucky"), + "倒霉" or "倒霉蛋" => GetString("Unlucky"), + "虛無" or "无效投票" => GetString("VoidBallot"), + "敏感" or "意识者" or "意识" => GetString("Aware"), + "嬌嫩" or "脆弱" or "脆弱者" => GetString("Fragile"), + "專業" or "双重猜测" => GetString("DoubleShot"), + "流氓" => GetString("Rascal"), + "無魂" or "没有灵魂" => GetString("Soulless"), + "墓碑" => GetString("Gravestone"), + "懶人" or "懒人" => GetString("Lazy"), + "驗屍" or "尸检" => GetString("Autopsy"), + "忠誠" or "忠诚" => GetString("Loyal"), + "惡靈" or "恶灵" => GetString("EvilSpirit"), + "狼化" or "招募" or "狼化的" or "被招募的" => GetString("Recruit"), + "被仰慕" or "仰慕" => GetString("Admired"), + "發光" or "光辉" => GetString("Glow"), + "病態" or "患病者" or "患病的" or "患病" => GetString("Diseased"), + "健康" or "健康的" or "健康者" => GetString("Antidote"), + "固執者" or "固执者" or "固執" or "固执" => GetString("Stubborn"), + "無影" or "迅捷" => GetString("Swift"), + "反噬" or "食尸鬼" => GetString("Ghoul"), + "嗜血者" => GetString("Bloodthirst"), + "獵夢者" or "梦魇" or "獵夢"=> GetString("Mare"), + "地雷" or "爆破者" or "爆破" => GetString("Burst"), + "偵察員" or "侦察员" or "偵察" or "侦察" => GetString("Sleuth"), + "笨拙" or "笨蛋" => GetString("Clumsy"), + "敏捷" => GetString("Nimble"), + "規避者" or "规避者" or "规避" => GetString("Circumvent"), + "名人" or "网络员" or "网络" => GetString("Cyber"), + "焦急者" or "焦急的" or "焦急" => GetString("Hurried"), + "OIIAI" => GetString("Oiiai"), + "順從者" or "影响者" or "順從" or "影响" => GetString("Influenced"), + "沉默者" or "沉默" => GetString("Silent"), + "易感者" or "易感" => GetString("Susceptible"), + "狡猾" or "棘手者" or "棘手" => GetString("Tricky"), + "彩虹" => GetString("Rainbow"), + "疲勞者" or "疲劳者" or "疲勞" or "疲劳" => GetString("Tired"), + "雕像" => GetString("Statue"), + "没有搜集的繁体中文" or "雷达" => GetString("Radar"), + + // 幽靈職業 and 幽灵职业 + // 偽裝者 and 内鬼 + "爪牙" => GetString("Minion"), + "黑手黨" or "黑手党" or "黑手" => GetString("Nemesis"), + "嗜血之魂" or "血液伯爵" => GetString("Bloodmoon"), + // 船員 and 船员 + "没有搜集的繁体中文" or "鬼怪" => GetString("Ghastly"), + "冤魂" or "典狱长" => GetString("Warden"), + "報應者" or "惩罚者" or "惩罚" or "报仇者" => GetString("Retributionist"), + + // 随机阵营职业 + "迷你船員" or "迷你船员" or "迷你" or "小孩" or "Mini" => GetString("Mini"),*/ + _ => text, + }; + } + + public static bool GetRoleByName(string name, out CustomRoles role) + { + role = new(); + + if (name == "" || name == string.Empty) return false; + + if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) + { + Regex r = new("[\u4e00-\u9fa5]+$"); + MatchCollection mc = r.Matches(name); + string result = string.Empty; + for (int i = 0; i < mc.Count; i++) + { + if (mc[i].ToString() == "是") continue; + result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 + } + name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); + } + else name = name.Trim().ToLower(); + + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", ""); + string nameWithoutId = Regex.Replace(name.Replace(" ", ""), @"^\d+", ""); + if (nameWithoutId == roleName) + { + role = rl; + return true; + } + } + return false; + } + public static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) + { + if (Options.CurrentGameMode == CustomGameMode.FFA) + { + Utils.SendMessage(GetString("ModeDescribe.FFA"), playerId); + return; + } + role = role.Trim().ToLower(); + if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); + if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); + if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); + if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); + if (role.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); + + if (role == "" || role == string.Empty) + { + Utils.ShowActiveRoles(playerId); + return; + } + + role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); + + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()); + if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) + { + string devMark = ""; + if ((isDev || isUp) && GameStates.IsLobby) + { + devMark = "▲"; + if (CustomRolesHelper.IsAdditionRole(rl) || rl is CustomRoles.GM or CustomRoles.Mini || rl.IsGhostRole()) devMark = ""; + if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = ""; + if (isUp) + { + if (devMark == "▲") Utils.SendMessage(string.Format(GetString("Message.YTPlanSelected"), roleName), playerId); + else Utils.SendMessage(string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); + } + if (devMark == "▲") + { + byte pid = playerId == 255 ? (byte)0 : playerId; + GhostRoleAssign.forceRole.Remove(pid); + RoleAssign.SetRoles.Remove(pid); + RoleAssign.SetRoles.Add(pid, rl); + } + if (rl.IsGhostRole() && !rl.IsAdditionRole() && isDev && (rl.GetCount() >= 1 && rl.GetMode() > 0)) + { + byte pid = playerId == 255 ? (byte)0 : playerId; + CustomRoles setrole = rl.GetCustomRoleTeam() switch + { + Custom_Team.Impostor => CustomRoles.ImpostorTOHE, + _ => CustomRoles.CrewmateTOHE + + }; + RoleAssign.SetRoles.Remove(pid); + RoleAssign.SetRoles.Add(pid, setrole); + GhostRoleAssign.forceRole[pid] = rl; + + devMark = "▲"; + } + + if (isUp) return; + } + var Des = rl.GetInfoLong(); + var title = devMark + $"" + rl.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + string rlHex = Utils.GetRoleColorCode(rl); + if (Options.CustomRoleSpawnChances.ContainsKey(rl)) + { + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(rl.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + } + // Show role info + Utils.SendMessage(Des, playerId, title, noReplay: true); + + // Show role settings + Utils.SendMessage("", playerId, Conf.ToString(), noReplay: true); + return; + } + } + if (isUp) Utils.SendMessage(GetString("Message.YTPlanCanNotFindRoleThePlayerEnter"), playerId); + else Utils.SendMessage(GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); + return; + } + public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) + { + canceled = false; + if (!AmongUsClient.Instance.AmHost) return; + + if (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); + + if (text.StartsWith("\n")) text = text[1..]; + //if (!text.StartsWith("/")) return; + string[] args = text.Split(' '); + string subArgs = ""; + string subArgs2 = ""; + + //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; + // if (SpamManager.CheckSpam(player, text)) return; + if (GuessManager.GuesserMsg(player, text)) { canceled = true; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } + if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } + if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } + if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Councillor cl && cl.MurderMsg(player, text)) { canceled = true; Logger.Info($"Is Councillor command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Swapper sw && sw.SwapMsg(player, text)) { canceled = true; Logger.Info($"Is Swapper command", "OnReceiveChat"); return; } + if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } + if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } + if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } + + Directory.CreateDirectory(modTagsFiles); + Directory.CreateDirectory(vipTagsFiles); + Directory.CreateDirectory(sponsorTagsFiles); + + if (Blackmailer.CheckBlackmaile(player) && player.IsAlive()) + { + Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); + ChatManager.SendPreviousMessagesToAll(); + ChatManager.cancel = false; + canceled = true; + return; + } + + switch (args[0]) + { + case "/r": + case "/role": + case "/р": + case "/роль": + //case "/职业": + //case "/角色": + Logger.Info($"Command '/r' was activated", "OnReceiveChat"); + if (text.Contains("/role") || text.Contains("/роль")/* || text.Contains("/角色")*/) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); + break; + + case "/m": + case "/myrole": + case "/minhafunção": + case "/м": + case "/мояроль": + case "/身份": + case "/我": + case "/我的身份": + case "/我的职业": + Logger.Info($"Command '/m' was activated", "OnReceiveChat"); + var role = player.GetCustomRole(); + if (GameStates.IsInGame) + { + var Des = player.GetRoleInfo(true); + var title = $"" + role.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + var Sub = new StringBuilder(); + var rlHex = Utils.GetRoleColorCode(role); + var SubTitle = $"" + GetString("YourAddon") + "\n"; + + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(opt, ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles.ToArray()) + { + Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + + } + if (Sub.ToString() != string.Empty) + { + var ACleared = Sub.ToString().Remove(0, 2); + ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "": ACleared; + Sub.Clear().Append(ACleared); + } + + Utils.SendMessage(Des, player.PlayerId, title, noReplay: true); + Utils.SendMessage("", player.PlayerId, Conf.ToString(), noReplay: true); + if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), player.PlayerId, SubTitle, noReplay: true); + + Logger.Info($"Command '/m' should be send message", "OnReceiveChat"); + } + else + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + + case "/h": + case "/help": + case "/ajuda": + case "/хелп": + case "/хэлп": + case "/помощь": + case "/帮助": + case "/教程": + Utils.ShowHelpToClient(player.PlayerId); + break; + + case "/ans": + case "/asw": + case "/answer": + case "/回答": + Quizmaster.AnswerByChat(player, args); + break; + + case "/qmquiz": + case "/提问": + Quizmaster.ShowQuestion(player); + break; + + case "/l": + case "/lastresult": + case "/fimdejogo": + case "/上局信息": + case "/信息": + case "/情况": + Utils.ShowKillLog(player.PlayerId); + Utils.ShowLastRoles(player.PlayerId); + Utils.ShowLastResult(player.PlayerId); + break; + + case "/gr": + case "/gameresults": + case "/resultados": + case "/对局结果": + case "/上局结果": + case "/结果": + Utils.ShowLastResult(player.PlayerId); + break; + + case "/kh": + case "/killlog": + case "/击杀日志": + case "/击杀情况": + Utils.ShowKillLog(player.PlayerId); + break; + + case "/rs": + case "/sum": + case "/rolesummary": + case "/sumario": + case "/sumário": + case "/summary": + case "/результат": + case "/上局职业": + case "/职业信息": + case "/对局职业": + Utils.ShowLastRoles(player.PlayerId); + break; + + case "/ghostinfo": + case "/幽灵职业介绍": + case "/鬼魂职业介绍": + case "/幽灵职业": + case "/鬼魂职业": + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); + break; + + case "/apocinfo": + case "/apocalypseinfo": + case "/末日中立职业介绍": + case "/末日中立介绍": + case "/末日类中立职业介绍": + case "/末日类中立介绍": + Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + + case "/rn": + case "/rename": + case "/renomear": + case "/переименовать": + case "/重命名": + case "/命名为": + if (Options.PlayerCanSetName.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().NameCmd || Utils.IsPlayerVIP(player.FriendCode)) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + if (args.Length < 1) break; + if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) + { + Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); + break; + } + Main.AllPlayerNames[player.PlayerId] = args.Skip(1).Join(delimiter: " "); + Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), player.PlayerId); + break; + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/n": + case "/now": + case "/atual": + case "/设置": + case "/系统设置": + case "/模组设置": + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + case "funções": + case "职业": + case "角色": + Utils.ShowActiveRoles(player.PlayerId); + break; + case "a": + case "all": + case "tudo": + case "所有": + case "全部": + Utils.ShowAllActiveSettings(player.PlayerId); + break; + default: + Utils.ShowActiveSettings(player.PlayerId); + break; + } + break; + + case "/up": + case "/指定": + case "/成为": + _ = text.Remove(0, 3); + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), player.PlayerId); + break; + } + else + { + Utils.SendMessage(GetString("Message.OnlyCanBeUsedByHost"), player.PlayerId); + break; + } + + case "/win": + case "/winner": + case "/vencedor": + case "/胜利": + case "/获胜": + case "/赢": + if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), player.PlayerId); + else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList), player.PlayerId); + break; + + + case "/pv": + canceled = true; + if (!Pollvotes.Any()) + { + Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); + break; + } + if (PollVoted.Contains(player.PlayerId)) + { + Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); + break; + } + + subArgs = args.Length != 2 ? "" : args[1]; + char vote = ' '; + + if (int.TryParse(subArgs, out int integer) && (Pollvotes.Count - 1) >= integer) + { + vote = char.ToUpper((char)(integer + 65)); + } + else if (!(char.TryParse(subArgs, out vote) && Pollvotes.ContainsKey(char.ToUpper(vote)))) + { + Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); + break; + } + vote = char.ToUpper(vote); + + PollVoted.Add(player.PlayerId); + Pollvotes[vote]++; + Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, Pollvotes[vote]), player.PlayerId); + Logger.Info($"The new value of {vote} is {Pollvotes[vote]}", "TestPV_CHAR"); + + break; + + case "/icon": + case "/icons": + case "/符号": + case "/标志": + { + Utils.SendMessage(GetString("Command.icons"), player.PlayerId, GetString("IconsTitle")); + break; + } + + case "/kc": + case "/kcount": + case "/количество": + case "/убийцы": + case "/存活阵营": + case "/阵营": + case "/存货阵营信息": + case "/阵营信息": + if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; + + var allAlivePlayers = Main.AllAlivePlayerControls; + int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); + int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + + var sub = new StringBuilder(); + sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); + + if (Options.ShowMadmatesInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); + + Utils.SendMessage(sub.ToString(), player.PlayerId); + break; + + case "/d": + case "/death": + case "/morto": + case "/умер": + case "/причина": + case "/死亡原因": + case "/死亡": + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + else if (player.IsAlive()) + { + Utils.SendMessage(GetString("DeathCmd.HeyPlayer") + "" + player.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Vote) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), player.PlayerId); + break; + } + else + { + var killer = player.GetRealKiller(out var MurderRole); + string killerName = killer == null ? "N/A" : killer.GetRealName(); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); + break; + } + + case "/t": + case "/template": + case "/шаблон": + case "/пример": + case "/模板": + case "/模板信息": + if (args.Length > 1) TemplateManager.SendTemplate(args[1], player.PlayerId); + else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); + break; + + case "/colour": + case "/color": + case "/cor": + case "/цвет": + case "/颜色": + case "/更改颜色": + case "/修改颜色": + case "/换颜色": + if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd || Utils.IsPlayerVIP(player.FriendCode)) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + var color = Utils.MsgToColor(subArgs); + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); + break; + } + player.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/quit": + case "/qt": + case "/sair": + case "/退出": + case "/退": + if (Options.PlayerCanUseQuitCommand.GetBool()) + { + subArgs = args.Length < 2 ? "" : args[1]; + var cid = player.PlayerId.ToString(); + cid = cid.Length != 1 ? cid.Substring(1, 1) : cid; + if (subArgs.Equals(cid)) + { + string name = player.GetRealName(); + Utils.SendMessage(string.Format(GetString("Message.PlayerQuitForever"), name)); + AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); + } + else + { + Utils.SendMessage(string.Format(GetString("SureUse.quit"), cid), player.PlayerId); + } + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/id": + case "/айди": + case "/编号": + case "/玩家编号": + if ((Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + && !Options.EnableVoteCommand.GetBool()) break; + + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText, player.PlayerId); + break; + + case "/mid": + case "/玩家列表": + case "/玩家信息": + case "/玩家编号列表": + //canceled = true; + //checking if modlist on or not + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("midCommandDisabled"), player.PlayerId); + break; + } + //checking if player is has necessary privellege or not + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("midCommandNoAccess"), player.PlayerId); + break; + } + string msgText1 = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText1, player.PlayerId); + break; + + case "/ban": + case "/banir": + case "/бан": + case "/забанить": + case "/封禁": + //canceled = true; + // Check if the ban command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("BanCommandDisabled"), player.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("BanCommandNoAccess"), player.PlayerId); + break; + } + string banReason; + if (args.Length < 3) + { + Utils.SendMessage(GetString("BanCommandNoReason"), player.PlayerId); + break; + } + else + { + subArgs = args[1]; + banReason = string.Join(" ", args.Skip(2)); + } + //subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); + break; + } + + if (banPlayerId == 0) + { + Utils.SendMessage(GetString("BanCommandBanHost"), player.PlayerId); + break; + } + + var bannedPlayer = Utils.GetPlayerById(banPlayerId); + if (bannedPlayer == null) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from baning other moderators + if (Utils.IsPlayerModerator(bannedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("BanCommandBanMod"), player.PlayerId); + break; + } + + // Ban the specified player + AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); + string bannedPlayerName = bannedPlayer.GetRealName(); + string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{player.name} \nReason: {banReason}\n"; + if (GameStates.IsInGame) + { + textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend1); + //string moderatorName = player.GetRealName().ToString(); + //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; + //moderatorName = moderatorName.Substring(startIndex); + //string extractedString = + string modLogname = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n1) ? n1 : ""; + string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; + string moderatorFriendCode = player.FriendCode.ToString(); + string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); + string bannedPlayerHashPuid = bannedPlayer.GetClient().GetHashedPuid(); + string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{bannedPlayerHashPuid},{banlogname} Reason: {banReason}"; + File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); + break; + + case "/warn": + case "/aviso": + case "/варн": + case "/пред": + case "/предупредить": + case "/警告": + case "/提醒": + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("WarnCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("WarnCommandNoAccess"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); + break; + } + if (warnPlayerId == 0) + { + Utils.SendMessage(GetString("WarnCommandWarnHost"), player.PlayerId); + break; + } + + var warnedPlayer = Utils.GetPlayerById(warnPlayerId); + if (warnedPlayer == null) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from warning other moderators + if (Utils.IsPlayerModerator(warnedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("WarnCommandWarnMod"), player.PlayerId); + break; + } + // warn the specified player + string warnReason = "Reason : Not specified\n"; + string warnedPlayerName = warnedPlayer.GetRealName(); + //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; + if (args.Length > 2) + { + warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + } + else + { + Utils.SendMessage("Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", player.PlayerId); + } + Utils.SendMessage($" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{player.name}"); + //string moderatorName1 = player.GetRealName().ToString(); + //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; + //moderatorName1 = moderatorName1.Substring(startIndex1); + string modLogname1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n2) ? n2 : ""; + string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; + string moderatorFriendCode1 = player.FriendCode.ToString(); + string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); + string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); + string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; + File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); + + break; + case "/kick": + case "/expulsar": + case "/кик": + case "/кикнуть": + case "/выгнать": + case "/踢出": + case "/踢": + // Check if the kick command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); + break; + } + + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + break; + } + + if (kickPlayerId == 0) + { + Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); + break; + } + + var kickedPlayer = Utils.GetPlayerById(kickPlayerId); + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from kicking other moderators + if (Utils.IsPlayerModerator(kickedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); + string kickedPlayerName = kickedPlayer.GetRealName(); + string kickReason = "Reason : Not specified\n"; + if (args.Length > 2) + kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + else + { + Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", player.PlayerId); + } + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {player.name} \n {kickReason}"; + + if (GameStates.IsInGame) + { + textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + //string moderatorName2 = player.GetRealName().ToString(); + //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; + //moderatorName2 = moderatorName2.Substring(startIndex2); + string modLogname2 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n3) ? n3 : ""; + string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; + + string moderatorFriendCode2 = player.FriendCode.ToString(); + string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); + string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); + string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; + File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); + + break; + case "/modcolor": + case "/modcolour": + case "/模组端颜色": + case "/模组颜色": + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("ColorCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("ColorCommandNoAccess"), player.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); + break; + } + if (!Options.GradientTagsOpt.GetBool()) + { + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "modcolor"); + Utils.SendMessage(GetString("ColorInvalidHexCode"), player.PlayerId); + break; + } + string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePath)) + { + Logger.Warn($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + File.Create(colorFilePath).Close(); + } + + File.WriteAllText(colorFilePath, $"{subArgs}"); + break; + } + else + { + subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; + Regex regex = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); + if (string.IsNullOrEmpty(subArgs) || !regex.IsMatch(subArgs)) + { + Logger.Msg($"{subArgs}", "modcolor"); + Utils.SendMessage(GetString("ColorInvalidGradientCode"), player.PlayerId); + break; + } + string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + File.Create(colorFilePath).Close(); + } + //Logger.Msg($"File exists, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + //Logger.Msg($"{subArgs}","modcolor"); + File.WriteAllText(colorFilePath, $"{subArgs}"); + break; + } + case "/vipcolor": + case "/vipcolour": + case "/VIP玩家颜色": + case "/VIP颜色": + if (Options.ApplyVipList.GetValue() == 0) + { + Utils.SendMessage(GetString("VipColorCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerVIP(player.FriendCode)) + { + Utils.SendMessage(GetString("VipColorCommandNoAccess"), player.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("VipColorCommandNoLobby"), player.PlayerId); + break; + } + if (!Options.GradientTagsOpt.GetBool()) + { + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "vipcolor"); + Utils.SendMessage(GetString("VipColorInvalidHexCode"), player.PlayerId); + break; + } + string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePathh)) + { + Logger.Warn($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + File.Create(colorFilePathh).Close(); + } + + File.WriteAllText(colorFilePathh, $"{subArgs}"); + break; + } + else + { + subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; + Regex regexx = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); + if (string.IsNullOrEmpty(subArgs) || !regexx.IsMatch(subArgs)) + { + Logger.Msg($"{subArgs}", "vipcolor"); + Utils.SendMessage(GetString("VipColorInvalidGradientCode"), player.PlayerId); + break; + } + string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePathh)) + { + Logger.Msg($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + File.Create(colorFilePathh).Close(); + } + //Logger.Msg($"File exists, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + //Logger.Msg($"{subArgs}","modcolor"); + File.WriteAllText(colorFilePathh, $"{subArgs}"); + break; + } + case "/tagcolor": + case "/tagcolour": + case "/标签颜色": + case "/附加名称颜色": + string name1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; + if (name1 == "") break; + if (!name1.Contains('\r') && player.FriendCode.GetDevUser().HasTag()) + { + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "tagcolor"); + Utils.SendMessage(GetString("TagColorInvalidHexCode"), player.PlayerId); + break; + } + string tagColorFilePath = $"{sponsorTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(tagColorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); + File.Create(tagColorFilePath).Close(); + } + + File.WriteAllText(tagColorFilePath, $"{subArgs}"); + } + break; + + case "/xf": + case "/修复": + case "/修": + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) continue; + + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); + break; + + case "/tpout": + case "/传送出": + case "/传出": + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + player.RpcTeleport(new Vector2(0.1f, 3.8f)); + break; + case "/tpin": + case "/传进": + case "/传送进": + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + player.RpcTeleport(new Vector2(-0.2f, 1.3f)); + break; + + case "/vote": + case "/投票": + case "/票": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + + + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + ChatManager.SendPreviousMessagesToAll(); + } + + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), player.PlayerId); + break; + } + } + if (!player.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + player.RpcCastVote((byte)arg); + } + break; + + case "/say": + case "/s": + case "/с": + case "/сказать": + case "/说": + if (player.FriendCode.GetDevUser().IsDev) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")} ~ {player.GetRealName(clientData: true)}"); + } + else if (player.FriendCode.IsDevUser() && !dbConnect.IsBooster(player.FriendCode)) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")} ~ {player.GetRealName(clientData: true)}"); + } + else if (Utils.IsPlayerModerator(player.FriendCode)) + { + if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowSayCommand.GetBool() == false) + { + Utils.SendMessage(GetString("SayCommandDisabled"), player.PlayerId); + break; + } + else + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromModerator")} ~ {player.GetRealName(clientData: true)}"); + //string moderatorName3 = player.GetRealName().ToString(); + //int startIndex3 = moderatorName3.IndexOf("♥") + "♥".Length; + //moderatorName3 = moderatorName3.Substring(startIndex3); + string modLogname3 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n4) ? n4 : ""; + + string moderatorFriendCode3 = player.FriendCode.ToString(); + string logMessage3 = $"[{DateTime.Now}] {moderatorFriendCode3},{modLogname3} used /s: {args.Skip(1).Join(delimiter: " ")}"; + File.AppendAllText(modLogFiles, logMessage3 + Environment.NewLine); + + } + } + break; + case "/rps": + case "/剪刀石头布": + //canceled = true; + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + else if (playerChoice < 0 || playerChoice > 2) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(0, 3); + var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; + if (botChoice == playerChoice) + { + Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), player.PlayerId); + } + else if ((botChoice == 0 && playerChoice == 2) || + (botChoice == 1 && playerChoice == 0) || + (botChoice == 2 && playerChoice == 1)) + { + Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), player.PlayerId); + } + else + { + Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), player.PlayerId); + } + break; + } + case "/coinflip": + case "/抛硬币": + //canceled = true; + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("CoinflipCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(1,101); + var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); + Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), player.PlayerId); + break; + } + case "/gno": + case "/猜数字": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + //canceled = true; + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + else if (guessedNo < 0 || guessedNo > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + else + { + int targetNumber = Main.GuessNumber[player.PlayerId][0]; + if (Main.GuessNumber[player.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[player.PlayerId][0]; + } + Main.GuessNumber[player.PlayerId][1]--; + if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) + { + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + //targetNumber = Main.GuessNumber[player.PlayerId][0]; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); + break; + } + else if (guessedNo < targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + break; + } + else if (guessedNo > targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + break; + } + else + { + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + break; + } + } + case "/rand": + case "/XY数字": + case "/范围游戏": + case "/猜范围": + case "/范围": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + subArgs = args.Length != 3 ? "" : args[1]; + subArgs2 = args.Length != 3 ? "" : args[2]; + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); + break; + } + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) + { + Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botResult = rand.Next(playerChoice1, playerChoice2 + 1); + Utils.SendMessage(string.Format(GetString("RandResult"), botResult), player.PlayerId); + break; + } + case "/8ball": + case "/8号球": + case "/幸运球": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + canceled = true; + var rando = IRandom.Instance; + int result = rando.Next(0, 16); + string str = ""; + switch (result) + { + case 0: + str = GetString("8BallYes"); + break; + case 1: + str = GetString("8BallNo"); + break; + case 2: + str = GetString("8BallMaybe"); + break; + case 3: + str = GetString("8BallTryAgainLater"); + break; + case 4: + str = GetString("8BallCertain"); + break; + case 5: + str = GetString("8BallNotLikely"); + break; + case 6: + str = GetString("8BallLikely"); + break; + case 7: + str = GetString("8BallDontCount"); + break; + case 8: + str = GetString("8BallStop"); + break; + case 9: + str = GetString("8BallPossibly"); + break; + case 10: + str = GetString("8BallProbably"); + break; + case 11: + str = GetString("8BallProbablyNot"); + break; + case 12: + str = GetString("8BallBetterNotTell"); + break; + case 13: + str = GetString("8BallCantPredict"); + break; + case 14: + str = GetString("8BallWithoutDoubt"); + break; + case 15: + str = GetString("8BallWithDoubt"); + break; + } + Utils.SendMessage("" + str + "", player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); + break; + case "/me": + + string Devbox = player.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; + string UpBox = player.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; + string ColorBox = player.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; + + subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); + if (string.IsNullOrEmpty(subArgs)) + { + Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), player.PlayerId, player.GetRealName(clientData: true), player.GetClient().FriendCode, player.GetClient().GetHashedPuid(), player.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); + } + else + { + if (Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("Message.MeCommandNoPermission"), player.PlayerId); + break; + } + + + + if (byte.TryParse(subArgs, out byte meid)) + { + if (meid != player.PlayerId) + { + var targetplayer = Utils.GetPlayerById(meid); + if (targetplayer != null && targetplayer.GetClient() != null) + { + Utils.SendMessage($"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}", player.PlayerId); + } + else + { + Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); + } + } + else + { + Utils.SendMessage($"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); + } + } + else + { + Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); + } + } + break; + + + default: + if (SpamManager.CheckSpam(player, text)) return; + break; + } + } +} +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +class ChatUpdatePatch +{ + public static bool DoBlockChat = false; + public static ChatController Instance; + public static void Postfix(ChatController __instance) + { + if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; + if (DoBlockChat) return; + + Instance ??= __instance; + + if (Main.DarkTheme.Value) + { + var chatBubble = __instance.chatBubblePool.Prefab.Cast(); + chatBubble.TextArea.overrideColorTags = false; + chatBubble.TextArea.color = Color.white; + chatBubble.Background.color = Color.black; + } + + var player = PlayerControl.LocalPlayer; + if (GameStates.IsInGame || player.Data.IsDead) + { + player = Main.AllAlivePlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() + ?? Main.AllPlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() + ?? player; + } + //Logger.Info($"player is null? {player == null}", "ChatUpdatePatch"); + if (player == null) return; + + (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; + //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); + + if (sendTo != byte.MaxValue && GameStates.IsLobby) + { + var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); + if (networkedPlayerInfo != null) + { + if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) + { + var delaymessage = Main.MessagesToSend[0]; + Main.MessagesToSend.RemoveAt(0); + Main.MessagesToSend.Add(delaymessage); + return; + } + // green beans color id is -1 + } + // It is impossible to get null player here unless it quits + } + Main.MessagesToSend.RemoveAt(0); + + int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); + var name = player.Data.PlayerName; + + //__instance.freeChatField.textArea.characterLimit = 999; + + if (clientId == -1) + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); + player.SetName(name); + } + + + var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); + writer.StartMessage(clientId); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(title) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(player.Data.PlayerName) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + + __instance.timeSinceLastMessage = 0f; + } +} +[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] +internal class UpdateCharCountPatch +{ + public static void Postfix(FreeChatInputField __instance) + { + int length = __instance.textArea.text.Length; + __instance.charCountText.SetText($"{length}/{__instance.textArea.characterLimit}"); + if (length < (AmongUsClient.Instance.AmHost ? 888 : 250)) + __instance.charCountText.color = Color.black; + else if (length < (AmongUsClient.Instance.AmHost ? 999 : 300)) + __instance.charCountText.color = new Color(1f, 1f, 0f, 1f); + else + __instance.charCountText.color = Color.red; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] +class RpcSendChatPatch +{ + public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) + { + if (string.IsNullOrWhiteSpace(chatText)) + { + __result = false; + return false; + } + if (!GameStates.IsModHost) + { + __result = false; + return true; + } + int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); + chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); + if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) + DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); + if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) + DestroyableSingleton.Instance.SendWho(); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); + messageWriter.Write(chatText); + messageWriter.EndMessage(); + __result = true; + return false; + } +} From edc89313219c99617b5f87ae3a8f3412461f5bb8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 3 Oct 2024 18:25:30 +0800 Subject: [PATCH 699/778] Vanilla Map Decorations --- Modules/OptionHolder.cs | 21 ++++++++++++++++++ Patches/ShipStatusPatch.cs | 45 +++++++++++++++++++++++++------------- Resources/Lang/en_US.json | 5 +++++ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 93036fc9b3..f07b7543fa 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -271,6 +271,12 @@ private enum RatesZeroOne public static OptionItem DecontaminationTimeOnMiraHQ; public static OptionItem DecontaminationTimeOnPolus; + public static OptionItem EnableHalloweenDecorations; + public static OptionItem HalloweenDecorationsSkeld; + public static OptionItem HalloweenDecorationsMira; + public static OptionItem HalloweenDecorationsDleks; + public static OptionItem EnableBirthdayDecorationSkeld; + // Sabotage Settings public static OptionItem CommsCamouflage; public static OptionItem DisableOnSomeMaps; @@ -1272,6 +1278,21 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(ChangeDecontaminationTime) .SetValueFormat(OptionFormat.Seconds) .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + // Vanilla Map Decorations + EnableHalloweenDecorations = BooleanOptionItem.Create(60506, "EnableHalloweenDecorations", false, TabGroup.ModSettings, false) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + HalloweenDecorationsSkeld = BooleanOptionItem.Create(60507, "HalloweenDecorationsSkeld", false, TabGroup.ModSettings, false) + .SetParent(EnableHalloweenDecorations) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + HalloweenDecorationsMira = BooleanOptionItem.Create(60508, "HalloweenDecorationsMira", false, TabGroup.ModSettings, false) + .SetParent(EnableHalloweenDecorations) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + HalloweenDecorationsDleks = BooleanOptionItem.Create(60509, "HalloweenDecorationsDleks", false, TabGroup.ModSettings, false) + .SetParent(EnableHalloweenDecorations) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + EnableBirthdayDecorationSkeld = BooleanOptionItem.Create(60518, "EnableBirthdayDecorationSkeld", false, TabGroup.ModSettings, false) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + // Sabotage TextOptionItem.Create(10000026, "MenuTitle.Sabotage", TabGroup.ModSettings) .SetGameMode(CustomGameMode.Standard) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index c4704bdc53..b222ea7e68 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -175,7 +175,7 @@ public static bool Prefix(/*ShipStatus __instance,*/ SystemTypes room) [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] class StartPatch { - public static void Postfix() + public static void Postfix(ShipStatus __instance) { Logger.CurrentMethod(); Logger.Info("-----------Start of game-----------", "Phase"); @@ -196,21 +196,36 @@ public static void Postfix() } } - if (GameStates.PolusIsActive && Main.EnableCustomDecorations.Value) + switch (Utils.GetActiveMapName()) { - var Dropship = GameObject.Find("Dropship/panel_fuel"); - if (Dropship != null) - { - var Decorations = UnityEngine.Object.Instantiate(Dropship, GameObject.Find("Dropship")?.transform); - Decorations.name = "Dropship_Decorations"; - Decorations.transform.DestroyChildren(); - UnityEngine.Object.Destroy(Decorations.GetComponent()); - UnityEngine.Object.Destroy(Decorations.GetComponent()); - UnityEngine.Object.Destroy(Decorations.GetComponent()); - Decorations.GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.Dropship-Decorations.png", 100f); - Decorations.transform.SetSiblingIndex(1); - Decorations.transform.localPosition = new(0.0709f, 0.73f); - } + case MapNames.Skeld: + if (Options.HalloweenDecorationsSkeld.GetBool()) + __instance.transform.FindChild("Helloween")?.gameObject.SetActive(true); + + if (Options.EnableBirthdayDecorationSkeld.GetBool()) + __instance.transform.FindChild("BirthdayDecorSkeld")?.gameObject.SetActive(true); + break; + case MapNames.Mira when Options.HalloweenDecorationsMira.GetBool(): + __instance.transform.FindChild("Halloween")?.gameObject.SetActive(true); + break; + case MapNames.Dleks when Options.HalloweenDecorationsDleks.GetBool(): + __instance.transform.FindChild("Helloween")?.gameObject.SetActive(true); + break; + case MapNames.Polus when Main.EnableCustomDecorations.Value: + var Dropship = GameObject.Find("Dropship/panel_fuel"); + if (Dropship != null) + { + var Decorations = UnityEngine.Object.Instantiate(Dropship, GameObject.Find("Dropship")?.transform); + Decorations.name = "Dropship_Decorations"; + Decorations.transform.DestroyChildren(); + UnityEngine.Object.Destroy(Decorations.GetComponent()); + UnityEngine.Object.Destroy(Decorations.GetComponent()); + UnityEngine.Object.Destroy(Decorations.GetComponent()); + Decorations.GetComponent().sprite = Utils.LoadSprite("TOHE.Resources.Images.Dropship-Decorations.png", 100f); + Decorations.transform.SetSiblingIndex(1); + Decorations.transform.localPosition = new(0.0709f, 0.73f); + } + break; } } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 27a3f4acd9..b95f7e3445 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1319,6 +1319,11 @@ "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", "DecontaminationTimeOnPolus": "Decontamination Time On Polus", + "EnableHalloweenDecorations": "Halloween Decorations (The Skeld/MIRA HQ/Dleks)", + "HalloweenDecorationsSkeld": "Enable On The Skeld", + "HalloweenDecorationsMira": "Enable On MIRA HQ", + "HalloweenDecorationsDleks": "Enable On dlekS ehT", + "EnableBirthdayDecorationSkeld": "Birthday Decoration On The Skeld", "ApplyDenyNameList": "Apply DenyName List", "KickPlayerFriendCodeInvalid": "Kick players with an invalid friend code", "TempBanPlayerFriendCodeInvalid": "Temp Ban players with an invalid friend code", From 9e7b60eab992ae7940f59015b8bc076a17c895c8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 3 Oct 2024 18:57:15 +0800 Subject: [PATCH 700/778] Set Random Decoration When Birthday And Halloween Is Active --- Modules/OptionHolder.cs | 3 +++ Patches/ShipStatusPatch.cs | 18 ++++++++++++++++-- Resources/Lang/en_US.json | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index f07b7543fa..22d3d29494 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -276,6 +276,7 @@ private enum RatesZeroOne public static OptionItem HalloweenDecorationsMira; public static OptionItem HalloweenDecorationsDleks; public static OptionItem EnableBirthdayDecorationSkeld; + public static OptionItem RandomBirthdayAndHalloweenDecorationSkeld; // Sabotage Settings public static OptionItem CommsCamouflage; @@ -1292,6 +1293,8 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetColor(new Color32(19, 188, 233, byte.MaxValue)); EnableBirthdayDecorationSkeld = BooleanOptionItem.Create(60518, "EnableBirthdayDecorationSkeld", false, TabGroup.ModSettings, false) .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + RandomBirthdayAndHalloweenDecorationSkeld = BooleanOptionItem.Create(60519, "RandomBirthdayAndHalloweenDecorationSkeld", false, TabGroup.ModSettings, false) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); // Sabotage TextOptionItem.Create(10000026, "MenuTitle.Sabotage", TabGroup.ModSettings) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index b222ea7e68..3d76d32fa3 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -199,10 +199,24 @@ public static void Postfix(ShipStatus __instance) switch (Utils.GetActiveMapName()) { case MapNames.Skeld: - if (Options.HalloweenDecorationsSkeld.GetBool()) + var halloweenDecorationIsActive = Options.HalloweenDecorationsSkeld.GetBool(); + var birthdayDecorationIsActive = Options.EnableBirthdayDecorationSkeld.GetBool(); + var halloweenDecorationObject = __instance.transform.FindChild("Helloween"); + var birthdayDecorationObject = __instance.transform.FindChild("BirthdayDecorSkeld"); + + if (Options.RandomBirthdayAndHalloweenDecorationSkeld.GetBool() && halloweenDecorationIsActive && birthdayDecorationIsActive) + { + var random = IRandom.Instance.Next(0, 100); + if (random < 50) + halloweenDecorationObject?.gameObject.SetActive(true); + else + birthdayDecorationObject?.gameObject.SetActive(true); + break; + } + if (halloweenDecorationIsActive) __instance.transform.FindChild("Helloween")?.gameObject.SetActive(true); - if (Options.EnableBirthdayDecorationSkeld.GetBool()) + if (birthdayDecorationIsActive) __instance.transform.FindChild("BirthdayDecorSkeld")?.gameObject.SetActive(true); break; case MapNames.Mira when Options.HalloweenDecorationsMira.GetBool(): diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b95f7e3445..1500f6b597 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1324,6 +1324,7 @@ "HalloweenDecorationsMira": "Enable On MIRA HQ", "HalloweenDecorationsDleks": "Enable On dlekS ehT", "EnableBirthdayDecorationSkeld": "Birthday Decoration On The Skeld", + "RandomBirthdayAndHalloweenDecorationSkeld": "Set Random Decoration When Birthday And Halloween Is Active On The Skeld", "ApplyDenyNameList": "Apply DenyName List", "KickPlayerFriendCodeInvalid": "Kick players with an invalid friend code", "TempBanPlayerFriendCodeInvalid": "Temp Ban players with an invalid friend code", From 01687228774514e497ea521da8d5d8670e9f0562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B5=F0=9D=92=90=F0=9D=92=8F=F0=9D=92=82?= =?UTF-8?q?=F0=9D=92=8D=F0=9D=92=96=F0=9D=92=94=F0=9F=8D=A5?= <134705775+Reborn5537@users.noreply.github.com> Date: Thu, 3 Oct 2024 23:30:04 +0800 Subject: [PATCH 701/778] Okay, I admit I missed something --- Patches/ChatCommandPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 75d5207b28..709fbbc390 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -687,6 +687,7 @@ public static bool Prefix(ChatController __instance) case "/s": case "/с": case "/сказать": + case "/说": canceled = true; if (args.Length > 1) Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); From 83feeea8c0e0ffdfd52e2460b20a22631f61348a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B5=F0=9D=92=90=F0=9D=92=8F=F0=9D=92=82?= =?UTF-8?q?=F0=9D=92=8D=F0=9D=92=96=F0=9D=92=94=F0=9F=8D=A5?= <134705775+Reborn5537@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:09:58 +0800 Subject: [PATCH 702/778] End? --- Patches/ChatCommandPatch.cs | 6386 +++++++++++++++++------------------ 1 file changed, 3191 insertions(+), 3195 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 709fbbc390..ceb7af3c12 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1,381 +1,378 @@ -using Assets.CoreScripts; -using Hazel; -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using TOHE.Modules; -using TOHE.Modules.ChatManager; -using TOHE.Roles.Core; -using TOHE.Roles.Core.AssignManager; -using TOHE.Roles.Crewmate; -using TOHE.Roles.Impostor; -using TOHE.Roles.Neutral; -using UnityEngine; -using static TOHE.Translator; - - -namespace TOHE; - -[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] -internal class ChatCommands -{ - private static readonly string modLogFiles = @"./TOHE-DATA/ModLogs.txt"; - private static readonly string modTagsFiles = @"./TOHE-DATA/Tags/MOD_TAGS"; - private static readonly string sponsorTagsFiles = @"./TOHE-DATA/Tags/SPONSOR_TAGS"; - private static readonly string vipTagsFiles = @"./TOHE-DATA/Tags/VIP_TAGS"; - - private static readonly Dictionary Pollvotes = []; - private static readonly Dictionary PollQuestions = []; - private static readonly List PollVoted = []; - private static float Polltimer = 120f; - private static string PollMSG = ""; - - public const string Csize = "85%"; // CustomRole Settings Font-Size - public const string Asize = "75%"; // All Appended Addons Font-Size - - public static List ChatHistory = []; - - public static bool Prefix(ChatController __instance) - { - if (__instance.quickChatField.visible == false && __instance.freeChatField.textArea.text == "") return false; - if (!GameStates.IsModHost && !AmongUsClient.Instance.AmHost) return true; - __instance.timeSinceLastMessage = 3f; - var text = __instance.freeChatField.textArea.text; - if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); - ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; - string[] args = text.Split(' '); - string subArgs = ""; - string subArgs2 = ""; - var canceled = false; - var cancelVal = ""; - Main.isChatCommand = true; - Logger.Info(text, "SendChat"); - if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && AmongUsClient.Instance.AmHost) // Blackmailer.ForBlackmailer.Contains(PlayerControl.LocalPlayer.PlayerId)) && PlayerControl.LocalPlayer.IsAlive()) - { - ChatManager.SendMessage(PlayerControl.LocalPlayer, text); - } - //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn" && text[..3] != "/rs") args[0] = "/r"; - if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; - - if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - Directory.CreateDirectory(modTagsFiles); - Directory.CreateDirectory(vipTagsFiles); - Directory.CreateDirectory(sponsorTagsFiles); - - if (Blackmailer.CheckBlackmaile(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()) - { - goto Canceled; - } - switch (args[0]) - { +using Assets.CoreScripts; +using Hazel; +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using TOHE.Modules; +using TOHE.Modules.ChatManager; +using TOHE.Roles.Core; +using TOHE.Roles.Core.AssignManager; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using UnityEngine; +using static TOHE.Translator; + + +namespace TOHE; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] +internal class ChatCommands +{ + private static readonly string modLogFiles = @"./TOHE-DATA/ModLogs.txt"; + private static readonly string modTagsFiles = @"./TOHE-DATA/Tags/MOD_TAGS"; + private static readonly string sponsorTagsFiles = @"./TOHE-DATA/Tags/SPONSOR_TAGS"; + private static readonly string vipTagsFiles = @"./TOHE-DATA/Tags/VIP_TAGS"; + + private static readonly Dictionary Pollvotes = []; + private static readonly Dictionary PollQuestions = []; + private static readonly List PollVoted = []; + private static float Polltimer = 120f; + private static string PollMSG = ""; + + public const string Csize = "85%"; // CustomRole Settings Font-Size + public const string Asize = "75%"; // All Appended Addons Font-Size + + public static List ChatHistory = []; + + public static bool Prefix(ChatController __instance) + { + if (__instance.quickChatField.visible == false && __instance.freeChatField.textArea.text == "") return false; + if (!GameStates.IsModHost && !AmongUsClient.Instance.AmHost) return true; + __instance.timeSinceLastMessage = 3f; + var text = __instance.freeChatField.textArea.text; + if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); + ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; + string[] args = text.Split(' '); + string subArgs = ""; + string subArgs2 = ""; + var canceled = false; + var cancelVal = ""; + Main.isChatCommand = true; + Logger.Info(text, "SendChat"); + if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && AmongUsClient.Instance.AmHost) // Blackmailer.ForBlackmailer.Contains(PlayerControl.LocalPlayer.PlayerId)) && PlayerControl.LocalPlayer.IsAlive()) + { + ChatManager.SendMessage(PlayerControl.LocalPlayer, text); + } + //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn" && text[..3] != "/rs") args[0] = "/r"; + if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; + + if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + Directory.CreateDirectory(modTagsFiles); + Directory.CreateDirectory(vipTagsFiles); + Directory.CreateDirectory(sponsorTagsFiles); + + if (Blackmailer.CheckBlackmaile(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()) + { + goto Canceled; + } + switch (args[0]) + { case "/dump": - case "/导出日志": - case "/日志": - case "/导出": - Utils.DumpLog(); - break; - case "/v": - case "/version": + case "/导出日志": + case "/日志": + case "/导出": + Utils.DumpLog(); + break; + case "/v": + case "/version": case "/versão": - case "/版本": - canceled = true; - string version_text = ""; - var player = PlayerControl.LocalPlayer; - var title = "" + GetString("DefaultSystemMessageTitle") + ""; - var name = player?.Data?.PlayerName; - try - { - foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key).ToArray()) - { - var pc = Utils.GetClientById(kvp.Key)?.Character; - version_text += $"{kvp.Key}/{(pc?.PlayerId != null ? pc.PlayerId.ToString() : "null")}:{pc?.GetRealName(clientData: true) ?? "null"}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; - } - if (version_text != "") - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, version_text); - player.SetName(name); - } - } - catch (Exception e) - { - Logger.Error(e.Message, "/version"); - version_text = "Error while getting version : " + e.Message; - if (version_text != "") - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, version_text); - player.SetName(name); - } - } - break; - - default: - Main.isChatCommand = false; - break; - } - if (AmongUsClient.Instance.AmHost) - { - Main.isChatCommand = true; - switch (args[0]) - { - case "/ans": - case "/asw": + case "/版本": + canceled = true; + string version_text = ""; + var player = PlayerControl.LocalPlayer; + var title = "" + GetString("DefaultSystemMessageTitle") + ""; + var name = player?.Data?.PlayerName; + try + { + foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key).ToArray()) + { + var pc = Utils.GetClientById(kvp.Key)?.Character; + version_text += $"{kvp.Key}/{(pc?.PlayerId != null ? pc.PlayerId.ToString() : "null")}:{pc?.GetRealName(clientData: true) ?? "null"}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; + } + if (version_text != "") + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, version_text); + player.SetName(name); + } + } + catch (Exception e) + { + Logger.Error(e.Message, "/version"); + version_text = "Error while getting version : " + e.Message; + if (version_text != "") + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, version_text); + player.SetName(name); + } + } + break; + + default: + Main.isChatCommand = false; + break; + } + if (AmongUsClient.Instance.AmHost) + { + Main.isChatCommand = true; + switch (args[0]) + { + case "/ans": + case "/asw": case "/answer": - case "/回答": - Quizmaster.AnswerByChat(PlayerControl.LocalPlayer, args); - break; - + case "/回答": + Quizmaster.AnswerByChat(PlayerControl.LocalPlayer, args); + break; + case "/qmquiz": - case "/提问": - Quizmaster.ShowQuestion(PlayerControl.LocalPlayer); - break; - - case "/win": - case "/winner": + case "/提问": + Quizmaster.ShowQuestion(PlayerControl.LocalPlayer); + break; + + case "/win": + case "/winner": case "/vencedor": case "/胜利": case "/获胜": case "/赢": - canceled = true; - if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists")); - else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList)); - break; - - case "/l": - case "/lastresult": + case "/胜利者": + case "/获胜的人": + case "/赢家": + canceled = true; + if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists")); + else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList)); + break; + + case "/l": + case "/lastresult": case "/fimdejogo": case "/上局信息": case "/信息": - case "/情况": - canceled = true; - Utils.ShowKillLog(); - Utils.ShowLastRoles(); - Utils.ShowLastResult(); - break; - - case "/gr": - case "/gameresults": + case "/情况": + canceled = true; + Utils.ShowKillLog(); + Utils.ShowLastRoles(); + Utils.ShowLastResult(); + break; + + case "/gr": + case "/gameresults": case "/resultados": case "/对局结果": case "/上局结果": - case "/结果": - canceled = true; - Utils.ShowLastResult(); - break; - - case "/kh": + case "/结果": + canceled = true; + Utils.ShowLastResult(); + break; + + case "/kh": case "/killlog": case "/击杀日志": - case "/击杀情况": - canceled = true; - Utils.ShowKillLog(); - break; - - case "/rs": - case "/sum": - case "/rolesummary": - case "/sumario": - case "/sumário": - case "/summary": + case "/击杀情况": + canceled = true; + Utils.ShowKillLog(); + break; + + case "/rs": + case "/sum": + case "/rolesummary": + case "/sumario": + case "/sumário": + case "/summary": case "/результат": case "/上局职业": case "/职业信息": - case "/对局职业": - canceled = true; - Utils.ShowLastRoles(); - break; - + case "/对局职业": + canceled = true; + Utils.ShowLastRoles(); + break; + case "/ghostinfo": case "/幽灵职业介绍": case "/鬼魂职业介绍": case "/幽灵职业": - case "/鬼魂职业": - canceled = true; - Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/apocinfo": + case "/鬼魂职业": + canceled = true; + Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/apocinfo": case "/apocalypseinfo": case "/末日中立职业介绍": case "/末日中立介绍": case "/末日类中立职业介绍": - case "/末日类中立介绍": - canceled = true; - Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); - break; - - - case "/rn": - case "/rename": - case "/renomear": + case "/末日类中立介绍": + canceled = true; + Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + + + case "/rn": + case "/rename": + case "/renomear": case "/переименовать": case "/重命名": - case "/命名为": - canceled = true; - if (args.Length < 1) break; - if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) { - Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else Main.HostRealName = args.Skip(1).Join(delimiter: " "); - Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/hn": - case "/hidename": + case "/命名为": + canceled = true; + if (args.Length < 1) break; + if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) { + Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else Main.HostRealName = args.Skip(1).Join(delimiter: " "); + Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/hn": + case "/hidename": case "/semnome": case "/隐藏名字": - case "/藏名": - canceled = true; - Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); - GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = - ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) - ? $"{Main.HideName.Value}" - : $"{Main.HideName.Value}"; - break; - - case "/level": - case "/nível": + case "/藏名": + canceled = true; + Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); + GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = + ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) + ? $"{Main.HideName.Value}" + : $"{Main.HideName.Value}"; + break; + + case "/level": + case "/nível": case "/nivel": case "/等级": - case "/等级设置为": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), PlayerControl.LocalPlayer.PlayerId); - _ = int.TryParse(subArgs, out int input); - if (input is < 1 or > 999) - { - Utils.SendMessage(GetString("Message.AllowLevelRange"), PlayerControl.LocalPlayer.PlayerId); - break; - } - var number = Convert.ToUInt32(input); - PlayerControl.LocalPlayer.RpcSetLevel(number - 1); - break; - - case "/n": - case "/now": + case "/等级设置为": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), PlayerControl.LocalPlayer.PlayerId); + _ = int.TryParse(subArgs, out int input); + if (input is < 1 or > 999) + { + Utils.SendMessage(GetString("Message.AllowLevelRange"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var number = Convert.ToUInt32(input); + PlayerControl.LocalPlayer.RpcSetLevel(number - 1); + break; + + case "/n": + case "/now": case "/atual": case "/设置": case "/系统设置": - case "/模组设置": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "r": - case "roles": - case "funções": - // case "职业": - //case "角色": - Utils.ShowActiveRoles(); - break; - case "a": - case "all": - case "tudo": - case "所有": - case "全部": - Utils.ShowAllActiveSettings(); - break; - default: - Utils.ShowActiveSettings(); - break; - } - break; - - case "/dis": - case "/disconnect": + case "/模组设置": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + case "funções": + Utils.ShowActiveRoles(); + break; + case "a": + case "all": + case "tudo": + Utils.ShowAllActiveSettings(); + break; + default: + Utils.ShowActiveSettings(); + break; + } + break; + + case "/dis": + case "/disconnect": case "/desconectar": - case "/断连": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "crew": + case "/断连": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "crew": case "tripulante": - case "船员": - GameManager.Instance.enabled = false; - Utils.NotifyGameEnding(); - GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); - break; - - case "imp": + case "船员": + GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); + GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); + break; + + case "imp": case "impostor": case "内鬼": - case "伪装者": - GameManager.Instance.enabled = false; - Utils.NotifyGameEnding(); - GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); - break; - - default: - __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); - if (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Brazilian) - { - __instance.AddChat(PlayerControl.LocalPlayer, "tripulante | impostor"); - } - cancelVal = "/dis"; - break; - } - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); - break; - - case "/r": - case "/role": - case "/р": - case "/роль": - //case "/职业": - //case "/角色": - canceled = true; - if (text.Contains("/role") || text.Contains("/роль")/* || text.Contains("/角色")*/) - subArgs = text.Remove(0, 5); - else - subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); - break; - + case "伪装者": + GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + break; + + default: + __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); + if (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Brazilian) + { + __instance.AddChat(PlayerControl.LocalPlayer, "tripulante | impostor"); + } + cancelVal = "/dis"; + break; + } + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); + break; + + case "/r": + case "/role": + case "/р": + case "/роль": + canceled = true; + if (text.Contains("/role") || text.Contains("/роль")) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); + break; + case "/up": case "/指定": - case "/成为": - canceled = true; - subArgs = text.Remove(0, 3); - if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp){ - Utils.SendMessage($"{GetString("InvalidPermissionCMD")}", PlayerControl.LocalPlayer.PlayerId); - break; - } - if (!Options.EnableUpMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); - break; - - //case "/setbasic": - // canceled = true; - // if (GameStates.IsLobby) - // { - // break; - // } - // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); - // break; - - case "/setplayers": + case "/成为": + canceled = true; + subArgs = text.Remove(0, 3); + if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp){ + Utils.SendMessage($"{GetString("InvalidPermissionCMD")}", PlayerControl.LocalPlayer.PlayerId); + break; + } + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); + break; + + //case "/setbasic": + // canceled = true; + // if (GameStates.IsLobby) + // { + // break; + // } + // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); + // break; + + case "/setplayers": case "/maxjogadores": case "/设置最大玩家数": case "/设置最大玩家数量": @@ -383,3033 +380,3032 @@ public static bool Prefix(ChatController __instance) case "/设置玩家数量": case "/玩家数": case "/玩家数量": - case "/玩家": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - Utils.SendMessage(GetString("Message.MaxPlayers") + subArgs); - var numbereer = Convert.ToByte(subArgs); - if (GameStates.IsNormalGame) - GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = numbereer; - - else if (GameStates.IsHideNSeek) - GameOptionsManager.Instance.currentHideNSeekGameOptions.MaxPlayers = numbereer; - break; - - case "/h": - case "/help": - case "/ajuda": - case "/хелп": - case "/хэлп": + case "/玩家": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + Utils.SendMessage(GetString("Message.MaxPlayers") + subArgs); + var numbereer = Convert.ToByte(subArgs); + if (GameStates.IsNormalGame) + GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = numbereer; + + else if (GameStates.IsHideNSeek) + GameOptionsManager.Instance.currentHideNSeekGameOptions.MaxPlayers = numbereer; + break; + + case "/h": + case "/help": + case "/ajuda": + case "/хелп": + case "/хэлп": case "/помощь": case "/帮助": - case "/教程": - canceled = true; - Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); - break; - - case "/icon": + case "/教程": + canceled = true; + Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); + break; + + case "/icon": case "/icons": case "/符号": - case "/标志": - { - Utils.SendMessage(GetString("Command.icons"), PlayerControl.LocalPlayer.PlayerId, GetString("IconsTitle")); - break; - } - + case "/标志": + { + Utils.SendMessage(GetString("Command.icons"), PlayerControl.LocalPlayer.PlayerId, GetString("IconsTitle")); + break; + } + case "/iconhelp": case "/符号帮助": - case "/标志帮助": - { - Utils.SendMessage(GetString("Command.icons"), title: GetString("IconsTitle")); - break; - } - - case "/kc": - case "/kcount": - case "/количество": + case "/标志帮助": + { + Utils.SendMessage(GetString("Command.icons"), title: GetString("IconsTitle")); + break; + } + + case "/kc": + case "/kcount": + case "/количество": case "/убийцы": case "/存活阵营": case "/阵营": case "/存货阵营信息": - case "/阵营信息": - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; - - var allAlivePlayers = Main.AllAlivePlayerControls; - int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); - int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); - int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); - - var sub = new StringBuilder(); - sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); - - if (Options.ShowMadmatesInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); - - if (Options.ShowApocalypseInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); - - sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); - - Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); - break; + case "/阵营信息": + if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; + + var allAlivePlayers = Main.AllAlivePlayerControls; + int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); + int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + + var sub = new StringBuilder(); + sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); + + if (Options.ShowMadmatesInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); + + Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); + break; case "/vote": case "/投票": - case "/票": - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int arg)) - break; - var plr = Utils.GetPlayerById(arg); - - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (!Options.EnableVoteCommand.GetBool()) - { - Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) - { - canceled = true; - } - - if (arg != 253) // skip - { - if (plr == null || !plr.IsAlive()) - { - Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); - break; - } - } - if (!PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (GameStates.IsMeeting) - { - PlayerControl.LocalPlayer.RpcCastVote((byte)arg); - } - break; - - case "/d": - case "/death": - case "/morto": - case "/умер": + case "/票": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + } + + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + } + if (!PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + PlayerControl.LocalPlayer.RpcCastVote((byte)arg); + } + break; + + case "/d": + case "/death": + case "/morto": + case "/умер": case "/причина": case "/死亡原因": - case "/死亡": - canceled = true; - Logger.Info($"PlayerControl.LocalPlayer.PlayerId: {PlayerControl.LocalPlayer.PlayerId}", "/death command"); - if (GameStates.IsLobby) - { - Logger.Info("IsLobby", "/death command"); - Utils.SendMessage(text: GetString("Message.CanNotUseInLobby"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (PlayerControl.LocalPlayer.IsAlive()) - { - Logger.Info("IsAlive", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.HeyPlayer") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - Logger.Info("DeathReason.Vote", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) - { - Logger.Info("DeathReason.Shrouded", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) - { - Logger.Info("DeathReason.FollowingSuicide", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - Logger.Info("GetRealKiller()", "/death command"); - var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); - string killerName = killer == null ? "N/A" : killer.GetRealName(); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); - - break; - } - - - case "/m": - case "/myrole": - case "/minhafunção": - case "/м": + case "/死亡": + canceled = true; + Logger.Info($"PlayerControl.LocalPlayer.PlayerId: {PlayerControl.LocalPlayer.PlayerId}", "/death command"); + if (GameStates.IsLobby) + { + Logger.Info("IsLobby", "/death command"); + Utils.SendMessage(text: GetString("Message.CanNotUseInLobby"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (PlayerControl.LocalPlayer.IsAlive()) + { + Logger.Info("IsAlive", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.HeyPlayer") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote) + { + Logger.Info("DeathReason.Vote", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) + { + Logger.Info("DeathReason.Shrouded", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) + { + Logger.Info("DeathReason.FollowingSuicide", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + Logger.Info("GetRealKiller()", "/death command"); + var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); + string killerName = killer == null ? "N/A" : killer.GetRealName(); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); + + break; + } + + + case "/m": + case "/myrole": + case "/minhafunção": + case "/м": case "/мояроль": case "/身份": case "/我": case "/我的身份": - case "/我的职业": - canceled = true; - var role = PlayerControl.LocalPlayer.GetCustomRole(); - if (GameStates.IsInGame) - { - var lp = PlayerControl.LocalPlayer; - var Des = lp.GetRoleInfo(true); - var title = $"" + role.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); - var SubTitle = $"" + GetString("YourAddon") + "\n"; - - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - - foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.ToArray()) - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); - - if (Sub.ToString() != string.Empty) - { - var ACleared = Sub.ToString().Remove(0, 2); - ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; - Sub.Clear().Append(ACleared); - } - - Utils.SendMessage(Des, lp.PlayerId, title, noReplay: true); - Utils.SendMessage("", lp.PlayerId, Conf.ToString(), noReplay: true); - if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), lp.PlayerId, SubTitle, noReplay: true); - } - else - Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - + case "/我的职业": + canceled = true; + var role = PlayerControl.LocalPlayer.GetCustomRole(); + if (GameStates.IsInGame) + { + var lp = PlayerControl.LocalPlayer; + var Des = lp.GetRoleInfo(true); + var title = $"" + role.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + var Sub = new StringBuilder(); + var rlHex = Utils.GetRoleColorCode(role); + var SubTitle = $"" + GetString("YourAddon") + "\n"; + + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + + foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.ToArray()) + Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + + if (Sub.ToString() != string.Empty) + { + var ACleared = Sub.ToString().Remove(0, 2); + ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; + Sub.Clear().Append(ACleared); + } + + Utils.SendMessage(Des, lp.PlayerId, title, noReplay: true); + Utils.SendMessage("", lp.PlayerId, Conf.ToString(), noReplay: true); + if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), lp.PlayerId, SubTitle, noReplay: true); + } + else + Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + case "/me": case "/我的权限": - case "/权限": - canceled = true; - subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); - string Devbox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; - string UpBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; - string ColorBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; - - if (string.IsNullOrEmpty(subArgs)) - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); - } - else - { - if (byte.TryParse(subArgs, out byte meid)) - { - if (meid != PlayerControl.LocalPlayer.PlayerId) - { - var targetplayer = Utils.GetPlayerById(meid); - if (targetplayer != null && targetplayer.GetClient() != null) - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}"); - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); - } - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); - } - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); - } - } - break; - - case "/t": - case "/template": - case "/шаблон": + case "/权限": + canceled = true; + subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); + string Devbox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; + string UpBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; + string ColorBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; + + if (string.IsNullOrEmpty(subArgs)) + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); + } + else + { + if (byte.TryParse(subArgs, out byte meid)) + { + if (meid != PlayerControl.LocalPlayer.PlayerId) + { + var targetplayer = Utils.GetPlayerById(meid); + if (targetplayer != null && targetplayer.GetClient() != null) + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}"); + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); + } + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); + } + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); + } + } + break; + + case "/t": + case "/template": + case "/шаблон": case "/пример": case "/模板": - case "/模板信息": - canceled = true; - if (args.Length > 1) TemplateManager.SendTemplate(args[1]); - else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", PlayerControl.LocalPlayer.PlayerId); - break; - - case "/mw": + case "/模板信息": + canceled = true; + if (args.Length > 1) TemplateManager.SendTemplate(args[1]); + else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", PlayerControl.LocalPlayer.PlayerId); + break; + + case "/mw": case "/messagewait": case "/消息等待时间": - case "/消息冷却": - canceled = true; - if (args.Length > 1 && int.TryParse(args[1], out int sec)) - { - Main.MessageWait.Value = sec; - Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); - } - else Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); - break; - + case "/消息冷却": + canceled = true; + if (args.Length > 1 && int.TryParse(args[1], out int sec)) + { + Main.MessageWait.Value = sec; + Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); + } + else Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); + break; + case "/tpout": case "/传送出": - case "/传出": - canceled = true; - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcTeleport(new Vector2(0.1f, 3.8f)); - break; + case "/传出": + canceled = true; + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcTeleport(new Vector2(0.1f, 3.8f)); + break; case "/tpin": case "/传进": - case "/传送进": - canceled = true; - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcTeleport(new Vector2(-0.2f, 1.3f)); - break; - - case "/say": - case "/s": - case "/с": + case "/传送进": + canceled = true; + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcTeleport(new Vector2(-0.2f, 1.3f)); + break; + + case "/say": + case "/s": + case "/с": case "/сказать": - case "/说": - canceled = true; - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); - break; - + case "/说": + canceled = true; + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); + break; + case "/mid": case "/玩家列表": case "/玩家信息": - case "/玩家编号列表": - canceled = true; - string msgText1 = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText1, PlayerControl.LocalPlayer.PlayerId); - break; - - case "/ban": - case "/banir": - case "/бан": + case "/玩家编号列表": + canceled = true; + string msgText1 = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText1, PlayerControl.LocalPlayer.PlayerId); + break; + + case "/ban": + case "/banir": + case "/бан": case "/забанить": - case "/封禁": - canceled = true; - - string banReason = ""; - if (args.Length < 3) - { - Utils.SendMessage(GetString("BanCommandNoReason"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - subArgs = args[1]; - banReason = string.Join(" ", args.Skip(2)); - } - //subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (banPlayerId == 0) - { - Utils.SendMessage(GetString("BanCommandBanHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var bannedPlayer = Utils.GetPlayerById(banPlayerId); - if (bannedPlayer == null) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // Ban the specified player - AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); - string bannedPlayerName = bannedPlayer.GetRealName(); - string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{PlayerControl.LocalPlayer.name} \nReason: {banReason}\n"; - if (GameStates.IsInGame) - { - textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend1); - //string moderatorName = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; - //moderatorName = moderatorName.Substring(startIndex); - //string extractedString = - string moderatorFriendCode = PlayerControl.LocalPlayer.FriendCode.ToString(); - string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); - string modLogname = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n1) ? n1 : ""; - string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; - string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{banlogname} Reason: {banReason}"; - File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); - break; - - case "/warn": - case "/aviso": - case "/варн": - case "/пред": + case "/封禁": + canceled = true; + + string banReason = ""; + if (args.Length < 3) + { + Utils.SendMessage(GetString("BanCommandNoReason"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + subArgs = args[1]; + banReason = string.Join(" ", args.Skip(2)); + } + //subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (banPlayerId == 0) + { + Utils.SendMessage(GetString("BanCommandBanHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var bannedPlayer = Utils.GetPlayerById(banPlayerId); + if (bannedPlayer == null) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Ban the specified player + AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); + string bannedPlayerName = bannedPlayer.GetRealName(); + string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{PlayerControl.LocalPlayer.name} \nReason: {banReason}\n"; + if (GameStates.IsInGame) + { + textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend1); + //string moderatorName = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; + //moderatorName = moderatorName.Substring(startIndex); + //string extractedString = + string moderatorFriendCode = PlayerControl.LocalPlayer.FriendCode.ToString(); + string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); + string modLogname = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n1) ? n1 : ""; + string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; + string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{banlogname} Reason: {banReason}"; + File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); + break; + + case "/warn": + case "/aviso": + case "/варн": + case "/пред": case "/предупредить": case "/警告": - case "/提醒": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (warnPlayerId == 0) - { - Utils.SendMessage(GetString("WarnCommandWarnHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var warnedPlayer = Utils.GetPlayerById(warnPlayerId); - if (warnedPlayer == null) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // warn the specified player - string textToSend2 = ""; - string warnReason = "Reason : Not specified\n"; - string warnedPlayerName = warnedPlayer.GetRealName(); - //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; - if (args.Length > 2) - { - warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - } - else - { - Utils.SendMessage(GetString("WarnExample"), PlayerControl.LocalPlayer.PlayerId); - } - textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{PlayerControl.LocalPlayer.name}"; - Utils.SendMessage(textToSend2); - //string moderatorName1 = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; - //moderatorName1 = moderatorName1.Substring(startIndex1); - string modLogname1 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n2) ? n2 : ""; - string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; - - string moderatorFriendCode1 = PlayerControl.LocalPlayer.FriendCode.ToString(); - string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); - string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); - string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; - File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); - - break; - - case "/kick": - case "/expulsar": - case "/кик": - case "/кикнуть": + case "/提醒": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (warnPlayerId == 0) + { + Utils.SendMessage(GetString("WarnCommandWarnHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var warnedPlayer = Utils.GetPlayerById(warnPlayerId); + if (warnedPlayer == null) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // warn the specified player + string textToSend2 = ""; + string warnReason = "Reason : Not specified\n"; + string warnedPlayerName = warnedPlayer.GetRealName(); + //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; + if (args.Length > 2) + { + warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + } + else + { + Utils.SendMessage(GetString("WarnExample"), PlayerControl.LocalPlayer.PlayerId); + } + textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{PlayerControl.LocalPlayer.name}"; + Utils.SendMessage(textToSend2); + //string moderatorName1 = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; + //moderatorName1 = moderatorName1.Substring(startIndex1); + string modLogname1 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n2) ? n2 : ""; + string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; + + string moderatorFriendCode1 = PlayerControl.LocalPlayer.FriendCode.ToString(); + string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); + string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); + string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; + File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); + + break; + + case "/kick": + case "/expulsar": + case "/кик": + case "/кикнуть": case "/выгнать": case "/踢出": - case "/踢": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (kickPlayerId == 0) - { - Utils.SendMessage(GetString("KickCommandKickHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var kickedPlayer = Utils.GetPlayerById(kickPlayerId); - if (kickedPlayer == null) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); - string kickedPlayerName = kickedPlayer.GetRealName(); - string kickReason = "Reason : Not specified\n"; - if (args.Length > 2) - kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - else - { - Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", PlayerControl.LocalPlayer.PlayerId); - } - string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {PlayerControl.LocalPlayer.name} \n {kickReason}"; - - if (GameStates.IsInGame) - { - textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend); - //string moderatorName2 = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; - //moderatorName2 = moderatorName2.Substring(startIndex2); - - string modLogname2 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n3) ? n3 : ""; - string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; - - string moderatorFriendCode2 = PlayerControl.LocalPlayer.FriendCode.ToString(); - string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); - string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); - string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; - File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); - - break; - - case "/tagcolor": + case "/踢": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (kickPlayerId == 0) + { + Utils.SendMessage(GetString("KickCommandKickHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var kickedPlayer = Utils.GetPlayerById(kickPlayerId); + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); + string kickedPlayerName = kickedPlayer.GetRealName(); + string kickReason = "Reason : Not specified\n"; + if (args.Length > 2) + kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + else + { + Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", PlayerControl.LocalPlayer.PlayerId); + } + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {PlayerControl.LocalPlayer.name} \n {kickReason}"; + + if (GameStates.IsInGame) + { + textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + //string moderatorName2 = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; + //moderatorName2 = moderatorName2.Substring(startIndex2); + + string modLogname2 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n3) ? n3 : ""; + string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; + + string moderatorFriendCode2 = PlayerControl.LocalPlayer.FriendCode.ToString(); + string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); + string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); + string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; + File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); + + break; + + case "/tagcolor": case "/tagcolour": case "/标签颜色": - case "/附加名称颜色": - canceled = true; - string name = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n) ? n : ""; - if (name == "") break; - if (!name.Contains('\r') && PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag()) - { - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "tagcolor"); - Utils.SendMessage(GetString("TagColorInvalidHexCode"), PlayerControl.LocalPlayer.PlayerId); - break; - } - string tagColorFilePath = $"{sponsorTagsFiles}/{PlayerControl.LocalPlayer.FriendCode}.txt"; - if (!File.Exists(tagColorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); - File.Create(tagColorFilePath).Close(); - } - File.WriteAllText(tagColorFilePath, $"{subArgs}"); - } - break; - - case "/exe": - case "/уничтожить": - case "/повесить": - case "/казнить": - case "/казнь": + case "/附加名称颜色": + canceled = true; + string name = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n) ? n : ""; + if (name == "") break; + if (!name.Contains('\r') && PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag()) + { + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "tagcolor"); + Utils.SendMessage(GetString("TagColorInvalidHexCode"), PlayerControl.LocalPlayer.PlayerId); + break; + } + string tagColorFilePath = $"{sponsorTagsFiles}/{PlayerControl.LocalPlayer.FriendCode}.txt"; + if (!File.Exists(tagColorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); + File.Create(tagColorFilePath).Close(); + } + File.WriteAllText(tagColorFilePath, $"{subArgs}"); + } + break; + + case "/exe": + case "/уничтожить": + case "/повесить": + case "/казнить": + case "/казнь": case "/мут": case "/驱逐": - case "/驱赶": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (args.Length < 2 || !int.TryParse(args[1], out int id)) break; - var player = Utils.GetPlayerById(id); - if (player != null) - { - player.Data.IsDead = true; - player.SetDeathReason(PlayerState.DeathReason.etc); - player.SetRealKiller(PlayerControl.LocalPlayer); - Main.PlayerStates[player.PlayerId].SetDead(); - player.RpcExileV2(); - MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); - - if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); - } - break; - - case "/kill": - case "/matar": + case "/驱赶": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (args.Length < 2 || !int.TryParse(args[1], out int id)) break; + var player = Utils.GetPlayerById(id); + if (player != null) + { + player.Data.IsDead = true; + player.SetDeathReason(PlayerState.DeathReason.etc); + player.SetRealKiller(PlayerControl.LocalPlayer); + Main.PlayerStates[player.PlayerId].SetDead(); + player.RpcExileV2(); + MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); + + if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + } + break; + + case "/kill": + case "/matar": case "/убить": case "/击杀": - case "/杀死": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (args.Length < 2 || !int.TryParse(args[1], out int id2)) break; - var target = Utils.GetPlayerById(id2); - if (target != null) - { - target.RpcMurderPlayer(target); - if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); - - _ = new LateTask(() => - { - Utils.NotifyRoles(NoCache: true); - - }, 0.2f, "Update NotifyRoles players after /kill"); - } - break; - - case "/colour": - case "/color": - case "/cor": + case "/杀死": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (args.Length < 2 || !int.TryParse(args[1], out int id2)) break; + var target = Utils.GetPlayerById(id2); + if (target != null) + { + target.RpcMurderPlayer(target); + if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); + + _ = new LateTask(() => + { + Utils.NotifyRoles(NoCache: true); + + }, 0.2f, "Update NotifyRoles players after /kill"); + } + break; + + case "/colour": + case "/color": + case "/cor": case "/цвет": case "/颜色": case "/更改颜色": case "/修改颜色": - case "/换颜色": - canceled = true; - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - var color = Utils.MsgToColor(subArgs, true); - if (color == byte.MaxValue) - { - Utils.SendMessage(GetString("IllegalColor"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcSetColor(color); - Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/quit": - case "/qt": + case "/换颜色": + canceled = true; + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + var color = Utils.MsgToColor(subArgs, true); + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/quit": + case "/qt": case "/sair": case "/退出": - case "/退": - canceled = true; - Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); - break; - + case "/退": + canceled = true; + Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); + break; + case "/xf": case "/修复": - case "/修": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - foreach (var pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) continue; - - pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); - } - ChatUpdatePatch.DoBlockChat = false; - //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); - Utils.SendMessage(GetString("Message.TryFixName"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/id": + case "/修": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) continue; + + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/id": case "/айди": case "/编号": - case "/玩家编号": - canceled = true; - string msgText = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText, PlayerControl.LocalPlayer.PlayerId); - break; - - /* - case "/qq": - canceled = true; - if (Main.newLobby) Cloud.ShareLobby(true); - else Utils.SendMessage("很抱歉,每个房间车队姬只会发一次", PlayerControl.LocalPlayer.PlayerId); - break; - */ - + case "/玩家编号": + canceled = true; + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText, PlayerControl.LocalPlayer.PlayerId); + break; + + /* + case "/qq": + canceled = true; + if (Main.newLobby) Cloud.ShareLobby(true); + else Utils.SendMessage("很抱歉,每个房间车队姬只会发一次", PlayerControl.LocalPlayer.PlayerId); + break; + */ + case "/setrole": case "/设置的职业": - case "/指定的职业": - canceled = true; - subArgs = text.Remove(0, 8); - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); - break; - - case "/changerole": + case "/指定的职业": + canceled = true; + subArgs = text.Remove(0, 8); + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); + break; + + case "/changerole": case "/mudarfunção": case "/改变职业": - case "/修改职业": - canceled = true; - if (GameStates.IsHideNSeek) break; - if (!(DebugModeManager.AmDebugger && GameStates.IsInGame)) break; - if (GameStates.IsOnlineGame && !PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) break; - subArgs = text.Remove(0, 11); - var setRole = FixRoleNameInput(subArgs).ToLower().Trim().Replace(" ", string.Empty); - Logger.Info(setRole, "changerole Input"); - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); - //Logger.Info(roleName, "2"); - if (setRole == roleName) - { - PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); - PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes(), true); - PlayerControl.LocalPlayer.RpcSetCustomRole(rl); - PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); - Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); - Utils.NotifyRoles(NoCache: true); - Utils.MarkEveryoneDirtySettings(); - break; - } - } - break; - - case "/end": - case "/encerrar": + case "/修改职业": + canceled = true; + if (GameStates.IsHideNSeek) break; + if (!(DebugModeManager.AmDebugger && GameStates.IsInGame)) break; + if (GameStates.IsOnlineGame && !PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) break; + subArgs = text.Remove(0, 11); + var setRole = FixRoleNameInput(subArgs).ToLower().Trim().Replace(" ", string.Empty); + Logger.Info(setRole, "changerole Input"); + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + //Logger.Info(roleName, "2"); + if (setRole == roleName) + { + PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); + PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes(), true); + PlayerControl.LocalPlayer.RpcSetCustomRole(rl); + PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); + Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); + Utils.NotifyRoles(NoCache: true); + Utils.MarkEveryoneDirtySettings(); + break; + } + } + break; + + case "/end": + case "/encerrar": case "/завершить": case "/结束": - case "/结束游戏": - canceled = true; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - break; + case "/结束游戏": + canceled = true; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + break; case "/cosid": case "/装扮编号": - case "/衣服编号": - canceled = true; - var of = PlayerControl.LocalPlayer.Data.DefaultOutfit; - Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); - Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); - Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); - Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); - Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); - Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); - break; - - case "/mt": + case "/衣服编号": + canceled = true; + var of = PlayerControl.LocalPlayer.Data.DefaultOutfit; + Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); + Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); + Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); + Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); + Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); + Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); + break; + + case "/mt": case "/hy": case "/强制过会议": case "/强制跳过会议": case "/过会议": case "/结束会议": case "/强制结束会议": - case "/跳过会议": - canceled = true; - if (GameStates.IsMeeting) - { - MeetingHud.Instance.RpcClose(); - } - else - { - PlayerControl.LocalPlayer.NoCheckStartMeeting(null, force: true); - } - break; - + case "/跳过会议": + canceled = true; + if (GameStates.IsMeeting) + { + MeetingHud.Instance.RpcClose(); + } + else + { + PlayerControl.LocalPlayer.NoCheckStartMeeting(null, force: true); + } + break; + case "/cs": case "/播放声音": - case "/播放音效": - canceled = true; - subArgs = text.Remove(0, 3); - PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); - break; - + case "/播放音效": + canceled = true; + subArgs = text.Remove(0, 3); + PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); + break; + case "/sd": case "/播放音效给": - case "/播放声音给": - canceled = true; - subArgs = text.Remove(0, 3); - if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) break; - RPC.PlaySoundRPC(PlayerControl.LocalPlayer.PlayerId, (Sounds)sound1); - break; - + case "/播放声音给": + canceled = true; + subArgs = text.Remove(0, 3); + if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) break; + RPC.PlaySoundRPC(PlayerControl.LocalPlayer.PlayerId, (Sounds)sound1); + break; + case "/poll": case "/发起投票": - case "/执行投票": - canceled = true; - - - if (args.Length == 2 && args[1] == GetString("Replay") && Pollvotes.Any() && PollMSG != string.Empty) - { - Utils.SendMessage(PollMSG); - break; - } - - PollMSG = string.Empty; - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - Polltimer = 120f; - - static System.Collections.IEnumerator StartPollCountdown() - { - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - bool playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); - - - while (playervoted && Polltimer > 0f) - { - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); - Polltimer -= Time.deltaTime; - yield return null; - } - - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - - Logger.Info($"FINNISHED!! playervote?: {!playervoted} polltime?: {Polltimer <= 0}", "/poll - StartPollCountdown"); - - DetermineResults(); - } - - static void DetermineResults() - { - int basenum = Pollvotes.Values.Max(); - var winners = Pollvotes.Where(x => x.Value == basenum); - - string msg = ""; - - Color32 clr = new(47, 234, 45, 255); //Main.PlayerColors.First(x => x.Key == PlayerControl.LocalPlayer.PlayerId).Value; - var tytul = Utils.ColorString(clr, GetString("PollResultTitle")); - - if (winners.Count() == 1) - { - var losers = Pollvotes.Where(x => x.Key != winners.First().Key); - msg = string.Format(GetString("Poll.Result"), $"{winners.First().Key}{PollQuestions[winners.First().Key]}", winners.First().Value); - - for (int i = 0; i < losers.Count(); i++) - { - msg += $"\n{losers.ElementAt(i).Key} / {losers.ElementAt(i).Value} {PollQuestions[losers.ElementAt(i).Key]}"; - - } - msg += ""; - - - Utils.SendMessage(msg, title: tytul); - } - else - { - var tienum = Pollvotes.Values.Max(); - var tied = Pollvotes.Where(x => x.Value == tienum); - - for (int i = 0; i < (tied.Count() - 1); i++) - { - msg += "\n" + tied.ElementAt(i).Key + PollQuestions[tied.ElementAt(i).Key] + " & "; - } - msg += "\n" + tied.Last().Key + PollQuestions[tied.Last().Key]; - - Utils.SendMessage(string.Format(GetString("Poll.Tied"), msg, tienum), title: tytul); - } - - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - } - - - if (Main.AllPlayerControls.Length < 3) - { - Utils.SendMessage(GetString("Poll.MissingPlayers"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Poll.OnlyInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (args.SkipWhile(x => !x.Contains('?')).ToArray().Length < 3 || !args.Any(x => x.Contains('?'))) - { - Utils.SendMessage(GetString("PollUsage"), PlayerControl.LocalPlayer.PlayerId); - break; - } - var resultat = args.TakeWhile(x => !x.Contains('?')).Concat(args.SkipWhile(x => !x.Contains('?')).Take(1)); - - string tytul = string.Join(" ", resultat.Skip(1)); - bool Longtitle = tytul.Length > 30; - tytul = Utils.ColorString(Palette.PlayerColors[PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId], tytul); - var altTitle = Utils.ColorString(new Color32(151, 198, 230, 255), GetString("PollTitle")); - - var ClearTIT = args.ToList(); - ClearTIT.RemoveRange(0, resultat.ToArray().Length); - - var Questions = ClearTIT.ToArray(); - string msg = ""; - - - if (Longtitle) msg += "" + tytul + "\n\n"; - for (int i = 0; i < Math.Clamp(Questions.Length, 2, 5); i++) - { - msg += Utils.ColorString(RndCLR(), $"{char.ToUpper((char)(i + 65))}) {Questions[i]}\n"); - Pollvotes[char.ToUpper((char)(i + 65))] = 0; - PollQuestions[char.ToUpper((char)(i + 65))] = $"〖 {Questions[i]} 〗"; - } - msg += $"\n{GetString("Poll.Begin")}"; - msg += $"\n{GetString("Poll.TimeInfo")}"; - PollMSG = !Longtitle ? "" + tytul + "\n\n" + msg : msg; - - Logger.Info($"Poll message: {msg}", "MEssapoll"); - - Utils.SendMessage(msg, title: !Longtitle ? tytul: altTitle); - - Main.Instance.StartCoroutine(StartPollCountdown()); - - - static Color32 RndCLR() - { - byte r, g, b; - - r = (byte)IRandom.Instance.Next(45, 185); - g = (byte)IRandom.Instance.Next(45, 185); - b = (byte)IRandom.Instance.Next(45, 185); - - return new Color32(r, g, b, 255); - } - - break; - + case "/执行投票": + canceled = true; + + + if (args.Length == 2 && args[1] == GetString("Replay") && Pollvotes.Any() && PollMSG != string.Empty) + { + Utils.SendMessage(PollMSG); + break; + } + + PollMSG = string.Empty; + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + Polltimer = 120f; + + static System.Collections.IEnumerator StartPollCountdown() + { + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + bool playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); + + + while (playervoted && Polltimer > 0f) + { + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); + Polltimer -= Time.deltaTime; + yield return null; + } + + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + + Logger.Info($"FINNISHED!! playervote?: {!playervoted} polltime?: {Polltimer <= 0}", "/poll - StartPollCountdown"); + + DetermineResults(); + } + + static void DetermineResults() + { + int basenum = Pollvotes.Values.Max(); + var winners = Pollvotes.Where(x => x.Value == basenum); + + string msg = ""; + + Color32 clr = new(47, 234, 45, 255); //Main.PlayerColors.First(x => x.Key == PlayerControl.LocalPlayer.PlayerId).Value; + var tytul = Utils.ColorString(clr, GetString("PollResultTitle")); + + if (winners.Count() == 1) + { + var losers = Pollvotes.Where(x => x.Key != winners.First().Key); + msg = string.Format(GetString("Poll.Result"), $"{winners.First().Key}{PollQuestions[winners.First().Key]}", winners.First().Value); + + for (int i = 0; i < losers.Count(); i++) + { + msg += $"\n{losers.ElementAt(i).Key} / {losers.ElementAt(i).Value} {PollQuestions[losers.ElementAt(i).Key]}"; + + } + msg += ""; + + + Utils.SendMessage(msg, title: tytul); + } + else + { + var tienum = Pollvotes.Values.Max(); + var tied = Pollvotes.Where(x => x.Value == tienum); + + for (int i = 0; i < (tied.Count() - 1); i++) + { + msg += "\n" + tied.ElementAt(i).Key + PollQuestions[tied.ElementAt(i).Key] + " & "; + } + msg += "\n" + tied.Last().Key + PollQuestions[tied.Last().Key]; + + Utils.SendMessage(string.Format(GetString("Poll.Tied"), msg, tienum), title: tytul); + } + + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + } + + + if (Main.AllPlayerControls.Length < 3) + { + Utils.SendMessage(GetString("Poll.MissingPlayers"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Poll.OnlyInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (args.SkipWhile(x => !x.Contains('?')).ToArray().Length < 3 || !args.Any(x => x.Contains('?'))) + { + Utils.SendMessage(GetString("PollUsage"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var resultat = args.TakeWhile(x => !x.Contains('?')).Concat(args.SkipWhile(x => !x.Contains('?')).Take(1)); + + string tytul = string.Join(" ", resultat.Skip(1)); + bool Longtitle = tytul.Length > 30; + tytul = Utils.ColorString(Palette.PlayerColors[PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId], tytul); + var altTitle = Utils.ColorString(new Color32(151, 198, 230, 255), GetString("PollTitle")); + + var ClearTIT = args.ToList(); + ClearTIT.RemoveRange(0, resultat.ToArray().Length); + + var Questions = ClearTIT.ToArray(); + string msg = ""; + + + if (Longtitle) msg += "" + tytul + "\n\n"; + for (int i = 0; i < Math.Clamp(Questions.Length, 2, 5); i++) + { + msg += Utils.ColorString(RndCLR(), $"{char.ToUpper((char)(i + 65))}) {Questions[i]}\n"); + Pollvotes[char.ToUpper((char)(i + 65))] = 0; + PollQuestions[char.ToUpper((char)(i + 65))] = $"〖 {Questions[i]} 〗"; + } + msg += $"\n{GetString("Poll.Begin")}"; + msg += $"\n{GetString("Poll.TimeInfo")}"; + PollMSG = !Longtitle ? "" + tytul + "\n\n" + msg : msg; + + Logger.Info($"Poll message: {msg}", "MEssapoll"); + + Utils.SendMessage(msg, title: !Longtitle ? tytul: altTitle); + + Main.Instance.StartCoroutine(StartPollCountdown()); + + + static Color32 RndCLR() + { + byte r, g, b; + + r = (byte)IRandom.Instance.Next(45, 185); + g = (byte)IRandom.Instance.Next(45, 185); + b = (byte)IRandom.Instance.Next(45, 185); + + return new Color32(r, g, b, 255); + } + + break; + case "/rps": - case "/剪刀石头布": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - subArgs = args.Length != 2 ? "" : args[1]; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (playerChoice < 0 || playerChoice > 2) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(0, 3); - var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; - if (botChoice == playerChoice) - { - Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - else if ((botChoice == 0 && playerChoice == 2) || - (botChoice == 1 && playerChoice == 0) || - (botChoice == 2 && playerChoice == 1)) - { - Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - else - { - Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - break; - } + case "/剪刀石头布": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + subArgs = args.Length != 2 ? "" : args[1]; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (playerChoice < 0 || playerChoice > 2) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(0, 3); + var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; + if (botChoice == playerChoice) + { + Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + else if ((botChoice == 0 && playerChoice == 2) || + (botChoice == 1 && playerChoice == 0) || + (botChoice == 2 && playerChoice == 1)) + { + Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + else + { + Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + break; + } case "/coinflip": - case "/抛硬币": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("CoinFlipCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(1, 101); - var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); - Utils.SendMessage(string.Format(GetString("CoinFlipResult"),coinSide), PlayerControl.LocalPlayer.PlayerId); - break; - } + case "/抛硬币": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CoinFlipCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(1, 101); + var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); + Utils.SendMessage(string.Format(GetString("CoinFlipResult"),coinSide), PlayerControl.LocalPlayer.PlayerId); + break; + } case "/gno": - case "/猜数字": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo < 0 || guessedNo > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - int targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - } - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]--; - if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; - //targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; - break; - } - - } + case "/猜数字": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo < 0 || guessedNo > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + int targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + } + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]--; + if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] == 0 && guessedNo != targetNumber) + { + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; + //targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo < targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo > targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; + break; + } + + } case "/rand": case "/XY数字": case "/范围游戏": case "/猜范围": - case "/范围": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - subArgs = args.Length != 3 ? "" : args[1]; - subArgs2 = args.Length != 3 ? "" : args[2]; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) - { - Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botResult = rand.Next(playerChoice1, playerChoice2 + 1); - Utils.SendMessage(string.Format(GetString("RandResult"), botResult), PlayerControl.LocalPlayer.PlayerId); - break; - } - + case "/范围": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + subArgs = args.Length != 3 ? "" : args[1]; + subArgs2 = args.Length != 3 ? "" : args[2]; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) + { + Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botResult = rand.Next(playerChoice1, playerChoice2 + 1); + Utils.SendMessage(string.Format(GetString("RandResult"), botResult), PlayerControl.LocalPlayer.PlayerId); + break; + } + case "/8ball": case "/8号球": - case "/幸运球": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - var rando = IRandom.Instance; - int result = rando.Next(0, 16); - string str = ""; - switch (result) - { - case 0: - str = GetString("8BallYes"); - break; - case 1: - str = GetString("8BallNo"); - break; - case 2: - str = GetString("8BallMaybe"); - break; - case 3: - str = GetString("8BallTryAgainLater"); - break; - case 4: - str = GetString("8BallCertain"); - break; - case 5: - str = GetString("8BallNotLikely"); - break; - case 6: - str = GetString("8BallLikely"); - break; - case 7: - str = GetString("8BallDontCount"); - break; - case 8: - str = GetString("8BallStop"); - break; - case 9: - str = GetString("8BallPossibly"); - break; - case 10: - str = GetString("8BallProbably"); - break; - case 11: - str = GetString("8BallProbablyNot"); - break; - case 12: - str = GetString("8BallBetterNotTell"); - break; - case 13: - str = GetString("8BallCantPredict"); - break; - case 14: - str = GetString("8BallWithoutDoubt"); - break; - case 15: - str = GetString("8BallWithDoubt"); - break; - } - Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); - break; - - default: - Main.isChatCommand = false; - break; - } - } - goto Skip; - Canceled: - Main.isChatCommand = false; - canceled = true; - Skip: - if (canceled) - { - Logger.Info("Command Canceled", "ChatCommand"); - __instance.freeChatField.textArea.Clear(); - __instance.freeChatField.textArea.SetText(cancelVal); - - __instance.quickChatMenu.Clear(); - __instance.quickChatField.Clear(); - } - return !canceled; - } - - public static string FixRoleNameInput(string text) - { - text = text.Replace("着", "者").Trim().ToLower(); - return text switch - { - // Because of partial translation conflicts (zh-cn and zh-tw) - // Need to wait for follow-up finishing - - /* - // GM - "GM(遊戲大師)" or "管理员" or "管理" or "gm" or "GM" => GetString("GM"), - - // 原版职业 - "船員" or "船员" or "白板" or "天选之子" => GetString("CrewmateTOHE"), - "工程師" or "工程师" => GetString("EngineerTOHE"), - "科學家" or "科学家" => GetString("ScientistTOHE"), - "守護天使" or "守护天使" => GetString("GuardianAngelTOHE"), - "偽裝者" or "内鬼" => GetString("ImpostorTOHE"), - "變形者" or "变形者" => GetString("ShapeshifterTOHE"), - - // 隱藏職業 and 隐藏职业 - "陽光開朗大男孩" or "阳光开朗大男孩" => GetString("Sunnyboy"), - "吟遊詩人" or "吟游诗人" => GetString("Bard"), - "核爆者" or "核武器" => GetString("Nuker"), - - // 偽裝者陣營職業 and 内鬼阵营职业 - "賞金獵人" or "赏金猎人" or "赏金" => GetString("BountyHunter"), - "煙火工匠" or "烟花商人" or "烟花爆破者" or "烟花" => GetString("Fireworker"), - "嗜血殺手" or "嗜血杀手" or "嗜血" => GetString("Mercenary"), - "百变怪" or "千面鬼" or "千面" => GetString("ShapeMaster"), - "吸血鬼" or "吸血" => GetString("Vampire"), - "吸血鬼之王" or "吸血鬼女王" => GetString("Vampiress"), - "術士" or "术士" => GetString("Warlock"), - "刺客" or "忍者" => GetString("Ninja"), - "僵屍" or "僵尸" or"殭屍" or "丧尸" => GetString("Zombie"), - "駭客" or "骇客" or "黑客" => GetString("Anonymous"), - "礦工" or "矿工" => GetString("Miner"), - "殺人機器" or "杀戮机器" or "杀戮" or "机器" or "杀戮兵器" => GetString("KillingMachine"), - "通緝犯" or "逃逸者" or "逃逸" => GetString("Escapist"), - "女巫" => GetString("Witch"), - "傀儡師" or "傀儡师" or "傀儡" => GetString("Puppeteer"), - "主謀" or "策划者" => GetString("Mastermind"), - "時間竊賊" or "蚀时者" or "蚀时" or "偷时" => GetString("TimeThief"), - "狙擊手" or "狙击手" or "狙击" => GetString("Sniper"), - "送葬者" or "暗杀者" => GetString("Undertaker"), - "裂縫製造者" or "裂缝制造者" => GetString("RiftMaker"), - "邪惡的追踪者" or "邪恶追踪者" or "邪恶的追踪者" => GetString("EvilTracker"), - "邪惡賭怪" or "邪恶赌怪" or "坏赌" or "恶赌" or "邪恶赌怪" => GetString("EvilGuesser"), - "監管者" or "监管者" or "监管" => GetString("AntiAdminer"), - "狂妄殺手" or "狂妄杀手" => GetString("Arrogance"), - "自爆兵" or "自爆" => GetString("Bomber"), - "清道夫" or "清道" => GetString("Scavenger"), - "陷阱師" or "诡雷" => GetString("Trapster"), - "歹徒" => GetString("Gangster"), - "清潔工" or "清理工" or "清洁工" => GetString("Cleaner"), - "球狀閃電" or "球状闪电" => GetString("Lightning"), - "貪婪者" or "贪婪者" or "贪婪" => GetString("Greedy"), - "被詛咒的狼" or "呪狼" => GetString("CursedWolf"), - "換魂師" or "夺魂者" or "夺魂" => GetString("SoulCatcher"), - "快槍手" or "快枪手" or "快枪" => GetString("QuickShooter"), - "隱蔽者" or "隐蔽者" or "小黑人" => GetString("Camouflager"), - "抹除者" or "抹除" => GetString("Eraser"), - "肢解者" or "肢解" => GetString("Butcher"), - "劊子手" or "刽子手" => GetString("Hangman"), - "隱身人" or "隐匿者" or "隐匿" or "隐身" => GetString("Swooper"), - "船鬼" => GetString("Crewpostor"), - "野人" => GetString("Wildling"), - "騙術師" or "骗术师" => GetString("Trickster"), - "衛道士" or "卫道士" or "内鬼市长" => GetString("Vindicator"), - "寄生蟲" or "寄生虫" => GetString("Parasite"), - "分散者" or "分散" => GetString("Disperser"), - "抑鬱者" or "抑郁者" or "抑郁" => GetString("Inhibitor"), - "破壞者" or "破坏者" or "破坏" => GetString("Saboteur"), - "議員" or "邪恶法官" or "议员" or "邪恶审判" => GetString("Councillor"), - "眩暈者" or "眩晕者" or "眩晕" => GetString("Dazzler"), - "簽約人" or "死亡契约" or "死亡" or "锲约" => GetString("Deathpact"), - "吞噬者" or "吞噬" => GetString("Devourer"), - "軍師" or "军师" => GetString("Consigliere"), - "化型者" or "化形者" => GetString("Morphling"), - "躁動者" or "龙卷风" => GetString("Twister"), - "策畫者" or "潜伏者" or "潜伏" => GetString("Lurker"), - "罪犯" => GetString("Convict"), - "幻想家" or "幻想" => GetString("Visionary"), - "逃亡者" or "逃亡" => GetString("Refugee"), - "潛伏者" or "失败者" or "失败的man" or "失败" => GetString("Underdog"), - "賭博者" or "速度者" or "速度" => GetString("Ludopath"), - "懸賞者" or "教父" => GetString("Godfather"), - "天文學家" or "天文学家" or "天文家" or "天文学" => GetString("Chronomancer"), - "設陷者" or "设陷者" or "设陷" => GetString("Pitfall"), - "狂戰士" or "狂战士" or "升级者" or "狂战士" => GetString("Berserker"), - "壞迷你船員" or "坏迷你船员" or "坏小孩" or "坏迷你" => GetString("EvilMini"), - "勒索者" or "勒索" => GetString("Blackmailer"), - "教唆者" or "教唆" => GetString("Instigator"), - - // 船員陣營職業 and 船员阵营职业 - "擺爛人" or "摆烂人" or "摆烂" => GetString("Needy"), - "大明星" or "明星" => GetString("SuperStar"), - "網紅" or "网红" => GetString("Celebrity"), - "清洗者" or "清洗" => GetString("Cleanser"), - "守衛者" or "守卫者" => GetString("Keeper"), - "俠客" or "侠客" or "正义使者" => GetString("Knight"), - "市長" or "市长" => GetString("Mayor"), - "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), - "愚者" => GetString("Psychic"), - "修理工" or "修理" or "修理大师" => GetString("Mechanic"), - "警長" or "警长" => GetString("Sheriff"), - "義警" or "义务警员" or "警员" => GetString("Vigilante"), - "監禁者" or "狱警" or "狱卒" => GetString("Jailer"), - "模仿者" or "模仿猫" or "模仿" => GetString("CopyCat"), - "告密者" => GetString("Snitch"), - "展現者" or "展现者" or "展现" => GetString("Marshall"), - "增速師" or "增速者" or "增速" => GetString("SpeedBooster"), - "法醫" or "法医" => GetString("Doctor"), - "獨裁主義者" or "独裁者" or "独裁" => GetString("Dictator"), - "偵探" or "侦探" => GetString("Detective"), - "正義賭怪" or "正义赌怪" or "好赌" or "正义的赌怪" => GetString("NiceGuesser"), - "賭場管理員" or "竞猜大师" or "竞猜" => GetString("GuessMaster"), - "傳送師" or "传送师" => GetString("Transporter"), - "時間大師" or "时间操控者" or "时间操控" => GetString("TimeManager"), - "老兵" => GetString("Veteran"), - "埋雷兵" => GetString("Bastion"), - "保鑣" or "保镖" => GetString("Bodyguard"), - "贗品商" or "赝品商" => GetString("Deceiver"), - "擲彈兵" or "掷雷兵" => GetString("Grenadier"), - "軍醫" or "医生" => GetString("Medic"), - "占卜師" or "调查员" or "占卜师" => GetString("FortuneTeller"), - "法官" or "正义法官" or "正义审判" => GetString("Judge"), - "殯葬師" or "入殓师" => GetString("Mortician"), - "通靈師" or "通灵师" => GetString("Mediumshiper"), - "和平之鴿" or "和平之鸽" => GetString("Pacifist"), - "窺視者" or "观察者" or "观察" => GetString("Observer"), - "君主" => GetString("Monarch"), - "預言家" or "预言家" or "预言" => GetString("Overseer"), - "驗屍官" or "验尸官" or "验尸" => GetString("Coroner"), - "正義的追蹤者" or "正义追踪者" or "正义的追踪者" => GetString("Tracker"), - "商人" => GetString("Merchant"), - "總統" or "总统" => GetString("President"), - "獵鷹" or "猎鹰" => GetString("Hawk"), - "捕快" or "下属" => GetString("Deputy"), - "算命師" or "研究者" => GetString("Investigator"), - "守護者" or "守护者" or "守护" => GetString("Guardian"), - "賢者" or "瘾君子" or "醉酒" => GetString("Addict"), - "鼹鼠" => GetString("Mole"), - "藥劑師" or "炼金术士" or "药剂" => GetString("Alchemist"), - "尋跡者" or "寻迹者" or "寻迹" or "寻找鸡腿" => GetString("Tracefinder"), - "先知" or "神谕" or "神谕者" => GetString("Oracle"), - "靈魂論者" or "灵魂论者" => GetString("Spiritualist"), - "變色龍" or "变色龙" or "变色" => GetString("Chameleon"), - "檢查員" or "检查员" or "检查" => GetString("Inspector"), - "仰慕者" or "仰慕" => GetString("Admirer"), - "時間之主" or "时间之主" or "回溯时间" => GetString("TimeMaster"), - "十字軍" or "十字军" => GetString("Crusader"), - "遐想者" or "遐想" => GetString("Reverie"), - "瞭望者" or "瞭望员" => GetString("Lookout"), - "通訊員" or "通信员" => GetString("Telecommunication"), - "執燈人" or "执灯人" or "执灯" or "灯人" or "小灯人" => GetString("Lighter"), - "任務管理員" or "任务管理者" => GetString("TaskManager"), - "目擊者" or "目击者" or "目击" => GetString("Witness"), - "換票師" or "换票师" => GetString("Swapper"), - "警察局長" or "警察局长" => GetString("ChiefOfPolice"), - "好迷你船員" or "好迷你船员" or "好迷你" or "好小孩" => GetString("NiceMini"), - "間諜" or "间谍" => GetString("Spy"), - "隨機者" or "萧暮" or "暮" or "萧暮不姓萧" => GetString("Randomizer"), - "猜想者" or "猜想" or "谜团" => GetString("Enigma"), - "船長" or "舰长" or "船长" => GetString("Captain"), - "慈善家" or "恩人" => GetString("Benefactor"), - - // 中立陣營職業 and 中立阵营职业 - "小丑" or "丑皇" => GetString("Jester"), - "縱火犯" or "纵火犯" or "纵火者" or "纵火" => GetString("Arsonist"), - "焚燒狂" or "焚烧狂" or "焚烧" => GetString("Pyromaniac"), - "神風特攻隊" or "神风特攻队" => GetString("Kamikaze"), - "獵人" or "猎人" => GetString("Huntsman"), - "恐怖分子" => GetString("Terrorist"), - "暴民" or "处刑人" or "处刑" or "处刑者" => GetString("Executioner"), - "律師" or "律师" => GetString("Lawyer"), - "投機主義者" or "投机者" or "投机" => GetString("Opportunist"), - "瑪利歐" or "马里奥" => GetString("Vector"), - "豺狼" or "蓝狼" => GetString("Jackal"), - "神" or "上帝" => GetString("God"), - "冤罪師" or "冤罪师" or "冤罪" => GetString("Innocent"), - "暗殺者" or "隐形者" =>GetString("Stealth"), - "企鵝" or "企鹅" =>GetString("Penguin"), - "鵜鶘" or "鹈鹕" => GetString("Pelican"), - "疫醫" or "瘟疫学家" => GetString("PlagueDoctor"), - "革命家" or "革命者" => GetString("Revolutionist"), - "單身狗" => GetString("Hater"), - "柯南" => GetString("Konan"), - "玩家" => GetString("Demon"), - "潛藏者" or "潜藏" => GetString("Stalker"), - "工作狂" => GetString("Workaholic"), - "至日者" or "至日" => GetString("Solsticer"), - "集票者" or "集票" => GetString("Collector"), - "挑釁者" or "自爆卡车" => GetString("Provocateur"), - "嗜血騎士" or "嗜血骑士" => GetString("BloodKnight"), - "瘟疫之源" or "瘟疫使者" => GetString("PlagueBearer"), - "萬疫之神" or "瘟疫" => GetString("Pestilence"), - "故障者" or "缺点者" or "缺点" => GetString("Glitch"), - "跟班" or "跟班小弟" => GetString("Sidekick"), - "追隨者" or "赌徒" or "下注" => GetString("Follower"), - "魅魔" => GetString("Cultist"), - "連環殺手" or "连环杀手" => GetString("SerialKiller"), - "劍聖" or "天启" => GetString("Juggernaut"), - "感染者" or "感染" => GetString("Infectious"), - "病原體" or "病毒" => GetString("Virus"), - "起訴人" or "起诉人" => GetString("Pursuer"), - "怨靈" or "幽灵" => GetString("Phantom"), - "挑戰者" or "决斗者" or "挑战者" => GetString("Pirate"), - "炸彈王" or "炸弹狂" or "煽动者" => GetString("Agitater"), - "獨行者" or "独行者" => GetString("Maverick"), - "被詛咒的靈魂" or "诅咒之人" => GetString("CursedSoul"), - "竊賊" or "小偷" => GetString("Pickpocket"), - "背叛者" or "背叛" => GetString("Traitor"), - "禿鷲" or "秃鹫" => GetString("Vulture"), - "搗蛋鬼" or "任务执行者" => GetString("Taskinator"), - "麵包師" or "面包师" => GetString("Baker"), - "飢荒" or "饥荒" => GetString("Famine"), - "靈魂召喚者" or "灵魂召唤者" => GetString("Spiritcaller"), - "失憶者" or "失忆者" or "失忆" => GetString("Amnesiac"), - "模仿家" or "效仿者" => GetString("Imitator"), - "強盜" => GetString("Bandit"), - "分身者" => GetString("Doppelganger"), - "受虐狂" => GetString("PunchingBag"), - "賭神" or "末日赌怪" => GetString("Doomsayer"), - "裹屍布" or "裹尸布" => GetString("Shroud"), - "月下狼人" or "狼人" => GetString("Werewolf"), - "薩滿" or "萨满" => GetString("Shaman"), - "冒險家" or "探索者" => GetString("Seeker"), - "精靈" or "小精灵" or "精灵" => GetString("Pixie"), - "咒魔" or "神秘者" => GetString("Occultist"), - "靈魂收割者" or "灵魂收集者" or "灵魂收集" or "收集灵魂" => GetString("SoulCollector"), - "薛丁格的貓" or "薛定谔的猫" => GetString("SchrodingersCat"), - "暗戀者" or "浪漫者" => GetString("Romantic"), - "報復者" or "复仇浪漫者" => GetString("VengefulRomantic"), - "絕情者" or "无情浪漫者" => GetString("RuthlessRomantic"), - "毒醫" or "投毒者" => GetString("Poisoner"), - "代碼工程師" or "巫师" => GetString("HexMaster"), - "幻影" or "魅影" => GetString("Wraith"), - "掃把星" or "扫把星" => GetString("Jinx"), - "魔藥師" or "药剂师" => GetString("PotionMaster"), - "死靈法師" or "亡灵巫师" => GetString("Necromancer"), - "測驗者" or "测验长" => GetString("Quizmaster"), - - // 附加職業 and 附加职业 - "絕境者" or "绝境者" => GetString("LastImpostor"), - "超頻" or "超频波" or "超频" => GetString("Overclocked"), - "戀人" or "恋人" => GetString("Lovers"), - "叛徒" => GetString("Madmate"), - "觀察者" or "窥视者" or "觀察" or "窥视" => GetString("Watcher"), - "閃電俠" or "闪电侠" or "閃電" or "闪电" => GetString("Flash"), - "持燈人" or "火炬" or "持燈" => GetString("Torch"), - "靈媒" or "灵媒" or "靈媒" => GetString("Seer"), - "破平者" or "破平" => GetString("Tiebreaker"), - "膽小鬼" or "胆小鬼" or "膽小" or "胆小" => GetString("Oblivious"), - "視障" or "迷幻者" or "視障" or "迷幻" => GetString("Bewilder"), - "墨鏡" or "患者" => GetString("Sunglasses"), - "加班狂" => GetString("Workhorse"), - "蠢蛋" => GetString("Fool"), - "復仇者" or "复仇者" or "復仇" or "复仇" => GetString("Avanger"), - "Youtuber" or "UP主" or "YT" => GetString("Youtuber"), - "利己主義者" or "利己主义者" or "利己主義" or "利己主义" => GetString("Egoist"), - "竊票者" or "窃票者" or "竊票" or "窃票" => GetString("TicketsStealer"), - //"雙重人格" or "双重人格" => GetString("Schizophrenic"), - "保險箱" or "宝箱怪" => GetString("Mimic"), - "賭怪" or "赌怪" => GetString("Guesser"), - "死神" => GetString("Necroview"), - "長槍" or "持枪" => GetString("Reach"), - "魅魔小弟" => GetString("Charmed"), - "乾淨" or "干净" => GetString("Cleansed"), - "誘餌" or "诱饵" => GetString("Bait"), - "陷阱師" or "陷阱师" => GetString("Trapper"), - "被感染" or "感染" => GetString("Infected"), - "防賭" or "不可被赌" => GetString("Onbound"), - "反擊者" or "回弹者" or "回弹" => GetString("Rebound"), - "平凡者" or "平凡" => GetString("Mundane"), - "騎士" or "骑士" => GetString("Knighted"), - "漠視" or "不受重视" or "被漠視的" => GetString("Unreportable"), - "被傳染" or "传染性" => GetString("Contagious"), - "幸運" or "幸运加持" => GetString("Lucky"), - "倒霉" or "倒霉蛋" => GetString("Unlucky"), - "虛無" or "无效投票" => GetString("VoidBallot"), - "敏感" or "意识者" or "意识" => GetString("Aware"), - "嬌嫩" or "脆弱" or "脆弱者" => GetString("Fragile"), - "專業" or "双重猜测" => GetString("DoubleShot"), - "流氓" => GetString("Rascal"), - "無魂" or "没有灵魂" => GetString("Soulless"), - "墓碑" => GetString("Gravestone"), - "懶人" or "懒人" => GetString("Lazy"), - "驗屍" or "尸检" => GetString("Autopsy"), - "忠誠" or "忠诚" => GetString("Loyal"), - "惡靈" or "恶灵" => GetString("EvilSpirit"), - "狼化" or "招募" or "狼化的" or "被招募的" => GetString("Recruit"), - "被仰慕" or "仰慕" => GetString("Admired"), - "發光" or "光辉" => GetString("Glow"), - "病態" or "患病者" or "患病的" or "患病" => GetString("Diseased"), - "健康" or "健康的" or "健康者" => GetString("Antidote"), - "固執者" or "固执者" or "固執" or "固执" => GetString("Stubborn"), - "無影" or "迅捷" => GetString("Swift"), - "反噬" or "食尸鬼" => GetString("Ghoul"), - "嗜血者" => GetString("Bloodthirst"), - "獵夢者" or "梦魇" or "獵夢"=> GetString("Mare"), - "地雷" or "爆破者" or "爆破" => GetString("Burst"), - "偵察員" or "侦察员" or "偵察" or "侦察" => GetString("Sleuth"), - "笨拙" or "笨蛋" => GetString("Clumsy"), - "敏捷" => GetString("Nimble"), - "規避者" or "规避者" or "规避" => GetString("Circumvent"), - "名人" or "网络员" or "网络" => GetString("Cyber"), - "焦急者" or "焦急的" or "焦急" => GetString("Hurried"), - "OIIAI" => GetString("Oiiai"), - "順從者" or "影响者" or "順從" or "影响" => GetString("Influenced"), - "沉默者" or "沉默" => GetString("Silent"), - "易感者" or "易感" => GetString("Susceptible"), - "狡猾" or "棘手者" or "棘手" => GetString("Tricky"), - "彩虹" => GetString("Rainbow"), - "疲勞者" or "疲劳者" or "疲勞" or "疲劳" => GetString("Tired"), - "雕像" => GetString("Statue"), - "没有搜集的繁体中文" or "雷达" => GetString("Radar"), - - // 幽靈職業 and 幽灵职业 - // 偽裝者 and 内鬼 - "爪牙" => GetString("Minion"), - "黑手黨" or "黑手党" or "黑手" => GetString("Nemesis"), - "嗜血之魂" or "血液伯爵" => GetString("Bloodmoon"), - // 船員 and 船员 - "没有搜集的繁体中文" or "鬼怪" => GetString("Ghastly"), - "冤魂" or "典狱长" => GetString("Warden"), - "報應者" or "惩罚者" or "惩罚" or "报仇者" => GetString("Retributionist"), - - // 随机阵营职业 - "迷你船員" or "迷你船员" or "迷你" or "小孩" or "Mini" => GetString("Mini"),*/ - _ => text, - }; - } - - public static bool GetRoleByName(string name, out CustomRoles role) - { - role = new(); - - if (name == "" || name == string.Empty) return false; - - if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) - { - Regex r = new("[\u4e00-\u9fa5]+$"); - MatchCollection mc = r.Matches(name); - string result = string.Empty; - for (int i = 0; i < mc.Count; i++) - { - if (mc[i].ToString() == "是") continue; - result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 - } - name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); - } - else name = name.Trim().ToLower(); - - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", ""); - string nameWithoutId = Regex.Replace(name.Replace(" ", ""), @"^\d+", ""); - if (nameWithoutId == roleName) - { - role = rl; - return true; - } - } - return false; - } - public static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) - { - if (Options.CurrentGameMode == CustomGameMode.FFA) - { - Utils.SendMessage(GetString("ModeDescribe.FFA"), playerId); - return; - } - role = role.Trim().ToLower(); - if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); - if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); - if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); - if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); - if (role.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); - - if (role == "" || role == string.Empty) - { - Utils.ShowActiveRoles(playerId); - return; - } - - role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); - - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()); - if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) - { - string devMark = ""; - if ((isDev || isUp) && GameStates.IsLobby) - { - devMark = "▲"; - if (CustomRolesHelper.IsAdditionRole(rl) || rl is CustomRoles.GM or CustomRoles.Mini || rl.IsGhostRole()) devMark = ""; - if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = ""; - if (isUp) - { - if (devMark == "▲") Utils.SendMessage(string.Format(GetString("Message.YTPlanSelected"), roleName), playerId); - else Utils.SendMessage(string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); - } - if (devMark == "▲") - { - byte pid = playerId == 255 ? (byte)0 : playerId; - GhostRoleAssign.forceRole.Remove(pid); - RoleAssign.SetRoles.Remove(pid); - RoleAssign.SetRoles.Add(pid, rl); - } - if (rl.IsGhostRole() && !rl.IsAdditionRole() && isDev && (rl.GetCount() >= 1 && rl.GetMode() > 0)) - { - byte pid = playerId == 255 ? (byte)0 : playerId; - CustomRoles setrole = rl.GetCustomRoleTeam() switch - { - Custom_Team.Impostor => CustomRoles.ImpostorTOHE, - _ => CustomRoles.CrewmateTOHE - - }; - RoleAssign.SetRoles.Remove(pid); - RoleAssign.SetRoles.Add(pid, setrole); - GhostRoleAssign.forceRole[pid] = rl; - - devMark = "▲"; - } - - if (isUp) return; - } - var Des = rl.GetInfoLong(); - var title = devMark + $"" + rl.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - string rlHex = Utils.GetRoleColorCode(rl); - if (Options.CustomRoleSpawnChances.ContainsKey(rl)) - { - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(rl.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - } - // Show role info - Utils.SendMessage(Des, playerId, title, noReplay: true); - - // Show role settings - Utils.SendMessage("", playerId, Conf.ToString(), noReplay: true); - return; - } - } - if (isUp) Utils.SendMessage(GetString("Message.YTPlanCanNotFindRoleThePlayerEnter"), playerId); - else Utils.SendMessage(GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); - return; - } - public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) - { - canceled = false; - if (!AmongUsClient.Instance.AmHost) return; - - if (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); - - if (text.StartsWith("\n")) text = text[1..]; - //if (!text.StartsWith("/")) return; - string[] args = text.Split(' '); - string subArgs = ""; - string subArgs2 = ""; - - //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; - // if (SpamManager.CheckSpam(player, text)) return; - if (GuessManager.GuesserMsg(player, text)) { canceled = true; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } - if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } - if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } - if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Councillor cl && cl.MurderMsg(player, text)) { canceled = true; Logger.Info($"Is Councillor command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Swapper sw && sw.SwapMsg(player, text)) { canceled = true; Logger.Info($"Is Swapper command", "OnReceiveChat"); return; } - if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } - if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } - if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } - - Directory.CreateDirectory(modTagsFiles); - Directory.CreateDirectory(vipTagsFiles); - Directory.CreateDirectory(sponsorTagsFiles); - - if (Blackmailer.CheckBlackmaile(player) && player.IsAlive()) - { - Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); - ChatManager.SendPreviousMessagesToAll(); - ChatManager.cancel = false; - canceled = true; - return; - } - - switch (args[0]) - { - case "/r": - case "/role": - case "/р": - case "/роль": - //case "/职业": - //case "/角色": - Logger.Info($"Command '/r' was activated", "OnReceiveChat"); - if (text.Contains("/role") || text.Contains("/роль")/* || text.Contains("/角色")*/) - subArgs = text.Remove(0, 5); - else - subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); - break; - - case "/m": - case "/myrole": - case "/minhafunção": - case "/м": + case "/幸运球": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + var rando = IRandom.Instance; + int result = rando.Next(0, 16); + string str = ""; + switch (result) + { + case 0: + str = GetString("8BallYes"); + break; + case 1: + str = GetString("8BallNo"); + break; + case 2: + str = GetString("8BallMaybe"); + break; + case 3: + str = GetString("8BallTryAgainLater"); + break; + case 4: + str = GetString("8BallCertain"); + break; + case 5: + str = GetString("8BallNotLikely"); + break; + case 6: + str = GetString("8BallLikely"); + break; + case 7: + str = GetString("8BallDontCount"); + break; + case 8: + str = GetString("8BallStop"); + break; + case 9: + str = GetString("8BallPossibly"); + break; + case 10: + str = GetString("8BallProbably"); + break; + case 11: + str = GetString("8BallProbablyNot"); + break; + case 12: + str = GetString("8BallBetterNotTell"); + break; + case 13: + str = GetString("8BallCantPredict"); + break; + case 14: + str = GetString("8BallWithoutDoubt"); + break; + case 15: + str = GetString("8BallWithDoubt"); + break; + } + Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); + break; + + default: + Main.isChatCommand = false; + break; + } + } + goto Skip; + Canceled: + Main.isChatCommand = false; + canceled = true; + Skip: + if (canceled) + { + Logger.Info("Command Canceled", "ChatCommand"); + __instance.freeChatField.textArea.Clear(); + __instance.freeChatField.textArea.SetText(cancelVal); + + __instance.quickChatMenu.Clear(); + __instance.quickChatField.Clear(); + } + return !canceled; + } + + public static string FixRoleNameInput(string text) + { + text = text.Replace("着", "者").Trim().ToLower(); + return text switch + { + // Because of partial translation conflicts (zh-cn and zh-tw) + // Need to wait for follow-up finishing + + /* + // GM + "GM(遊戲大師)" or "管理员" or "管理" or "gm" or "GM" => GetString("GM"), + + // 原版职业 + "船員" or "船员" or "白板" or "天选之子" => GetString("CrewmateTOHE"), + "工程師" or "工程师" => GetString("EngineerTOHE"), + "科學家" or "科学家" => GetString("ScientistTOHE"), + "守護天使" or "守护天使" => GetString("GuardianAngelTOHE"), + "偽裝者" or "内鬼" => GetString("ImpostorTOHE"), + "變形者" or "变形者" => GetString("ShapeshifterTOHE"), + + // 隱藏職業 and 隐藏职业 + "陽光開朗大男孩" or "阳光开朗大男孩" => GetString("Sunnyboy"), + "吟遊詩人" or "吟游诗人" => GetString("Bard"), + "核爆者" or "核武器" => GetString("Nuker"), + + // 偽裝者陣營職業 and 内鬼阵营职业 + "賞金獵人" or "赏金猎人" or "赏金" => GetString("BountyHunter"), + "煙火工匠" or "烟花商人" or "烟花爆破者" or "烟花" => GetString("Fireworker"), + "嗜血殺手" or "嗜血杀手" or "嗜血" => GetString("Mercenary"), + "百变怪" or "千面鬼" or "千面" => GetString("ShapeMaster"), + "吸血鬼" or "吸血" => GetString("Vampire"), + "吸血鬼之王" or "吸血鬼女王" => GetString("Vampiress"), + "術士" or "术士" => GetString("Warlock"), + "刺客" or "忍者" => GetString("Ninja"), + "僵屍" or "僵尸" or"殭屍" or "丧尸" => GetString("Zombie"), + "駭客" or "骇客" or "黑客" => GetString("Anonymous"), + "礦工" or "矿工" => GetString("Miner"), + "殺人機器" or "杀戮机器" or "杀戮" or "机器" or "杀戮兵器" => GetString("KillingMachine"), + "通緝犯" or "逃逸者" or "逃逸" => GetString("Escapist"), + "女巫" => GetString("Witch"), + "傀儡師" or "傀儡师" or "傀儡" => GetString("Puppeteer"), + "主謀" or "策划者" => GetString("Mastermind"), + "時間竊賊" or "蚀时者" or "蚀时" or "偷时" => GetString("TimeThief"), + "狙擊手" or "狙击手" or "狙击" => GetString("Sniper"), + "送葬者" or "暗杀者" => GetString("Undertaker"), + "裂縫製造者" or "裂缝制造者" => GetString("RiftMaker"), + "邪惡的追踪者" or "邪恶追踪者" or "邪恶的追踪者" => GetString("EvilTracker"), + "邪惡賭怪" or "邪恶赌怪" or "坏赌" or "恶赌" or "邪恶赌怪" => GetString("EvilGuesser"), + "監管者" or "监管者" or "监管" => GetString("AntiAdminer"), + "狂妄殺手" or "狂妄杀手" => GetString("Arrogance"), + "自爆兵" or "自爆" => GetString("Bomber"), + "清道夫" or "清道" => GetString("Scavenger"), + "陷阱師" or "诡雷" => GetString("Trapster"), + "歹徒" => GetString("Gangster"), + "清潔工" or "清理工" or "清洁工" => GetString("Cleaner"), + "球狀閃電" or "球状闪电" => GetString("Lightning"), + "貪婪者" or "贪婪者" or "贪婪" => GetString("Greedy"), + "被詛咒的狼" or "呪狼" => GetString("CursedWolf"), + "換魂師" or "夺魂者" or "夺魂" => GetString("SoulCatcher"), + "快槍手" or "快枪手" or "快枪" => GetString("QuickShooter"), + "隱蔽者" or "隐蔽者" or "小黑人" => GetString("Camouflager"), + "抹除者" or "抹除" => GetString("Eraser"), + "肢解者" or "肢解" => GetString("Butcher"), + "劊子手" or "刽子手" => GetString("Hangman"), + "隱身人" or "隐匿者" or "隐匿" or "隐身" => GetString("Swooper"), + "船鬼" => GetString("Crewpostor"), + "野人" => GetString("Wildling"), + "騙術師" or "骗术师" => GetString("Trickster"), + "衛道士" or "卫道士" or "内鬼市长" => GetString("Vindicator"), + "寄生蟲" or "寄生虫" => GetString("Parasite"), + "分散者" or "分散" => GetString("Disperser"), + "抑鬱者" or "抑郁者" or "抑郁" => GetString("Inhibitor"), + "破壞者" or "破坏者" or "破坏" => GetString("Saboteur"), + "議員" or "邪恶法官" or "议员" or "邪恶审判" => GetString("Councillor"), + "眩暈者" or "眩晕者" or "眩晕" => GetString("Dazzler"), + "簽約人" or "死亡契约" or "死亡" or "锲约" => GetString("Deathpact"), + "吞噬者" or "吞噬" => GetString("Devourer"), + "軍師" or "军师" => GetString("Consigliere"), + "化型者" or "化形者" => GetString("Morphling"), + "躁動者" or "龙卷风" => GetString("Twister"), + "策畫者" or "潜伏者" or "潜伏" => GetString("Lurker"), + "罪犯" => GetString("Convict"), + "幻想家" or "幻想" => GetString("Visionary"), + "逃亡者" or "逃亡" => GetString("Refugee"), + "潛伏者" or "失败者" or "失败的man" or "失败" => GetString("Underdog"), + "賭博者" or "速度者" or "速度" => GetString("Ludopath"), + "懸賞者" or "教父" => GetString("Godfather"), + "天文學家" or "天文学家" or "天文家" or "天文学" => GetString("Chronomancer"), + "設陷者" or "设陷者" or "设陷" => GetString("Pitfall"), + "狂戰士" or "狂战士" or "升级者" or "狂战士" => GetString("Berserker"), + "壞迷你船員" or "坏迷你船员" or "坏小孩" or "坏迷你" => GetString("EvilMini"), + "勒索者" or "勒索" => GetString("Blackmailer"), + "教唆者" or "教唆" => GetString("Instigator"), + + // 船員陣營職業 and 船员阵营职业 + "擺爛人" or "摆烂人" or "摆烂" => GetString("Needy"), + "大明星" or "明星" => GetString("SuperStar"), + "網紅" or "网红" => GetString("Celebrity"), + "清洗者" or "清洗" => GetString("Cleanser"), + "守衛者" or "守卫者" => GetString("Keeper"), + "俠客" or "侠客" or "正义使者" => GetString("Knight"), + "市長" or "市长" => GetString("Mayor"), + "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), + "愚者" => GetString("Psychic"), + "修理工" or "修理" or "修理大师" => GetString("Mechanic"), + "警長" or "警长" => GetString("Sheriff"), + "義警" or "义务警员" or "警员" => GetString("Vigilante"), + "監禁者" or "狱警" or "狱卒" => GetString("Jailer"), + "模仿者" or "模仿猫" or "模仿" => GetString("CopyCat"), + "告密者" => GetString("Snitch"), + "展現者" or "展现者" or "展现" => GetString("Marshall"), + "增速師" or "增速者" or "增速" => GetString("SpeedBooster"), + "法醫" or "法医" => GetString("Doctor"), + "獨裁主義者" or "独裁者" or "独裁" => GetString("Dictator"), + "偵探" or "侦探" => GetString("Detective"), + "正義賭怪" or "正义赌怪" or "好赌" or "正义的赌怪" => GetString("NiceGuesser"), + "賭場管理員" or "竞猜大师" or "竞猜" => GetString("GuessMaster"), + "傳送師" or "传送师" => GetString("Transporter"), + "時間大師" or "时间操控者" or "时间操控" => GetString("TimeManager"), + "老兵" => GetString("Veteran"), + "埋雷兵" => GetString("Bastion"), + "保鑣" or "保镖" => GetString("Bodyguard"), + "贗品商" or "赝品商" => GetString("Deceiver"), + "擲彈兵" or "掷雷兵" => GetString("Grenadier"), + "軍醫" or "医生" => GetString("Medic"), + "占卜師" or "调查员" or "占卜师" => GetString("FortuneTeller"), + "法官" or "正义法官" or "正义审判" => GetString("Judge"), + "殯葬師" or "入殓师" => GetString("Mortician"), + "通靈師" or "通灵师" => GetString("Mediumshiper"), + "和平之鴿" or "和平之鸽" => GetString("Pacifist"), + "窺視者" or "观察者" or "观察" => GetString("Observer"), + "君主" => GetString("Monarch"), + "預言家" or "预言家" or "预言" => GetString("Overseer"), + "驗屍官" or "验尸官" or "验尸" => GetString("Coroner"), + "正義的追蹤者" or "正义追踪者" or "正义的追踪者" => GetString("Tracker"), + "商人" => GetString("Merchant"), + "總統" or "总统" => GetString("President"), + "獵鷹" or "猎鹰" => GetString("Hawk"), + "捕快" or "下属" => GetString("Deputy"), + "算命師" or "研究者" => GetString("Investigator"), + "守護者" or "守护者" or "守护" => GetString("Guardian"), + "賢者" or "瘾君子" or "醉酒" => GetString("Addict"), + "鼹鼠" => GetString("Mole"), + "藥劑師" or "炼金术士" or "药剂" => GetString("Alchemist"), + "尋跡者" or "寻迹者" or "寻迹" or "寻找鸡腿" => GetString("Tracefinder"), + "先知" or "神谕" or "神谕者" => GetString("Oracle"), + "靈魂論者" or "灵魂论者" => GetString("Spiritualist"), + "變色龍" or "变色龙" or "变色" => GetString("Chameleon"), + "檢查員" or "检查员" or "检查" => GetString("Inspector"), + "仰慕者" or "仰慕" => GetString("Admirer"), + "時間之主" or "时间之主" or "回溯时间" => GetString("TimeMaster"), + "十字軍" or "十字军" => GetString("Crusader"), + "遐想者" or "遐想" => GetString("Reverie"), + "瞭望者" or "瞭望员" => GetString("Lookout"), + "通訊員" or "通信员" => GetString("Telecommunication"), + "執燈人" or "执灯人" or "执灯" or "灯人" or "小灯人" => GetString("Lighter"), + "任務管理員" or "任务管理者" => GetString("TaskManager"), + "目擊者" or "目击者" or "目击" => GetString("Witness"), + "換票師" or "换票师" => GetString("Swapper"), + "警察局長" or "警察局长" => GetString("ChiefOfPolice"), + "好迷你船員" or "好迷你船员" or "好迷你" or "好小孩" => GetString("NiceMini"), + "間諜" or "间谍" => GetString("Spy"), + "隨機者" or "萧暮" or "暮" or "萧暮不姓萧" => GetString("Randomizer"), + "猜想者" or "猜想" or "谜团" => GetString("Enigma"), + "船長" or "舰长" or "船长" => GetString("Captain"), + "慈善家" or "恩人" => GetString("Benefactor"), + + // 中立陣營職業 and 中立阵营职业 + "小丑" or "丑皇" => GetString("Jester"), + "縱火犯" or "纵火犯" or "纵火者" or "纵火" => GetString("Arsonist"), + "焚燒狂" or "焚烧狂" or "焚烧" => GetString("Pyromaniac"), + "神風特攻隊" or "神风特攻队" => GetString("Kamikaze"), + "獵人" or "猎人" => GetString("Huntsman"), + "恐怖分子" => GetString("Terrorist"), + "暴民" or "处刑人" or "处刑" or "处刑者" => GetString("Executioner"), + "律師" or "律师" => GetString("Lawyer"), + "投機主義者" or "投机者" or "投机" => GetString("Opportunist"), + "瑪利歐" or "马里奥" => GetString("Vector"), + "豺狼" or "蓝狼" => GetString("Jackal"), + "神" or "上帝" => GetString("God"), + "冤罪師" or "冤罪师" or "冤罪" => GetString("Innocent"), + "暗殺者" or "隐形者" =>GetString("Stealth"), + "企鵝" or "企鹅" =>GetString("Penguin"), + "鵜鶘" or "鹈鹕" => GetString("Pelican"), + "疫醫" or "瘟疫学家" => GetString("PlagueDoctor"), + "革命家" or "革命者" => GetString("Revolutionist"), + "單身狗" => GetString("Hater"), + "柯南" => GetString("Konan"), + "玩家" => GetString("Demon"), + "潛藏者" or "潜藏" => GetString("Stalker"), + "工作狂" => GetString("Workaholic"), + "至日者" or "至日" => GetString("Solsticer"), + "集票者" or "集票" => GetString("Collector"), + "挑釁者" or "自爆卡车" => GetString("Provocateur"), + "嗜血騎士" or "嗜血骑士" => GetString("BloodKnight"), + "瘟疫之源" or "瘟疫使者" => GetString("PlagueBearer"), + "萬疫之神" or "瘟疫" => GetString("Pestilence"), + "故障者" or "缺点者" or "缺点" => GetString("Glitch"), + "跟班" or "跟班小弟" => GetString("Sidekick"), + "追隨者" or "赌徒" or "下注" => GetString("Follower"), + "魅魔" => GetString("Cultist"), + "連環殺手" or "连环杀手" => GetString("SerialKiller"), + "劍聖" or "天启" => GetString("Juggernaut"), + "感染者" or "感染" => GetString("Infectious"), + "病原體" or "病毒" => GetString("Virus"), + "起訴人" or "起诉人" => GetString("Pursuer"), + "怨靈" or "幽灵" => GetString("Phantom"), + "挑戰者" or "决斗者" or "挑战者" => GetString("Pirate"), + "炸彈王" or "炸弹狂" or "煽动者" => GetString("Agitater"), + "獨行者" or "独行者" => GetString("Maverick"), + "被詛咒的靈魂" or "诅咒之人" => GetString("CursedSoul"), + "竊賊" or "小偷" => GetString("Pickpocket"), + "背叛者" or "背叛" => GetString("Traitor"), + "禿鷲" or "秃鹫" => GetString("Vulture"), + "搗蛋鬼" or "任务执行者" => GetString("Taskinator"), + "麵包師" or "面包师" => GetString("Baker"), + "飢荒" or "饥荒" => GetString("Famine"), + "靈魂召喚者" or "灵魂召唤者" => GetString("Spiritcaller"), + "失憶者" or "失忆者" or "失忆" => GetString("Amnesiac"), + "模仿家" or "效仿者" => GetString("Imitator"), + "強盜" => GetString("Bandit"), + "分身者" => GetString("Doppelganger"), + "受虐狂" => GetString("PunchingBag"), + "賭神" or "末日赌怪" => GetString("Doomsayer"), + "裹屍布" or "裹尸布" => GetString("Shroud"), + "月下狼人" or "狼人" => GetString("Werewolf"), + "薩滿" or "萨满" => GetString("Shaman"), + "冒險家" or "探索者" => GetString("Seeker"), + "精靈" or "小精灵" or "精灵" => GetString("Pixie"), + "咒魔" or "神秘者" => GetString("Occultist"), + "靈魂收割者" or "灵魂收集者" or "灵魂收集" or "收集灵魂" => GetString("SoulCollector"), + "薛丁格的貓" or "薛定谔的猫" => GetString("SchrodingersCat"), + "暗戀者" or "浪漫者" => GetString("Romantic"), + "報復者" or "复仇浪漫者" => GetString("VengefulRomantic"), + "絕情者" or "无情浪漫者" => GetString("RuthlessRomantic"), + "毒醫" or "投毒者" => GetString("Poisoner"), + "代碼工程師" or "巫师" => GetString("HexMaster"), + "幻影" or "魅影" => GetString("Wraith"), + "掃把星" or "扫把星" => GetString("Jinx"), + "魔藥師" or "药剂师" => GetString("PotionMaster"), + "死靈法師" or "亡灵巫师" => GetString("Necromancer"), + "測驗者" or "测验长" => GetString("Quizmaster"), + + // 附加職業 and 附加职业 + "絕境者" or "绝境者" => GetString("LastImpostor"), + "超頻" or "超频波" or "超频" => GetString("Overclocked"), + "戀人" or "恋人" => GetString("Lovers"), + "叛徒" => GetString("Madmate"), + "觀察者" or "窥视者" or "觀察" or "窥视" => GetString("Watcher"), + "閃電俠" or "闪电侠" or "閃電" or "闪电" => GetString("Flash"), + "持燈人" or "火炬" or "持燈" => GetString("Torch"), + "靈媒" or "灵媒" or "靈媒" => GetString("Seer"), + "破平者" or "破平" => GetString("Tiebreaker"), + "膽小鬼" or "胆小鬼" or "膽小" or "胆小" => GetString("Oblivious"), + "視障" or "迷幻者" or "視障" or "迷幻" => GetString("Bewilder"), + "墨鏡" or "患者" => GetString("Sunglasses"), + "加班狂" => GetString("Workhorse"), + "蠢蛋" => GetString("Fool"), + "復仇者" or "复仇者" or "復仇" or "复仇" => GetString("Avanger"), + "Youtuber" or "UP主" or "YT" => GetString("Youtuber"), + "利己主義者" or "利己主义者" or "利己主義" or "利己主义" => GetString("Egoist"), + "竊票者" or "窃票者" or "竊票" or "窃票" => GetString("TicketsStealer"), + //"雙重人格" or "双重人格" => GetString("Schizophrenic"), + "保險箱" or "宝箱怪" => GetString("Mimic"), + "賭怪" or "赌怪" => GetString("Guesser"), + "死神" => GetString("Necroview"), + "長槍" or "持枪" => GetString("Reach"), + "魅魔小弟" => GetString("Charmed"), + "乾淨" or "干净" => GetString("Cleansed"), + "誘餌" or "诱饵" => GetString("Bait"), + "陷阱師" or "陷阱师" => GetString("Trapper"), + "被感染" or "感染" => GetString("Infected"), + "防賭" or "不可被赌" => GetString("Onbound"), + "反擊者" or "回弹者" or "回弹" => GetString("Rebound"), + "平凡者" or "平凡" => GetString("Mundane"), + "騎士" or "骑士" => GetString("Knighted"), + "漠視" or "不受重视" or "被漠視的" => GetString("Unreportable"), + "被傳染" or "传染性" => GetString("Contagious"), + "幸運" or "幸运加持" => GetString("Lucky"), + "倒霉" or "倒霉蛋" => GetString("Unlucky"), + "虛無" or "无效投票" => GetString("VoidBallot"), + "敏感" or "意识者" or "意识" => GetString("Aware"), + "嬌嫩" or "脆弱" or "脆弱者" => GetString("Fragile"), + "專業" or "双重猜测" => GetString("DoubleShot"), + "流氓" => GetString("Rascal"), + "無魂" or "没有灵魂" => GetString("Soulless"), + "墓碑" => GetString("Gravestone"), + "懶人" or "懒人" => GetString("Lazy"), + "驗屍" or "尸检" => GetString("Autopsy"), + "忠誠" or "忠诚" => GetString("Loyal"), + "惡靈" or "恶灵" => GetString("EvilSpirit"), + "狼化" or "招募" or "狼化的" or "被招募的" => GetString("Recruit"), + "被仰慕" or "仰慕" => GetString("Admired"), + "發光" or "光辉" => GetString("Glow"), + "病態" or "患病者" or "患病的" or "患病" => GetString("Diseased"), + "健康" or "健康的" or "健康者" => GetString("Antidote"), + "固執者" or "固执者" or "固執" or "固执" => GetString("Stubborn"), + "無影" or "迅捷" => GetString("Swift"), + "反噬" or "食尸鬼" => GetString("Ghoul"), + "嗜血者" => GetString("Bloodthirst"), + "獵夢者" or "梦魇" or "獵夢"=> GetString("Mare"), + "地雷" or "爆破者" or "爆破" => GetString("Burst"), + "偵察員" or "侦察员" or "偵察" or "侦察" => GetString("Sleuth"), + "笨拙" or "笨蛋" => GetString("Clumsy"), + "敏捷" => GetString("Nimble"), + "規避者" or "规避者" or "规避" => GetString("Circumvent"), + "名人" or "网络员" or "网络" => GetString("Cyber"), + "焦急者" or "焦急的" or "焦急" => GetString("Hurried"), + "OIIAI" => GetString("Oiiai"), + "順從者" or "影响者" or "順從" or "影响" => GetString("Influenced"), + "沉默者" or "沉默" => GetString("Silent"), + "易感者" or "易感" => GetString("Susceptible"), + "狡猾" or "棘手者" or "棘手" => GetString("Tricky"), + "彩虹" => GetString("Rainbow"), + "疲勞者" or "疲劳者" or "疲勞" or "疲劳" => GetString("Tired"), + "雕像" => GetString("Statue"), + "没有搜集的繁体中文" or "雷达" => GetString("Radar"), + + // 幽靈職業 and 幽灵职业 + // 偽裝者 and 内鬼 + "爪牙" => GetString("Minion"), + "黑手黨" or "黑手党" or "黑手" => GetString("Nemesis"), + "嗜血之魂" or "血液伯爵" => GetString("Bloodmoon"), + // 船員 and 船员 + "没有搜集的繁体中文" or "鬼怪" => GetString("Ghastly"), + "冤魂" or "典狱长" => GetString("Warden"), + "報應者" or "惩罚者" or "惩罚" or "报仇者" => GetString("Retributionist"), + + // 随机阵营职业 + "迷你船員" or "迷你船员" or "迷你" or "小孩" or "Mini" => GetString("Mini"),*/ + _ => text, + }; + } + + public static bool GetRoleByName(string name, out CustomRoles role) + { + role = new(); + + if (name == "" || name == string.Empty) return false; + + if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) + { + Regex r = new("[\u4e00-\u9fa5]+$"); + MatchCollection mc = r.Matches(name); + string result = string.Empty; + for (int i = 0; i < mc.Count; i++) + { + if (mc[i].ToString() == "是") continue; + result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 + } + name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); + } + else name = name.Trim().ToLower(); + + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", ""); + string nameWithoutId = Regex.Replace(name.Replace(" ", ""), @"^\d+", ""); + if (nameWithoutId == roleName) + { + role = rl; + return true; + } + } + return false; + } + public static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) + { + if (Options.CurrentGameMode == CustomGameMode.FFA) + { + Utils.SendMessage(GetString("ModeDescribe.FFA"), playerId); + return; + } + role = role.Trim().ToLower(); + if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); + if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); + if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); + if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); + if (role.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); + + if (role == "" || role == string.Empty) + { + Utils.ShowActiveRoles(playerId); + return; + } + + role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); + + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()); + if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) + { + string devMark = ""; + if ((isDev || isUp) && GameStates.IsLobby) + { + devMark = "▲"; + if (CustomRolesHelper.IsAdditionRole(rl) || rl is CustomRoles.GM or CustomRoles.Mini || rl.IsGhostRole()) devMark = ""; + if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = ""; + if (isUp) + { + if (devMark == "▲") Utils.SendMessage(string.Format(GetString("Message.YTPlanSelected"), roleName), playerId); + else Utils.SendMessage(string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); + } + if (devMark == "▲") + { + byte pid = playerId == 255 ? (byte)0 : playerId; + GhostRoleAssign.forceRole.Remove(pid); + RoleAssign.SetRoles.Remove(pid); + RoleAssign.SetRoles.Add(pid, rl); + } + if (rl.IsGhostRole() && !rl.IsAdditionRole() && isDev && (rl.GetCount() >= 1 && rl.GetMode() > 0)) + { + byte pid = playerId == 255 ? (byte)0 : playerId; + CustomRoles setrole = rl.GetCustomRoleTeam() switch + { + Custom_Team.Impostor => CustomRoles.ImpostorTOHE, + _ => CustomRoles.CrewmateTOHE + + }; + RoleAssign.SetRoles.Remove(pid); + RoleAssign.SetRoles.Add(pid, setrole); + GhostRoleAssign.forceRole[pid] = rl; + + devMark = "▲"; + } + + if (isUp) return; + } + var Des = rl.GetInfoLong(); + var title = devMark + $"" + rl.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + string rlHex = Utils.GetRoleColorCode(rl); + if (Options.CustomRoleSpawnChances.ContainsKey(rl)) + { + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(rl.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + } + // Show role info + Utils.SendMessage(Des, playerId, title, noReplay: true); + + // Show role settings + Utils.SendMessage("", playerId, Conf.ToString(), noReplay: true); + return; + } + } + if (isUp) Utils.SendMessage(GetString("Message.YTPlanCanNotFindRoleThePlayerEnter"), playerId); + else Utils.SendMessage(GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); + return; + } + public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) + { + canceled = false; + if (!AmongUsClient.Instance.AmHost) return; + + if (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); + + if (text.StartsWith("\n")) text = text[1..]; + //if (!text.StartsWith("/")) return; + string[] args = text.Split(' '); + string subArgs = ""; + string subArgs2 = ""; + + //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; + // if (SpamManager.CheckSpam(player, text)) return; + if (GuessManager.GuesserMsg(player, text)) { canceled = true; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } + if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } + if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } + if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Councillor cl && cl.MurderMsg(player, text)) { canceled = true; Logger.Info($"Is Councillor command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Swapper sw && sw.SwapMsg(player, text)) { canceled = true; Logger.Info($"Is Swapper command", "OnReceiveChat"); return; } + if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } + if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } + if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } + + Directory.CreateDirectory(modTagsFiles); + Directory.CreateDirectory(vipTagsFiles); + Directory.CreateDirectory(sponsorTagsFiles); + + if (Blackmailer.CheckBlackmaile(player) && player.IsAlive()) + { + Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); + ChatManager.SendPreviousMessagesToAll(); + ChatManager.cancel = false; + canceled = true; + return; + } + + switch (args[0]) + { + case "/r": + case "/role": + case "/р": + case "/роль": + Logger.Info($"Command '/r' was activated", "OnReceiveChat"); + if (text.Contains("/role") || text.Contains("/роль")) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); + break; + + case "/m": + case "/myrole": + case "/minhafunção": + case "/м": case "/мояроль": case "/身份": case "/我": case "/我的身份": - case "/我的职业": - Logger.Info($"Command '/m' was activated", "OnReceiveChat"); - var role = player.GetCustomRole(); - if (GameStates.IsInGame) - { - var Des = player.GetRoleInfo(true); - var title = $"" + role.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); - var SubTitle = $"" + GetString("YourAddon") + "\n"; - - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(opt, ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles.ToArray()) - { - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); - - } - if (Sub.ToString() != string.Empty) - { - var ACleared = Sub.ToString().Remove(0, 2); - ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "": ACleared; - Sub.Clear().Append(ACleared); - } - - Utils.SendMessage(Des, player.PlayerId, title, noReplay: true); - Utils.SendMessage("", player.PlayerId, Conf.ToString(), noReplay: true); - if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), player.PlayerId, SubTitle, noReplay: true); - - Logger.Info($"Command '/m' should be send message", "OnReceiveChat"); - } - else - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - - case "/h": - case "/help": - case "/ajuda": - case "/хелп": - case "/хэлп": + case "/我的职业": + Logger.Info($"Command '/m' was activated", "OnReceiveChat"); + var role = player.GetCustomRole(); + if (GameStates.IsInGame) + { + var Des = player.GetRoleInfo(true); + var title = $"" + role.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + var Sub = new StringBuilder(); + var rlHex = Utils.GetRoleColorCode(role); + var SubTitle = $"" + GetString("YourAddon") + "\n"; + + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(opt, ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles.ToArray()) + { + Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + + } + if (Sub.ToString() != string.Empty) + { + var ACleared = Sub.ToString().Remove(0, 2); + ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "": ACleared; + Sub.Clear().Append(ACleared); + } + + Utils.SendMessage(Des, player.PlayerId, title, noReplay: true); + Utils.SendMessage("", player.PlayerId, Conf.ToString(), noReplay: true); + if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), player.PlayerId, SubTitle, noReplay: true); + + Logger.Info($"Command '/m' should be send message", "OnReceiveChat"); + } + else + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + + case "/h": + case "/help": + case "/ajuda": + case "/хелп": + case "/хэлп": case "/помощь": case "/帮助": - case "/教程": - Utils.ShowHelpToClient(player.PlayerId); - break; - - case "/ans": - case "/asw": + case "/教程": + Utils.ShowHelpToClient(player.PlayerId); + break; + + case "/ans": + case "/asw": case "/answer": - case "/回答": - Quizmaster.AnswerByChat(player, args); - break; - + case "/回答": + Quizmaster.AnswerByChat(player, args); + break; + case "/qmquiz": - case "/提问": - Quizmaster.ShowQuestion(player); - break; - - case "/l": - case "/lastresult": + case "/提问": + Quizmaster.ShowQuestion(player); + break; + + case "/l": + case "/lastresult": case "/fimdejogo": case "/上局信息": case "/信息": - case "/情况": - Utils.ShowKillLog(player.PlayerId); - Utils.ShowLastRoles(player.PlayerId); - Utils.ShowLastResult(player.PlayerId); - break; - - case "/gr": - case "/gameresults": + case "/情况": + Utils.ShowKillLog(player.PlayerId); + Utils.ShowLastRoles(player.PlayerId); + Utils.ShowLastResult(player.PlayerId); + break; + + case "/gr": + case "/gameresults": case "/resultados": case "/对局结果": case "/上局结果": - case "/结果": - Utils.ShowLastResult(player.PlayerId); - break; - - case "/kh": + case "/结果": + Utils.ShowLastResult(player.PlayerId); + break; + + case "/kh": case "/killlog": case "/击杀日志": - case "/击杀情况": - Utils.ShowKillLog(player.PlayerId); - break; - - case "/rs": - case "/sum": - case "/rolesummary": - case "/sumario": - case "/sumário": - case "/summary": + case "/击杀情况": + Utils.ShowKillLog(player.PlayerId); + break; + + case "/rs": + case "/sum": + case "/rolesummary": + case "/sumario": + case "/sumário": + case "/summary": case "/результат": case "/上局职业": case "/职业信息": - case "/对局职业": - Utils.ShowLastRoles(player.PlayerId); - break; - + case "/对局职业": + Utils.ShowLastRoles(player.PlayerId); + break; + case "/ghostinfo": case "/幽灵职业介绍": case "/鬼魂职业介绍": case "/幽灵职业": - case "/鬼魂职业": - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); - break; - - case "/apocinfo": + case "/鬼魂职业": + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); + break; + + case "/apocinfo": case "/apocalypseinfo": case "/末日中立职业介绍": case "/末日中立介绍": case "/末日类中立职业介绍": - case "/末日类中立介绍": - Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); - break; - - case "/rn": - case "/rename": - case "/renomear": + case "/末日类中立介绍": + Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + + case "/rn": + case "/rename": + case "/renomear": case "/переименовать": case "/重命名": - case "/命名为": - if (Options.PlayerCanSetName.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().NameCmd || Utils.IsPlayerVIP(player.FriendCode)) - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - if (args.Length < 1) break; - if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) - { - Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); - break; - } - Main.AllPlayerNames[player.PlayerId] = args.Skip(1).Join(delimiter: " "); - Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), player.PlayerId); - break; - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/n": - case "/now": + case "/命名为": + if (Options.PlayerCanSetName.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().NameCmd || Utils.IsPlayerVIP(player.FriendCode)) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + if (args.Length < 1) break; + if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) + { + Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); + break; + } + Main.AllPlayerNames[player.PlayerId] = args.Skip(1).Join(delimiter: " "); + Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), player.PlayerId); + break; + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/n": + case "/now": case "/atual": case "/设置": case "/系统设置": - case "/模组设置": - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "r": - case "roles": - case "funções": - case "职业": - case "角色": - Utils.ShowActiveRoles(player.PlayerId); - break; - case "a": - case "all": - case "tudo": - case "所有": - case "全部": - Utils.ShowAllActiveSettings(player.PlayerId); - break; - default: - Utils.ShowActiveSettings(player.PlayerId); - break; - } - break; - + case "/模组设置": + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + case "funções": + Utils.ShowActiveRoles(player.PlayerId); + break; + case "a": + case "all": + case "tudo": + Utils.ShowAllActiveSettings(player.PlayerId); + break; + default: + Utils.ShowActiveSettings(player.PlayerId); + break; + } + break; + case "/up": case "/指定": - case "/成为": - _ = text.Remove(0, 3); - if (!Options.EnableUpMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), player.PlayerId); - break; - } - else - { - Utils.SendMessage(GetString("Message.OnlyCanBeUsedByHost"), player.PlayerId); - break; - } - - case "/win": - case "/winner": + case "/成为": + _ = text.Remove(0, 3); + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), player.PlayerId); + break; + } + else + { + Utils.SendMessage(GetString("Message.OnlyCanBeUsedByHost"), player.PlayerId); + break; + } + + case "/win": + case "/winner": case "/vencedor": case "/胜利": case "/获胜": case "/赢": - if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), player.PlayerId); - else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList), player.PlayerId); - break; - - - case "/pv": - canceled = true; - if (!Pollvotes.Any()) - { - Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); - break; - } - if (PollVoted.Contains(player.PlayerId)) - { - Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); - break; - } - - subArgs = args.Length != 2 ? "" : args[1]; - char vote = ' '; - - if (int.TryParse(subArgs, out int integer) && (Pollvotes.Count - 1) >= integer) - { - vote = char.ToUpper((char)(integer + 65)); - } - else if (!(char.TryParse(subArgs, out vote) && Pollvotes.ContainsKey(char.ToUpper(vote)))) - { - Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); - break; - } - vote = char.ToUpper(vote); - - PollVoted.Add(player.PlayerId); - Pollvotes[vote]++; - Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, Pollvotes[vote]), player.PlayerId); - Logger.Info($"The new value of {vote} is {Pollvotes[vote]}", "TestPV_CHAR"); - - break; - - case "/icon": + case "/胜利者": + case "/获胜的人": + case "/赢家": + if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), player.PlayerId); + else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList), player.PlayerId); + break; + + + case "/pv": + canceled = true; + if (!Pollvotes.Any()) + { + Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); + break; + } + if (PollVoted.Contains(player.PlayerId)) + { + Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); + break; + } + + subArgs = args.Length != 2 ? "" : args[1]; + char vote = ' '; + + if (int.TryParse(subArgs, out int integer) && (Pollvotes.Count - 1) >= integer) + { + vote = char.ToUpper((char)(integer + 65)); + } + else if (!(char.TryParse(subArgs, out vote) && Pollvotes.ContainsKey(char.ToUpper(vote)))) + { + Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); + break; + } + vote = char.ToUpper(vote); + + PollVoted.Add(player.PlayerId); + Pollvotes[vote]++; + Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, Pollvotes[vote]), player.PlayerId); + Logger.Info($"The new value of {vote} is {Pollvotes[vote]}", "TestPV_CHAR"); + + break; + + case "/icon": case "/icons": case "/符号": - case "/标志": - { - Utils.SendMessage(GetString("Command.icons"), player.PlayerId, GetString("IconsTitle")); - break; - } - - case "/kc": - case "/kcount": - case "/количество": + case "/标志": + { + Utils.SendMessage(GetString("Command.icons"), player.PlayerId, GetString("IconsTitle")); + break; + } + + case "/kc": + case "/kcount": + case "/количество": case "/убийцы": case "/存活阵营": case "/阵营": case "/存货阵营信息": - case "/阵营信息": - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; - - var allAlivePlayers = Main.AllAlivePlayerControls; - int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); - int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); - int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); - int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - - var sub = new StringBuilder(); - sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); - - if (Options.ShowMadmatesInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); - - if (Options.ShowApocalypseInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); - - sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); - - Utils.SendMessage(sub.ToString(), player.PlayerId); - break; - - case "/d": - case "/death": - case "/morto": - case "/умер": + case "/阵营信息": + if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; + + var allAlivePlayers = Main.AllAlivePlayerControls; + int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); + int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + + var sub = new StringBuilder(); + sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); + + if (Options.ShowMadmatesInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); + + Utils.SendMessage(sub.ToString(), player.PlayerId); + break; + + case "/d": + case "/death": + case "/morto": + case "/умер": case "/причина": case "/死亡原因": - case "/死亡": - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - else if (player.IsAlive()) - { - Utils.SendMessage(GetString("DeathCmd.HeyPlayer") + "" + player.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), player.PlayerId); - break; - } - else - { - var killer = player.GetRealKiller(out var MurderRole); - string killerName = killer == null ? "N/A" : killer.GetRealName(); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); - break; - } - - case "/t": - case "/template": - case "/шаблон": + case "/死亡": + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + else if (player.IsAlive()) + { + Utils.SendMessage(GetString("DeathCmd.HeyPlayer") + "" + player.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Vote) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), player.PlayerId); + break; + } + else + { + var killer = player.GetRealKiller(out var MurderRole); + string killerName = killer == null ? "N/A" : killer.GetRealName(); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); + break; + } + + case "/t": + case "/template": + case "/шаблон": case "/пример": case "/模板": - case "/模板信息": - if (args.Length > 1) TemplateManager.SendTemplate(args[1], player.PlayerId); - else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); - break; - - case "/colour": - case "/color": - case "/cor": + case "/模板信息": + if (args.Length > 1) TemplateManager.SendTemplate(args[1], player.PlayerId); + else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); + break; + + case "/colour": + case "/color": + case "/cor": case "/цвет": case "/颜色": case "/更改颜色": case "/修改颜色": - case "/换颜色": - if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd || Utils.IsPlayerVIP(player.FriendCode)) - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - var color = Utils.MsgToColor(subArgs); - if (color == byte.MaxValue) - { - Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); - break; - } - player.RpcSetColor(color); - Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/quit": - case "/qt": + case "/换颜色": + if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd || Utils.IsPlayerVIP(player.FriendCode)) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + var color = Utils.MsgToColor(subArgs); + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); + break; + } + player.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/quit": + case "/qt": case "/sair": case "/退出": - case "/退": - if (Options.PlayerCanUseQuitCommand.GetBool()) - { - subArgs = args.Length < 2 ? "" : args[1]; - var cid = player.PlayerId.ToString(); - cid = cid.Length != 1 ? cid.Substring(1, 1) : cid; - if (subArgs.Equals(cid)) - { - string name = player.GetRealName(); - Utils.SendMessage(string.Format(GetString("Message.PlayerQuitForever"), name)); - AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); - } - else - { - Utils.SendMessage(string.Format(GetString("SureUse.quit"), cid), player.PlayerId); - } - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/id": + case "/退": + if (Options.PlayerCanUseQuitCommand.GetBool()) + { + subArgs = args.Length < 2 ? "" : args[1]; + var cid = player.PlayerId.ToString(); + cid = cid.Length != 1 ? cid.Substring(1, 1) : cid; + if (subArgs.Equals(cid)) + { + string name = player.GetRealName(); + Utils.SendMessage(string.Format(GetString("Message.PlayerQuitForever"), name)); + AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); + } + else + { + Utils.SendMessage(string.Format(GetString("SureUse.quit"), cid), player.PlayerId); + } + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/id": case "/айди": case "/编号": - case "/玩家编号": - if ((Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) - && !Options.EnableVoteCommand.GetBool()) break; - - string msgText = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText, player.PlayerId); - break; - + case "/玩家编号": + if ((Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + && !Options.EnableVoteCommand.GetBool()) break; + + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText, player.PlayerId); + break; + case "/mid": case "/玩家列表": case "/玩家信息": - case "/玩家编号列表": - //canceled = true; - //checking if modlist on or not - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("midCommandDisabled"), player.PlayerId); - break; - } - //checking if player is has necessary privellege or not - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("midCommandNoAccess"), player.PlayerId); - break; - } - string msgText1 = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText1, player.PlayerId); - break; - - case "/ban": - case "/banir": - case "/бан": + case "/玩家编号列表": + //canceled = true; + //checking if modlist on or not + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("midCommandDisabled"), player.PlayerId); + break; + } + //checking if player is has necessary privellege or not + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("midCommandNoAccess"), player.PlayerId); + break; + } + string msgText1 = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText1, player.PlayerId); + break; + + case "/ban": + case "/banir": + case "/бан": case "/забанить": - case "/封禁": - //canceled = true; - // Check if the ban command is enabled in the settings - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("BanCommandDisabled"), player.PlayerId); - break; - } - - // Check if the player has the necessary privileges to use the command - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("BanCommandNoAccess"), player.PlayerId); - break; - } - string banReason; - if (args.Length < 3) - { - Utils.SendMessage(GetString("BanCommandNoReason"), player.PlayerId); - break; - } - else - { - subArgs = args[1]; - banReason = string.Join(" ", args.Skip(2)); - } - //subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); - break; - } - - if (banPlayerId == 0) - { - Utils.SendMessage(GetString("BanCommandBanHost"), player.PlayerId); - break; - } - - var bannedPlayer = Utils.GetPlayerById(banPlayerId); - if (bannedPlayer == null) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from baning other moderators - if (Utils.IsPlayerModerator(bannedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("BanCommandBanMod"), player.PlayerId); - break; - } - - // Ban the specified player - AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); - string bannedPlayerName = bannedPlayer.GetRealName(); - string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{player.name} \nReason: {banReason}\n"; - if (GameStates.IsInGame) - { - textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend1); - //string moderatorName = player.GetRealName().ToString(); - //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; - //moderatorName = moderatorName.Substring(startIndex); - //string extractedString = - string modLogname = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n1) ? n1 : ""; - string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; - string moderatorFriendCode = player.FriendCode.ToString(); - string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); - string bannedPlayerHashPuid = bannedPlayer.GetClient().GetHashedPuid(); - string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{bannedPlayerHashPuid},{banlogname} Reason: {banReason}"; - File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); - break; - - case "/warn": - case "/aviso": - case "/варн": - case "/пред": + case "/封禁": + //canceled = true; + // Check if the ban command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("BanCommandDisabled"), player.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("BanCommandNoAccess"), player.PlayerId); + break; + } + string banReason; + if (args.Length < 3) + { + Utils.SendMessage(GetString("BanCommandNoReason"), player.PlayerId); + break; + } + else + { + subArgs = args[1]; + banReason = string.Join(" ", args.Skip(2)); + } + //subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); + break; + } + + if (banPlayerId == 0) + { + Utils.SendMessage(GetString("BanCommandBanHost"), player.PlayerId); + break; + } + + var bannedPlayer = Utils.GetPlayerById(banPlayerId); + if (bannedPlayer == null) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from baning other moderators + if (Utils.IsPlayerModerator(bannedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("BanCommandBanMod"), player.PlayerId); + break; + } + + // Ban the specified player + AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); + string bannedPlayerName = bannedPlayer.GetRealName(); + string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{player.name} \nReason: {banReason}\n"; + if (GameStates.IsInGame) + { + textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend1); + //string moderatorName = player.GetRealName().ToString(); + //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; + //moderatorName = moderatorName.Substring(startIndex); + //string extractedString = + string modLogname = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n1) ? n1 : ""; + string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; + string moderatorFriendCode = player.FriendCode.ToString(); + string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); + string bannedPlayerHashPuid = bannedPlayer.GetClient().GetHashedPuid(); + string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{bannedPlayerHashPuid},{banlogname} Reason: {banReason}"; + File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); + break; + + case "/warn": + case "/aviso": + case "/варн": + case "/пред": case "/предупредить": case "/警告": - case "/提醒": - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("WarnCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("WarnCommandNoAccess"), player.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); - break; - } - if (warnPlayerId == 0) - { - Utils.SendMessage(GetString("WarnCommandWarnHost"), player.PlayerId); - break; - } - - var warnedPlayer = Utils.GetPlayerById(warnPlayerId); - if (warnedPlayer == null) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from warning other moderators - if (Utils.IsPlayerModerator(warnedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("WarnCommandWarnMod"), player.PlayerId); - break; - } - // warn the specified player - string warnReason = "Reason : Not specified\n"; - string warnedPlayerName = warnedPlayer.GetRealName(); - //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; - if (args.Length > 2) - { - warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - } - else - { - Utils.SendMessage("Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", player.PlayerId); - } - Utils.SendMessage($" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{player.name}"); - //string moderatorName1 = player.GetRealName().ToString(); - //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; - //moderatorName1 = moderatorName1.Substring(startIndex1); - string modLogname1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n2) ? n2 : ""; - string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; - string moderatorFriendCode1 = player.FriendCode.ToString(); - string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); - string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); - string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; - File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); - - break; - case "/kick": - case "/expulsar": - case "/кик": - case "/кикнуть": + case "/提醒": + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("WarnCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("WarnCommandNoAccess"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); + break; + } + if (warnPlayerId == 0) + { + Utils.SendMessage(GetString("WarnCommandWarnHost"), player.PlayerId); + break; + } + + var warnedPlayer = Utils.GetPlayerById(warnPlayerId); + if (warnedPlayer == null) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from warning other moderators + if (Utils.IsPlayerModerator(warnedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("WarnCommandWarnMod"), player.PlayerId); + break; + } + // warn the specified player + string warnReason = "Reason : Not specified\n"; + string warnedPlayerName = warnedPlayer.GetRealName(); + //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; + if (args.Length > 2) + { + warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + } + else + { + Utils.SendMessage("Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", player.PlayerId); + } + Utils.SendMessage($" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{player.name}"); + //string moderatorName1 = player.GetRealName().ToString(); + //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; + //moderatorName1 = moderatorName1.Substring(startIndex1); + string modLogname1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n2) ? n2 : ""; + string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; + string moderatorFriendCode1 = player.FriendCode.ToString(); + string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); + string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); + string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; + File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); + + break; + case "/kick": + case "/expulsar": + case "/кик": + case "/кикнуть": case "/выгнать": case "/踢出": - case "/踢": - // Check if the kick command is enabled in the settings - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); - break; - } - - // Check if the player has the necessary privileges to use the command - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); - break; - } - - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); - break; - } - - if (kickPlayerId == 0) - { - Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); - break; - } - - var kickedPlayer = Utils.GetPlayerById(kickPlayerId); - if (kickedPlayer == null) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from kicking other moderators - if (Utils.IsPlayerModerator(kickedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); - break; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); - string kickedPlayerName = kickedPlayer.GetRealName(); - string kickReason = "Reason : Not specified\n"; - if (args.Length > 2) - kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - else - { - Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", player.PlayerId); - } - string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {player.name} \n {kickReason}"; - - if (GameStates.IsInGame) - { - textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend); - //string moderatorName2 = player.GetRealName().ToString(); - //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; - //moderatorName2 = moderatorName2.Substring(startIndex2); - string modLogname2 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n3) ? n3 : ""; - string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; - - string moderatorFriendCode2 = player.FriendCode.ToString(); - string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); - string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); - string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; - File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); - - break; - case "/modcolor": + case "/踢": + // Check if the kick command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); + break; + } + + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + break; + } + + if (kickPlayerId == 0) + { + Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); + break; + } + + var kickedPlayer = Utils.GetPlayerById(kickPlayerId); + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from kicking other moderators + if (Utils.IsPlayerModerator(kickedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); + string kickedPlayerName = kickedPlayer.GetRealName(); + string kickReason = "Reason : Not specified\n"; + if (args.Length > 2) + kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + else + { + Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", player.PlayerId); + } + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {player.name} \n {kickReason}"; + + if (GameStates.IsInGame) + { + textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + //string moderatorName2 = player.GetRealName().ToString(); + //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; + //moderatorName2 = moderatorName2.Substring(startIndex2); + string modLogname2 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n3) ? n3 : ""; + string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; + + string moderatorFriendCode2 = player.FriendCode.ToString(); + string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); + string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); + string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; + File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); + + break; + case "/modcolor": case "/modcolour": case "/模组端颜色": - case "/模组颜色": - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("ColorCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("ColorCommandNoAccess"), player.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); - break; - } - if (!Options.GradientTagsOpt.GetBool()) - { - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "modcolor"); - Utils.SendMessage(GetString("ColorInvalidHexCode"), player.PlayerId); - break; - } - string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePath)) - { - Logger.Warn($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - File.Create(colorFilePath).Close(); - } - - File.WriteAllText(colorFilePath, $"{subArgs}"); - break; - } - else - { - subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; - Regex regex = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); - if (string.IsNullOrEmpty(subArgs) || !regex.IsMatch(subArgs)) - { - Logger.Msg($"{subArgs}", "modcolor"); - Utils.SendMessage(GetString("ColorInvalidGradientCode"), player.PlayerId); - break; - } - string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - File.Create(colorFilePath).Close(); - } - //Logger.Msg($"File exists, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - //Logger.Msg($"{subArgs}","modcolor"); - File.WriteAllText(colorFilePath, $"{subArgs}"); - break; - } - case "/vipcolor": + case "/模组颜色": + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("ColorCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("ColorCommandNoAccess"), player.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); + break; + } + if (!Options.GradientTagsOpt.GetBool()) + { + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "modcolor"); + Utils.SendMessage(GetString("ColorInvalidHexCode"), player.PlayerId); + break; + } + string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePath)) + { + Logger.Warn($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + File.Create(colorFilePath).Close(); + } + + File.WriteAllText(colorFilePath, $"{subArgs}"); + break; + } + else + { + subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; + Regex regex = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); + if (string.IsNullOrEmpty(subArgs) || !regex.IsMatch(subArgs)) + { + Logger.Msg($"{subArgs}", "modcolor"); + Utils.SendMessage(GetString("ColorInvalidGradientCode"), player.PlayerId); + break; + } + string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + File.Create(colorFilePath).Close(); + } + //Logger.Msg($"File exists, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + //Logger.Msg($"{subArgs}","modcolor"); + File.WriteAllText(colorFilePath, $"{subArgs}"); + break; + } + case "/vipcolor": case "/vipcolour": case "/VIP玩家颜色": - case "/VIP颜色": - if (Options.ApplyVipList.GetValue() == 0) - { - Utils.SendMessage(GetString("VipColorCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerVIP(player.FriendCode)) - { - Utils.SendMessage(GetString("VipColorCommandNoAccess"), player.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("VipColorCommandNoLobby"), player.PlayerId); - break; - } - if (!Options.GradientTagsOpt.GetBool()) - { - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "vipcolor"); - Utils.SendMessage(GetString("VipColorInvalidHexCode"), player.PlayerId); - break; - } - string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePathh)) - { - Logger.Warn($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - File.Create(colorFilePathh).Close(); - } - - File.WriteAllText(colorFilePathh, $"{subArgs}"); - break; - } - else - { - subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; - Regex regexx = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); - if (string.IsNullOrEmpty(subArgs) || !regexx.IsMatch(subArgs)) - { - Logger.Msg($"{subArgs}", "vipcolor"); - Utils.SendMessage(GetString("VipColorInvalidGradientCode"), player.PlayerId); - break; - } - string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePathh)) - { - Logger.Msg($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - File.Create(colorFilePathh).Close(); - } - //Logger.Msg($"File exists, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - //Logger.Msg($"{subArgs}","modcolor"); - File.WriteAllText(colorFilePathh, $"{subArgs}"); - break; - } - case "/tagcolor": + case "/VIP颜色": + if (Options.ApplyVipList.GetValue() == 0) + { + Utils.SendMessage(GetString("VipColorCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerVIP(player.FriendCode)) + { + Utils.SendMessage(GetString("VipColorCommandNoAccess"), player.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("VipColorCommandNoLobby"), player.PlayerId); + break; + } + if (!Options.GradientTagsOpt.GetBool()) + { + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "vipcolor"); + Utils.SendMessage(GetString("VipColorInvalidHexCode"), player.PlayerId); + break; + } + string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePathh)) + { + Logger.Warn($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + File.Create(colorFilePathh).Close(); + } + + File.WriteAllText(colorFilePathh, $"{subArgs}"); + break; + } + else + { + subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; + Regex regexx = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); + if (string.IsNullOrEmpty(subArgs) || !regexx.IsMatch(subArgs)) + { + Logger.Msg($"{subArgs}", "vipcolor"); + Utils.SendMessage(GetString("VipColorInvalidGradientCode"), player.PlayerId); + break; + } + string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePathh)) + { + Logger.Msg($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + File.Create(colorFilePathh).Close(); + } + //Logger.Msg($"File exists, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + //Logger.Msg($"{subArgs}","modcolor"); + File.WriteAllText(colorFilePathh, $"{subArgs}"); + break; + } + case "/tagcolor": case "/tagcolour": case "/标签颜色": - case "/附加名称颜色": - string name1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; - if (name1 == "") break; - if (!name1.Contains('\r') && player.FriendCode.GetDevUser().HasTag()) - { - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "tagcolor"); - Utils.SendMessage(GetString("TagColorInvalidHexCode"), player.PlayerId); - break; - } - string tagColorFilePath = $"{sponsorTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(tagColorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); - File.Create(tagColorFilePath).Close(); - } - - File.WriteAllText(tagColorFilePath, $"{subArgs}"); - } - break; - + case "/附加名称颜色": + string name1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; + if (name1 == "") break; + if (!name1.Contains('\r') && player.FriendCode.GetDevUser().HasTag()) + { + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "tagcolor"); + Utils.SendMessage(GetString("TagColorInvalidHexCode"), player.PlayerId); + break; + } + string tagColorFilePath = $"{sponsorTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(tagColorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); + File.Create(tagColorFilePath).Close(); + } + + File.WriteAllText(tagColorFilePath, $"{subArgs}"); + } + break; + case "/xf": case "/修复": - case "/修": - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - foreach (var pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) continue; - - pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); - } - ChatUpdatePatch.DoBlockChat = false; - //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); - Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); - break; - + case "/修": + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) continue; + + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); + break; + case "/tpout": case "/传送出": - case "/传出": - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - player.RpcTeleport(new Vector2(0.1f, 3.8f)); - break; + case "/传出": + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + player.RpcTeleport(new Vector2(0.1f, 3.8f)); + break; case "/tpin": case "/传进": - case "/传送进": - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - player.RpcTeleport(new Vector2(-0.2f, 1.3f)); - break; - + case "/传送进": + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + player.RpcTeleport(new Vector2(-0.2f, 1.3f)); + break; + case "/vote": case "/投票": - case "/票": - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int arg)) - break; - var plr = Utils.GetPlayerById(arg); - - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - - - if (!Options.EnableVoteCommand.GetBool()) - { - Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); - break; - } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) - { - canceled = true; - ChatManager.SendPreviousMessagesToAll(); - } - - if (arg != 253) // skip - { - if (plr == null || !plr.IsAlive()) - { - Utils.SendMessage(GetString("VoteDead"), player.PlayerId); - break; - } - } - if (!player.IsAlive()) - { - Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); - break; - } - if (GameStates.IsMeeting) - { - player.RpcCastVote((byte)arg); - } - break; - - case "/say": - case "/s": - case "/с": + case "/票": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + + + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + ChatManager.SendPreviousMessagesToAll(); + } + + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), player.PlayerId); + break; + } + } + if (!player.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + player.RpcCastVote((byte)arg); + } + break; + + case "/say": + case "/s": + case "/с": case "/сказать": - case "/说": - if (player.FriendCode.GetDevUser().IsDev) - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")} ~ {player.GetRealName(clientData: true)}"); - } - else if (player.FriendCode.IsDevUser() && !dbConnect.IsBooster(player.FriendCode)) - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")} ~ {player.GetRealName(clientData: true)}"); - } - else if (Utils.IsPlayerModerator(player.FriendCode)) - { - if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowSayCommand.GetBool() == false) - { - Utils.SendMessage(GetString("SayCommandDisabled"), player.PlayerId); - break; - } - else - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromModerator")} ~ {player.GetRealName(clientData: true)}"); - //string moderatorName3 = player.GetRealName().ToString(); - //int startIndex3 = moderatorName3.IndexOf("♥") + "♥".Length; - //moderatorName3 = moderatorName3.Substring(startIndex3); - string modLogname3 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n4) ? n4 : ""; - - string moderatorFriendCode3 = player.FriendCode.ToString(); - string logMessage3 = $"[{DateTime.Now}] {moderatorFriendCode3},{modLogname3} used /s: {args.Skip(1).Join(delimiter: " ")}"; - File.AppendAllText(modLogFiles, logMessage3 + Environment.NewLine); - - } - } - break; + case "/说": + if (player.FriendCode.GetDevUser().IsDev) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")} ~ {player.GetRealName(clientData: true)}"); + } + else if (player.FriendCode.IsDevUser() && !dbConnect.IsBooster(player.FriendCode)) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")} ~ {player.GetRealName(clientData: true)}"); + } + else if (Utils.IsPlayerModerator(player.FriendCode)) + { + if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowSayCommand.GetBool() == false) + { + Utils.SendMessage(GetString("SayCommandDisabled"), player.PlayerId); + break; + } + else + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromModerator")} ~ {player.GetRealName(clientData: true)}"); + //string moderatorName3 = player.GetRealName().ToString(); + //int startIndex3 = moderatorName3.IndexOf("♥") + "♥".Length; + //moderatorName3 = moderatorName3.Substring(startIndex3); + string modLogname3 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n4) ? n4 : ""; + + string moderatorFriendCode3 = player.FriendCode.ToString(); + string logMessage3 = $"[{DateTime.Now}] {moderatorFriendCode3},{modLogname3} used /s: {args.Skip(1).Join(delimiter: " ")}"; + File.AppendAllText(modLogFiles, logMessage3 + Environment.NewLine); + + } + } + break; case "/rps": - case "/剪刀石头布": - //canceled = true; - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - else if (playerChoice < 0 || playerChoice > 2) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(0, 3); - var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; - if (botChoice == playerChoice) - { - Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), player.PlayerId); - } - else if ((botChoice == 0 && playerChoice == 2) || - (botChoice == 1 && playerChoice == 0) || - (botChoice == 2 && playerChoice == 1)) - { - Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), player.PlayerId); - } - else - { - Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), player.PlayerId); - } - break; - } + case "/剪刀石头布": + //canceled = true; + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + else if (playerChoice < 0 || playerChoice > 2) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(0, 3); + var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; + if (botChoice == playerChoice) + { + Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), player.PlayerId); + } + else if ((botChoice == 0 && playerChoice == 2) || + (botChoice == 1 && playerChoice == 0) || + (botChoice == 2 && playerChoice == 1)) + { + Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), player.PlayerId); + } + else + { + Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), player.PlayerId); + } + break; + } case "/coinflip": - case "/抛硬币": - //canceled = true; - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("CoinflipCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(1,101); - var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); - Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), player.PlayerId); - break; - } + case "/抛硬币": + //canceled = true; + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("CoinflipCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(1,101); + var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); + Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), player.PlayerId); + break; + } case "/gno": - case "/猜数字": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - //canceled = true; - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - else if (guessedNo < 0 || guessedNo > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - else - { - int targetNumber = Main.GuessNumber[player.PlayerId][0]; - if (Main.GuessNumber[player.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[player.PlayerId][0]; - } - Main.GuessNumber[player.PlayerId][1]--; - if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - //targetNumber = Main.GuessNumber[player.PlayerId][0]; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); - break; - } - else if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - break; - } - else if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - break; - } - else - { - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - break; - } - } + case "/猜数字": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + //canceled = true; + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + else if (guessedNo < 0 || guessedNo > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + else + { + int targetNumber = Main.GuessNumber[player.PlayerId][0]; + if (Main.GuessNumber[player.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[player.PlayerId][0]; + } + Main.GuessNumber[player.PlayerId][1]--; + if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) + { + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + //targetNumber = Main.GuessNumber[player.PlayerId][0]; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); + break; + } + else if (guessedNo < targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + break; + } + else if (guessedNo > targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + break; + } + else + { + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + break; + } + } case "/rand": case "/XY数字": case "/范围游戏": case "/猜范围": - case "/范围": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - subArgs = args.Length != 3 ? "" : args[1]; - subArgs2 = args.Length != 3 ? "" : args[2]; - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); - break; - } - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) - { - Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botResult = rand.Next(playerChoice1, playerChoice2 + 1); - Utils.SendMessage(string.Format(GetString("RandResult"), botResult), player.PlayerId); - break; - } + case "/范围": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + subArgs = args.Length != 3 ? "" : args[1]; + subArgs2 = args.Length != 3 ? "" : args[2]; + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); + break; + } + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) + { + Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botResult = rand.Next(playerChoice1, playerChoice2 + 1); + Utils.SendMessage(string.Format(GetString("RandResult"), botResult), player.PlayerId); + break; + } case "/8ball": case "/8号球": - case "/幸运球": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - canceled = true; - var rando = IRandom.Instance; - int result = rando.Next(0, 16); - string str = ""; - switch (result) - { - case 0: - str = GetString("8BallYes"); - break; - case 1: - str = GetString("8BallNo"); - break; - case 2: - str = GetString("8BallMaybe"); - break; - case 3: - str = GetString("8BallTryAgainLater"); - break; - case 4: - str = GetString("8BallCertain"); - break; - case 5: - str = GetString("8BallNotLikely"); - break; - case 6: - str = GetString("8BallLikely"); - break; - case 7: - str = GetString("8BallDontCount"); - break; - case 8: - str = GetString("8BallStop"); - break; - case 9: - str = GetString("8BallPossibly"); - break; - case 10: - str = GetString("8BallProbably"); - break; - case 11: - str = GetString("8BallProbablyNot"); - break; - case 12: - str = GetString("8BallBetterNotTell"); - break; - case 13: - str = GetString("8BallCantPredict"); - break; - case 14: - str = GetString("8BallWithoutDoubt"); - break; - case 15: - str = GetString("8BallWithDoubt"); - break; - } - Utils.SendMessage("" + str + "", player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); - break; + case "/幸运球": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + canceled = true; + var rando = IRandom.Instance; + int result = rando.Next(0, 16); + string str = ""; + switch (result) + { + case 0: + str = GetString("8BallYes"); + break; + case 1: + str = GetString("8BallNo"); + break; + case 2: + str = GetString("8BallMaybe"); + break; + case 3: + str = GetString("8BallTryAgainLater"); + break; + case 4: + str = GetString("8BallCertain"); + break; + case 5: + str = GetString("8BallNotLikely"); + break; + case 6: + str = GetString("8BallLikely"); + break; + case 7: + str = GetString("8BallDontCount"); + break; + case 8: + str = GetString("8BallStop"); + break; + case 9: + str = GetString("8BallPossibly"); + break; + case 10: + str = GetString("8BallProbably"); + break; + case 11: + str = GetString("8BallProbablyNot"); + break; + case 12: + str = GetString("8BallBetterNotTell"); + break; + case 13: + str = GetString("8BallCantPredict"); + break; + case 14: + str = GetString("8BallWithoutDoubt"); + break; + case 15: + str = GetString("8BallWithDoubt"); + break; + } + Utils.SendMessage("" + str + "", player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); + break; case "/me": - - string Devbox = player.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; - string UpBox = player.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; - string ColorBox = player.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; - - subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); - if (string.IsNullOrEmpty(subArgs)) - { - Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), player.PlayerId, player.GetRealName(clientData: true), player.GetClient().FriendCode, player.GetClient().GetHashedPuid(), player.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); - } - else - { - if (Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("Message.MeCommandNoPermission"), player.PlayerId); - break; - } - - - - if (byte.TryParse(subArgs, out byte meid)) - { - if (meid != player.PlayerId) - { - var targetplayer = Utils.GetPlayerById(meid); - if (targetplayer != null && targetplayer.GetClient() != null) - { - Utils.SendMessage($"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}", player.PlayerId); - } - else - { - Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); - } - } - else - { - Utils.SendMessage($"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); - } - } - else - { - Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); - } - } - break; - - - default: - if (SpamManager.CheckSpam(player, text)) return; - break; - } - } -} -[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] -class ChatUpdatePatch -{ - public static bool DoBlockChat = false; - public static ChatController Instance; - public static void Postfix(ChatController __instance) - { - if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; - if (DoBlockChat) return; - - Instance ??= __instance; - - if (Main.DarkTheme.Value) - { - var chatBubble = __instance.chatBubblePool.Prefab.Cast(); - chatBubble.TextArea.overrideColorTags = false; - chatBubble.TextArea.color = Color.white; - chatBubble.Background.color = Color.black; - } - - var player = PlayerControl.LocalPlayer; - if (GameStates.IsInGame || player.Data.IsDead) - { - player = Main.AllAlivePlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() - ?? Main.AllPlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() - ?? player; - } - //Logger.Info($"player is null? {player == null}", "ChatUpdatePatch"); - if (player == null) return; - - (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; - //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); - - if (sendTo != byte.MaxValue && GameStates.IsLobby) - { - var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); - if (networkedPlayerInfo != null) - { - if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) - { - var delaymessage = Main.MessagesToSend[0]; - Main.MessagesToSend.RemoveAt(0); - Main.MessagesToSend.Add(delaymessage); - return; - } - // green beans color id is -1 - } - // It is impossible to get null player here unless it quits - } - Main.MessagesToSend.RemoveAt(0); - - int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); - var name = player.Data.PlayerName; - - //__instance.freeChatField.textArea.characterLimit = 999; - - if (clientId == -1) - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); - player.SetName(name); - } - - - var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); - writer.StartMessage(clientId); - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(title) - .EndRpc(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) - .Write(msg) - .EndRpc(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(player.Data.PlayerName) - .EndRpc(); - writer.EndMessage(); - writer.SendMessage(); - - __instance.timeSinceLastMessage = 0f; - } -} -[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] -internal class UpdateCharCountPatch -{ - public static void Postfix(FreeChatInputField __instance) - { - int length = __instance.textArea.text.Length; - __instance.charCountText.SetText($"{length}/{__instance.textArea.characterLimit}"); - if (length < (AmongUsClient.Instance.AmHost ? 888 : 250)) - __instance.charCountText.color = Color.black; - else if (length < (AmongUsClient.Instance.AmHost ? 999 : 300)) - __instance.charCountText.color = new Color(1f, 1f, 0f, 1f); - else - __instance.charCountText.color = Color.red; - } -} -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] -class RpcSendChatPatch -{ - public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) - { - if (string.IsNullOrWhiteSpace(chatText)) - { - __result = false; - return false; - } - if (!GameStates.IsModHost) - { - __result = false; - return true; - } - int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); - chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); - if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) - DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); - if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) - DestroyableSingleton.Instance.SendWho(); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); - messageWriter.Write(chatText); - messageWriter.EndMessage(); - __result = true; - return false; - } -} + case "/我的权限": + case "/权限": + + string Devbox = player.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; + string UpBox = player.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; + string ColorBox = player.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; + + subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); + if (string.IsNullOrEmpty(subArgs)) + { + Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), player.PlayerId, player.GetRealName(clientData: true), player.GetClient().FriendCode, player.GetClient().GetHashedPuid(), player.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); + } + else + { + if (Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("Message.MeCommandNoPermission"), player.PlayerId); + break; + } + + + + if (byte.TryParse(subArgs, out byte meid)) + { + if (meid != player.PlayerId) + { + var targetplayer = Utils.GetPlayerById(meid); + if (targetplayer != null && targetplayer.GetClient() != null) + { + Utils.SendMessage($"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}", player.PlayerId); + } + else + { + Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); + } + } + else + { + Utils.SendMessage($"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); + } + } + else + { + Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); + } + } + break; + + + default: + if (SpamManager.CheckSpam(player, text)) return; + break; + } + } +} +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +class ChatUpdatePatch +{ + public static bool DoBlockChat = false; + public static ChatController Instance; + public static void Postfix(ChatController __instance) + { + if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; + if (DoBlockChat) return; + + Instance ??= __instance; + + if (Main.DarkTheme.Value) + { + var chatBubble = __instance.chatBubblePool.Prefab.Cast(); + chatBubble.TextArea.overrideColorTags = false; + chatBubble.TextArea.color = Color.white; + chatBubble.Background.color = Color.black; + } + + var player = PlayerControl.LocalPlayer; + if (GameStates.IsInGame || player.Data.IsDead) + { + player = Main.AllAlivePlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() + ?? Main.AllPlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() + ?? player; + } + //Logger.Info($"player is null? {player == null}", "ChatUpdatePatch"); + if (player == null) return; + + (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; + //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); + + if (sendTo != byte.MaxValue && GameStates.IsLobby) + { + var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); + if (networkedPlayerInfo != null) + { + if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) + { + var delaymessage = Main.MessagesToSend[0]; + Main.MessagesToSend.RemoveAt(0); + Main.MessagesToSend.Add(delaymessage); + return; + } + // green beans color id is -1 + } + // It is impossible to get null player here unless it quits + } + Main.MessagesToSend.RemoveAt(0); + + int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); + var name = player.Data.PlayerName; + + //__instance.freeChatField.textArea.characterLimit = 999; + + if (clientId == -1) + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); + player.SetName(name); + } + + + var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); + writer.StartMessage(clientId); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(title) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(player.Data.PlayerName) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + + __instance.timeSinceLastMessage = 0f; + } +} +[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] +internal class UpdateCharCountPatch +{ + public static void Postfix(FreeChatInputField __instance) + { + int length = __instance.textArea.text.Length; + __instance.charCountText.SetText($"{length}/{__instance.textArea.characterLimit}"); + if (length < (AmongUsClient.Instance.AmHost ? 888 : 250)) + __instance.charCountText.color = Color.black; + else if (length < (AmongUsClient.Instance.AmHost ? 999 : 300)) + __instance.charCountText.color = new Color(1f, 1f, 0f, 1f); + else + __instance.charCountText.color = Color.red; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] +class RpcSendChatPatch +{ + public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) + { + if (string.IsNullOrWhiteSpace(chatText)) + { + __result = false; + return false; + } + if (!GameStates.IsModHost) + { + __result = false; + return true; + } + int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); + chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); + if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) + DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); + if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) + DestroyableSingleton.Instance.SendWho(); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); + messageWriter.Write(chatText); + messageWriter.EndMessage(); + __result = true; + return false; + } +} From ddf6a7a0a4cad83c25738602fdb6c5ece509dc5e Mon Sep 17 00:00:00 2001 From: hdhdh djiri Date: Fri, 4 Oct 2024 21:26:33 +0800 Subject: [PATCH 703/778] Fix Some situation killing bait would send log and play sound when bait is not reported --- Roles/AddOns/Common/Bait.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Roles/AddOns/Common/Bait.cs b/Roles/AddOns/Common/Bait.cs index a885c438f9..3184cf1f1e 100644 --- a/Roles/AddOns/Common/Bait.cs +++ b/Roles/AddOns/Common/Bait.cs @@ -77,6 +77,8 @@ public static void BaitAfterDeathTasks(PlayerControl killer, PlayerControl targe if (killer.Is(CustomRoles.KillingMachine) || killer.Is(CustomRoles.Swooper) || killer.Is(CustomRoles.Wraith) + || killer.Is(CustomRoles.Cleaner) + || (Options.DisableReportWhenCC.GetBool() && Utils.IsActive(SystemTypes.Comms) && Camouflage.IsActive && !Bait.BaitCanBeReportedUnderAllConditions.GetBool()) || (killer.Is(CustomRoles.Oblivious) && Oblivious.ObliviousBaitImmune.GetBool())) return; From 52113f244c50774b1fae80bcc3f0dd8b3e37ce61 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 4 Oct 2024 23:03:26 +0800 Subject: [PATCH 704/778] Fix Huntsman targets when he dead & Fix cancel start cooldown in game --- Patches/ControlPatch.cs | 2 +- Roles/Neutral/Huntsman.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 0d80d79a14..f1e1c93b14 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -258,7 +258,7 @@ public static void Postfix(/*ControllerManager __instance*/) } // Cancel start count down - if (Input.GetKeyDown(KeyCode.C) && GameStates.IsCountDown) + if (Input.GetKeyDown(KeyCode.C) && GameStates.IsCountDown && GameStates.IsLobby) { Logger.Info("Reset Countdown", "KeyCommand"); GameStartManager.Instance.ResetStartState(); diff --git a/Roles/Neutral/Huntsman.cs b/Roles/Neutral/Huntsman.cs index d3bb707309..7a24cdaf00 100644 --- a/Roles/Neutral/Huntsman.cs +++ b/Roles/Neutral/Huntsman.cs @@ -27,6 +27,7 @@ internal class Huntsman : RoleBase private static OptionItem MinKCD; private static OptionItem MaxKCD; + private bool IsDead = false; private readonly HashSet Targets = []; private float KCD = 25; @@ -52,6 +53,7 @@ public override void SetupCustomOption() public override void Add(byte playerId) { KCD = KillCooldown.GetFloat(); + IsDead = false; _ = new LateTask(() => { @@ -99,13 +101,19 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } return true; } + public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) + { + Targets.Clear(); + SendRPC(isSetTarget: false); + IsDead = true; + } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KCD; public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override string GetLowerText(PlayerControl player, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - if (isForMeeting) return string.Empty; + if (isForMeeting || IsDead) return string.Empty; var targetId = player.PlayerId; string output = string.Empty; @@ -120,7 +128,7 @@ public override string GetLowerText(PlayerControl player, PlayerControl seen = n } private void ResetTargets(bool isStartedGame = false) { - if (!AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost || IsDead) return; Targets.Clear(); SendRPC(isSetTarget: false); @@ -152,5 +160,5 @@ private void ResetTargets(bool isStartedGame = false) } public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl target) - => Targets.Contains(target.PlayerId) ? "6e5524" : string.Empty; + => !IsDead && Targets.Contains(target.PlayerId) ? "6e5524" : string.Empty; } From 8666a052dcb94f84bde1bd5b4270cdf7d32a7a2f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 4 Oct 2024 23:13:24 +0800 Subject: [PATCH 705/778] PluginVersion --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index 062dac3120..3d017f6941 100644 --- a/main.cs +++ b/main.cs @@ -42,7 +42,7 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1003.210.010000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginVersion = "2024.1004.210.010000"; // YEAR.MMDD.VERSION.CANARYDEV public const string PluginDisplayVersion = "2.1.0 Beta 1"; public const string SupportedVersionAU = "2024.8.13"; From d92bec5b242a2be4ded1b3790a236fa1dfc9c899 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 17:39:34 +0800 Subject: [PATCH 706/778] Lot of fixes for Phantom --- Modules/ExtendedPlayerControl.cs | 14 +++++ Modules/Utils.cs | 1 + Patches/PhantomRolePatch.cs | 96 ++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 29 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 961e99eaee..5f43dc35ec 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -254,6 +254,20 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis"); } + public static void RpcSetPetDesync(this PlayerControl player, string petId, PlayerControl seer) + { + var clientId = seer.GetClientId(); + if (clientId == -1) return; + if (AmongUsClient.Instance.ClientId == clientId) + { + player.SetPet(petId); + return; + } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetPetStr, SendOption.Reliable, clientId); + writer.Write(petId); + writer.Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } public static void RpcExile(this PlayerControl player) { player.Exiled(); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index a9c495c636..2188fe121a 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2358,6 +2358,7 @@ public static void AfterMeetingTasks() { try { + PhantomRolePatch.AfterMeeting(); ChatManager.ClearLastSysMsg(); FallFromLadder.Reset(); diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index fcc9cdaf9f..c356850d51 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -2,6 +2,7 @@ using AmongUs.GameOptions; using Il2CppInterop.Runtime.InteropTypes.Arrays; using TOHE.Roles.Core; +using UnityEngine; namespace TOHE.Patches; @@ -9,6 +10,7 @@ namespace TOHE.Patches; public static class PhantomRolePatch { private static readonly Il2CppSystem.Collections.Generic.List InvisibilityList = new(); + private static readonly Dictionary PetsList = []; /* * InnerSloth is doing careless stuffs. They didnt put amModdedHost check in cmd check vanish appear @@ -62,9 +64,16 @@ private static void CheckVanish_Prefix(PlayerControl __instance) _ = new LateTask(() => { - if (!Main.MeetingIsStarted) - phantom?.RpcExileDesync(target); - }, 1.2f, "Set Phantom invisible", shoudLog: false); + if (Main.MeetingIsStarted || phantom == null) return; + + var petId = phantom.Data.DefaultOutfit.PetId; + if (petId != "") + { + PetsList[phantom.PlayerId] = petId; + phantom?.RpcSetPetDesync("", target); + } + phantom?.RpcExileDesync(target); + }, 1.2f, $"Set Phantom invisible {target.PlayerId}", shoudLog: false); } InvisibilityList.Add(phantom); } @@ -77,7 +86,7 @@ private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnim var phantom = __instance; Logger.Info($"Player: {phantom.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); - if (phantom.walkingToVent || phantom.inVent) + if (phantom.inVent) { phantom.MyPhysics.RpcBootFromVent(Main.LastEnteredVent[phantom.PlayerId].Id); } @@ -96,16 +105,20 @@ private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnim // Check appear again for desync role if (target != null) phantom?.RpcCheckAppearDesync(shouldAnimate, target); - }, 0.5f, "Check Appear when vanish is over", shoudLog: false); + }, 0.5f, $"Check Appear when vanish is over {target.PlayerId}", shoudLog: false); _ = new LateTask(() => { - if (!Main.MeetingIsStarted) + if (Main.MeetingIsStarted || phantom == null) return; + + InvisibilityList.Remove(phantom); + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + + if (PetsList.TryGetValue(phantom.PlayerId, out var petId)) { - InvisibilityList.Remove(phantom); - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + phantom?.RpcSetPetDesync(petId, target); } - }, 1.8f, "Set Scientist when vanish is over", shoudLog: false); + }, 1.8f, $"Set Scientist when vanish is over {target.PlayerId}", shoudLog: false); } } [HarmonyPatch(nameof(PlayerControl.SetRoleInvisibility)), HarmonyPrefix] @@ -122,32 +135,57 @@ public static void OnReportDeadBody(PlayerControl seer) foreach (var phantom in InvisibilityList.GetFastEnumerator()) { - if (!phantom.IsAlive()) continue; - - var clientId = seer.GetClientId(); - _ = new LateTask(() => + if (!phantom.IsAlive()) { - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); - }, 0.01f, "Set Scientist in meeting", shoudLog: false); + InvisibilityList.Remove(phantom); + continue; + } - _ = new LateTask(() => - { - phantom?.RpcSetRoleDesync(RoleTypes.Phantom, clientId); - }, 1f, "Set Phantom in meeting", shoudLog: false); + Main.Instance.StartCoroutine(CoRevertInvisible(phantom, seer)); + } + } + private static bool InValid(PlayerControl phantom, PlayerControl seer) => seer.GetClientId() == -1 || phantom == null; + private static System.Collections.IEnumerator CoRevertInvisible(PlayerControl phantom, PlayerControl seer) + { + // Set Scientist for meeting + yield return new WaitForSeconds(0.001f); + { + if (InValid(phantom, seer)) yield break; - _ = new LateTask(() => - { - if (seer != null) - phantom?.RpcStartAppearDesync(false, seer); - }, 1.5f, "Check Appear in meeting", shoudLog: false); + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); + } + // Return Phantom in meeting + yield return new WaitForSeconds(1f); + { + if (InValid(phantom, seer)) yield break; - _ = new LateTask(() => - { - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + phantom?.RpcSetRoleDesync(RoleTypes.Phantom, seer.GetClientId()); + } + // Revert invis for phantom + yield return new WaitForSeconds(1f); + { + if (InValid(phantom, seer)) yield break; - InvisibilityList.Clear(); - }, 4f, "Set Scientist in meeting after reset", shoudLog: false); + phantom?.RpcStartAppearDesync(false, seer); } + // Set Scientist back + yield return new WaitForSeconds(4f); + { + if (InValid(phantom, seer)) yield break; + + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); + + if (PetsList.TryGetValue(phantom.PlayerId, out var petId)) + { + phantom?.RpcSetPetDesync(petId, seer); + } + } + yield break; + } + public static void AfterMeeting() + { + InvisibilityList.Clear(); + PetsList.Clear(); } } // Fixed vanilla bug for host (from TOH-Y) From 05040e1a43426d94d2646606511b5560511cf31b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 19:55:11 +0800 Subject: [PATCH 707/778] Improve Menu Description --- Modules/OptionHolder.cs | 6 +++--- Patches/GameOptionsMenuPatch.cs | 31 ++++++++++++++++++++++++++----- Patches/PhantomRolePatch.cs | 2 +- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 93036fc9b3..5870b86dd8 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1771,7 +1771,7 @@ private static System.Collections.IEnumerator CoLoadOptions() FixKillCooldownValue = FloatOptionItem.Create(60771, "FixKillCooldownValue", new(0f, 180f, 2.5f), 15f, TabGroup.ModSettings, false) .SetValueFormat(OptionFormat.Seconds) .SetParent(FixFirstKillCooldown); - // 首刀保护 + // First dead shield ShieldPersonDiedFirst = BooleanOptionItem.Create(60780, "ShieldPersonDiedFirst", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); @@ -1784,11 +1784,11 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - ShieldedCanUseKillButton = BooleanOptionItem.Create(60873, "ShieldedCanUseKillButton", false, TabGroup.ModSettings, false).SetParent(ShieldPersonDiedFirst) + ShieldedCanUseKillButton = BooleanOptionItem.Create(60873, "ShieldedCanUseKillButton", true, TabGroup.ModSettings, false).SetParent(ShieldPersonDiedFirst) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - EveryoneCanSeeDeathReason = BooleanOptionItem.Create(60781, "EveryoneCanSeeDeathReason", false, TabGroup.ModSettings, false) + EveryoneCanSeeDeathReason = BooleanOptionItem.Create(60782, "EveryoneCanSeeDeathReason", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index c2d7f0bd94..241d2affd8 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -45,7 +45,28 @@ private static bool InitializePrefix(GameOptionsMenu __instance) [HarmonyPatch(nameof(GameOptionsMenu.Initialize)), HarmonyPostfix] private static void InitializePostfix() { - GameObject.Find("PlayerOptionsMenu(Clone)")?.transform.FindChild("Background")?.gameObject.SetActive(false); + var optionMenu = GameObject.Find("PlayerOptionsMenu(Clone)"); + optionMenu?.transform.FindChild("Background")?.gameObject.SetActive(false); + + _ = new LateTask(() => + { + var menuDescription = optionMenu?.transform.FindChild("What Is This?"); + + var infoImage = menuDescription.transform.FindChild("InfoImage"); + infoImage.transform.localPosition = new(-4.65f, 0.16f, -1f); + infoImage.transform.localScale = new(0.2202f, 0.2202f, 0.3202f); + + var infoText = menuDescription.transform.FindChild("InfoText"); + infoText.transform.localPosition = new(-3.5f, 0.83f, -2f); + infoText.transform.localScale = new(1f, 1f, 1f); + + var cubeObject = menuDescription.transform.FindChild("Cube"); + cubeObject.transform.localPosition = new(-3.2f, 0.55f, -0.1f); + cubeObject.transform.localScale = new(0.61f, 0.64f, 1f); + + var menuDescriptionText = GameSettingMenu.Instance.MenuDescriptionText; + menuDescriptionText.m_marginWidth = 2.5f; + }, 0.1f, "Set Menu", shoudLog: false); } [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings)), HarmonyPrefix] @@ -609,7 +630,7 @@ private static bool InitializePrefix(StringOption __instance) private static void SetupHelpIcon(CustomRoles role, StringOption __instance) { var template = __instance.transform.FindChild("MinusButton"); - var icon = GameObject.Instantiate(template, template.parent, true); + var icon = Object.Instantiate(template, template.parent, true); icon.gameObject.SetActive(true); icon.name = $"{role}HelpIcon"; var text = icon.GetComponentInChildren(); @@ -630,10 +651,10 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) { var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); var str = GetString($"{roleName}InfoLong"); - int Lenght = str.Length > 360 ? 360 : str.Length; - var infoLong = str[(str.IndexOf('\n') + 1)..Lenght]; + //int Lenght = str.Length > 360 ? 360 : str.Length; + var infoLong = str[(str.IndexOf('\n') + 1)..str.Length]; var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), GetString(role.ToString())); - var info = $"{ColorRole}: {infoLong}"; + var info = $"{ColorRole}: {infoLong}"; GameSettingMenu.Instance.MenuDescriptionText.text = info; } } diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index c356850d51..885d783308 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -10,7 +10,7 @@ namespace TOHE.Patches; public static class PhantomRolePatch { private static readonly Il2CppSystem.Collections.Generic.List InvisibilityList = new(); - private static readonly Dictionary PetsList = []; + private static readonly Dictionary PetsList = []; /* * InnerSloth is doing careless stuffs. They didnt put amModdedHost check in cmd check vanish appear From 3827f905b7f2664c17ff0d45bbdf943bd5834453 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 20:01:49 +0800 Subject: [PATCH 708/778] Check size text --- Patches/GameOptionsMenuPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 241d2affd8..c09f1f0217 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -651,10 +651,10 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) { var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); var str = GetString($"{roleName}InfoLong"); - //int Lenght = str.Length > 360 ? 360 : str.Length; + int size = str.Length > 510 ? 70 : 90; var infoLong = str[(str.IndexOf('\n') + 1)..str.Length]; var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), GetString(role.ToString())); - var info = $"{ColorRole}: {infoLong}"; + var info = $"{ColorRole}: {infoLong}"; GameSettingMenu.Instance.MenuDescriptionText.text = info; } } From 2dddfc7c992f8ea413d025aa8961f8c4f09b04af Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 20:04:17 +0800 Subject: [PATCH 709/778] Revert --- Modules/OptionHolder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 5870b86dd8..bac833e872 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1784,11 +1784,11 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - ShieldedCanUseKillButton = BooleanOptionItem.Create(60873, "ShieldedCanUseKillButton", true, TabGroup.ModSettings, false).SetParent(ShieldPersonDiedFirst) + ShieldedCanUseKillButton = BooleanOptionItem.Create(60782, "ShieldedCanUseKillButton", true, TabGroup.ModSettings, false).SetParent(ShieldPersonDiedFirst) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - EveryoneCanSeeDeathReason = BooleanOptionItem.Create(60782, "EveryoneCanSeeDeathReason", false, TabGroup.ModSettings, false) + EveryoneCanSeeDeathReason = BooleanOptionItem.Create(60781, "EveryoneCanSeeDeathReason", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(193, 255, 209, byte.MaxValue)); From 95b07c1446a46f313fd682998da3c7da579d95ee Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 20:11:33 +0800 Subject: [PATCH 710/778] Remove --- Roles/Neutral/Doppelganger.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index a30b3fd7f0..6c27d9e23c 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -12,7 +12,6 @@ internal class Doppelganger : RoleBase //===========================SETUP================================\\ private const int Id = 25000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Doppelganger); - public override bool IsExperimental => true; public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; From 0760f38a073887c8af8ae5e414ca7da69270c783 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 20:18:19 +0800 Subject: [PATCH 711/778] Fix --- Modules/CustomRolesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index c7c34e0c0f..6b99af3033 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -811,7 +811,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Scavenger) || pc.Is(CustomRoles.Lightning) || pc.Is(CustomRoles.Swift) - || pc.Is(CustomRoles.Swooper)) + || pc.Is(CustomRoles.Swooper) + || pc.Is(CustomRoles.DoubleAgent)) return false; if (!pc.GetCustomRole().IsImpostor()) return false; From d39f722e3bcfee071aca5e7a7c3d74373d3f49ea Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 21:06:32 +0800 Subject: [PATCH 712/778] Use forced check for Witness --- Roles/Crewmate/Witness.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index d47ed83966..7930b63e41 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -51,7 +51,7 @@ public override void SetAbilityButtonText(HudManager hud, byte id) } public override Sprite GetKillButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Examine"); - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { killer.SetKillCooldown(); if (Main.AllKillers.ContainsKey(target.PlayerId)) From d9d4b56afba6d21933c4d474d951f0309aceddd9 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 21:41:44 +0800 Subject: [PATCH 713/778] GetClientId() from NetworkedPlayerInfo --- Modules/ExtendedPlayerControl.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5f43dc35ec..dd5237a1c5 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -847,9 +847,11 @@ public static ClientData GetClient(this PlayerControl player) public static int GetClientId(this PlayerControl player) { if (player == null) return -1; - var client = player.GetClient(); - return client == null ? -1 : client.Id; + var data = player.Data; + return data == null ? -1 : data.ClientId; } + public static int GetClientId(this NetworkedPlayerInfo playerData) => playerData == null ? -1 : playerData.ClientId; + /// /// Only roles (no add-ons) /// From e06902a05c0faa715c09e4a164c4d2b452039375 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 5 Oct 2024 23:03:18 +0800 Subject: [PATCH 714/778] Remove --- Roles/Crewmate/Ventguard.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index fb90300742..b66a95ed34 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -16,11 +16,11 @@ internal class Ventguard : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.CrewmateSupport; //==================================================================\\ - public static OptionItem MaxGuards; - public static OptionItem BlockVentCooldown; - public static OptionItem BlockDoesNotAffectCrew; - public static OptionItem BlocksResetOnMeeting; - public static OptionItem AbilityUseGainWithEachTaskCompleted; + private static OptionItem MaxGuards; + private static OptionItem BlockVentCooldown; + private static OptionItem BlockDoesNotAffectCrew; + private static OptionItem BlocksResetOnMeeting; + //public static OptionItem AbilityUseGainWithEachTaskCompleted; private readonly HashSet BlockedVents = []; @@ -36,9 +36,9 @@ public override void SetupCustomOption() .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); BlocksResetOnMeeting = BooleanOptionItem.Create(Id + 13, "Ventguard_BlocksResetOnMeeting", true, TabGroup.CrewmateRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]); - AbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 14, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles, false) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]) - .SetValueFormat(OptionFormat.Times); + //AbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 14, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles, false) + // .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventguard]) + // .SetValueFormat(OptionFormat.Times); } public override void Init() @@ -58,7 +58,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override void SetAbilityButtonText(HudManager hud, byte playerId) { - hud.AbilityButton.buttonLabelText.text = GetString("VentguardVentButtonText"); + hud.AbilityButton.OverrideText(GetString("VentguardVentButtonText")); } public override void OnEnterVent(PlayerControl ventguard, Vent vent) From 36b11b27f3326c92bafb7f24c20e59371520b5f8 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:45:05 -0400 Subject: [PATCH 715/778] fix ejection because im a bum who forgot to change a variable --- Patches/MeetingHudPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 237d30ecb1..cc50ba7e20 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -512,7 +512,7 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti else name += string.Format(GetString("NeutralRemain"), neutralnum) + comma; if (Options.ShowNARemainOnEject.GetBool() && apocnum > 0) - name += string.Format(GetString("ApocRemain"), neutralnum) + comma; + name += string.Format(GetString("ApocRemain"), apocnum) + comma; } EndOfSession: From 0d099e877be2ec5fdc5faacb2a6d2b650f0ee175 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:47:53 -0400 Subject: [PATCH 716/778] apoc cant get rebirth nor evader --- Modules/CustomRolesHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 6b99af3033..fbc6f550f4 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -749,7 +749,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if (pc.Is(CustomRoles.Doppelganger) || pc.Is(CustomRoles.Jester) || pc.Is(CustomRoles.Zombie) - || pc.Is(CustomRoles.Solsticer)) return false; + || pc.Is(CustomRoles.Solsticer) || pc.IsNeutralApocalypse()) return false; break; case CustomRoles.Youtuber: @@ -1079,6 +1079,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Spurt)) return false; break; + case CustomRoles.Evader: + if (pc.IsNeutralApocalypse()) + return false; + break; } return true; From 3b2391272202577598c0f0fcf910cf6708c73cf4 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:51:46 -0400 Subject: [PATCH 717/778] apoc can see eachother's addons --- Modules/ExtendedPlayerControl.cs | 1 + Modules/OptionHolder.cs | 11 +++++++++-- Resources/Lang/en_US.json | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index dd5237a1c5..1fbbf1c30c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1276,6 +1276,7 @@ public static bool ShowSubRoleTarget(this PlayerControl seer, PlayerControl targ else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || seer.Is(CustomRoles.God) || (seer.IsHost() && Main.GodMode.Value)) return true; else if (Main.VisibleTasksCount && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) return true; else if (Options.ImpsCanSeeEachOthersAddOns.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor) && !subRole.IsBetrayalAddon()) return true; + else if (Options.ApocCanSeeEachOthersAddOns.GetBool() && seer.IsNeutralApocalypse() && target.IsNeutralApocalypse() && !subRole.IsBetrayalAddon()) return true; else if ((subRole is CustomRoles.Madmate or CustomRoles.Sidekick diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index bac833e872..222d47f65a 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -497,11 +497,15 @@ private enum RatesZeroOne public static OptionItem NonNeutralKillingRolesMaxPlayer; public static OptionItem NeutralKillingRolesMinPlayer; public static OptionItem NeutralKillingRolesMaxPlayer; + public static OptionItem NeutralRoleWinTogether; + public static OptionItem NeutralWinTogether; + + // Neutral Apocalypse public static OptionItem NeutralApocalypseRolesMinPlayer; public static OptionItem NeutralApocalypseRolesMaxPlayer; public static OptionItem TransformedNeutralApocalypseCanBeGuessed; - public static OptionItem NeutralRoleWinTogether; - public static OptionItem NeutralWinTogether; + public static OptionItem ApocCanSeeEachOthersAddOns; + // Add-on public static OptionItem NameDisplayAddons; @@ -916,6 +920,9 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetGameMode(CustomGameMode.Standard) .SetHeader(true); + ApocCanSeeEachOthersAddOns = BooleanOptionItem.Create(60025, "ApocCanSeeEachOthersAddOns", true, TabGroup.NeutralRoles, false) + .SetGameMode(CustomGameMode.Standard); + CustomRoleManager.GetNormalOptions(Custom_RoleType.NeutralApocalypse).ForEach(r => r.SetupCustomOption()); #endregion diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 27a3f4acd9..ab79079871 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1427,6 +1427,7 @@ "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", + "ApocCanSeeEachOthersAddOns": "Neutral Apocalypse can see each other's Add-ons", "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", "ImpKnowWhosMadmate": "Impostors know Madmates", From a62d249e010eb71feb85148803d9bfe5dd1957f5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 6 Oct 2024 14:26:54 +0800 Subject: [PATCH 718/778] Fix lot of bugs --- Modules/ExtendedPlayerControl.cs | 4 +- Modules/LocateArrow.cs | 4 ++ Modules/TargetArrow.cs | 4 ++ Modules/Utils.cs | 10 +---- Patches/ChatCommandPatch.cs | 2 +- Roles/Crewmate/CopyCat.cs | 4 +- Roles/Crewmate/Jailer.cs | 30 ++++++++------- Roles/Crewmate/Judge.cs | 65 ++++++++++++-------------------- Roles/Neutral/Amnesiac.cs | 37 +++++++++--------- Roles/Neutral/Executioner.cs | 5 ++- Roles/Neutral/Lawyer.cs | 5 ++- 11 files changed, 83 insertions(+), 87 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index dd5237a1c5..64b84bf9cc 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -100,6 +100,8 @@ public static void RpcRevive(this PlayerControl player) player.SetKillCooldown(); player.RpcResetAbilityCooldown(); player.SyncGeneralOptions(); + + Utils.DoNotifyRoles(SpecifySeer: player, NoCache: true); } /// /// Changes the Role Basis of player during the game @@ -111,7 +113,7 @@ public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles new var playerId = player.PlayerId; var playerClientId = player.GetClientId(); - var playerRole = Utils.GetRoleMap(playerId).CustomRole; + var playerRole = player.GetCustomRole(); var newRoleType = newCustomRole.GetRoleTypes(); RoleTypes remeberRoleType; diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index 94972b6a1f..e380772c82 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -78,6 +78,8 @@ public static void Remove(byte seer, Vector3 locate) { var arrowInfo = new ArrowInfo(seer, locate); var removeList = new List(LocateArrows.Keys.Where(k => k.Equals(arrowInfo))); + if (!removeList.Any()) return; + foreach (ArrowInfo a in removeList.ToArray()) { LocateArrows.Remove(a); @@ -94,6 +96,8 @@ public static void Remove(byte seer, Vector3 locate) public static void RemoveAllTarget(byte seer) { var removeList = new List(LocateArrows.Keys.Where(k => k.From == seer)); + if (!removeList.Any()) return; + foreach (ArrowInfo arrowInfo in removeList.ToArray()) { LocateArrows.Remove(arrowInfo); diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index 96536733d4..4f2566e39d 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -78,6 +78,8 @@ public static void Remove(byte seer, byte target) { var arrowInfo = new ArrowInfo(seer, target); var removeList = new List(TargetArrows.Keys.Where(k => k.Equals(arrowInfo))); + if (!removeList.Any()) return; + foreach (ArrowInfo a in removeList.ToArray()) { TargetArrows.Remove(a); @@ -94,6 +96,8 @@ public static void Remove(byte seer, byte target) public static void RemoveAllTarget(byte seer) { var removeList = new List(TargetArrows.Keys.Where(k => k.From == seer)); + if (!removeList.Any()) return; + foreach (ArrowInfo arrowInfo in removeList.ToArray()) { TargetArrows.Remove(arrowInfo); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 2188fe121a..e56d016754 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1874,12 +1874,9 @@ public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro) return; - if (Main.MeetingIsStarted && !isForMeeting) return; + if (Main.MeetingIsStarted && !(isForMeeting || GameEndCheckerForNormal.GameIsEnded)) return; if (Main.AllPlayerControls == null) return; - //Do not update NotifyRoles during meetings - if (GameStates.IsMeeting && !GameEndCheckerForNormal.GameIsEnded) return; - //var caller = new System.Diagnostics.StackFrame(1, false); //var callerMethod = caller.GetMethod(); //string callerMethodName = callerMethod.Name; @@ -1891,12 +1888,9 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro) return Task.CompletedTask; - if (Main.MeetingIsStarted && !isForMeeting) return Task.CompletedTask; + if (Main.MeetingIsStarted && !(isForMeeting || GameEndCheckerForNormal.GameIsEnded)) return Task.CompletedTask; if (Main.AllPlayerControls == null) return Task.CompletedTask; - //Do not update NotifyRoles during meetings - if (GameStates.IsMeeting && !GameEndCheckerForNormal.GameIsEnded) return Task.CompletedTask; - //var logger = Logger.Handler("DoNotifyRoles"); HudManagerPatch.NowCallNotifyRolesCount++; diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index ceb7af3c12..431fdd43db 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -435,7 +435,7 @@ public static bool Prefix(ChatController __instance) int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + int apocnum = allAlivePlayers.Count(pc => pc.IsNeutralApocalypse() || pc.IsTransformedNeutralApocalypse()); var sub = new StringBuilder(); sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 650ea274b6..5aad19da96 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -75,8 +75,8 @@ public static void UnAfterMeetingTasks() { pc.GetRoleClass()?.OnRemove(pc.PlayerId); } - pc.RpcSetCustomRole(CustomRoles.CopyCat); pc.RpcChangeRoleBasis(CustomRoles.CopyCat); + pc.RpcSetCustomRole(CustomRoles.CopyCat); } pc.ResetKillCooldown(); } @@ -134,8 +134,8 @@ CustomRoles.Baker when Baker.CurrentBread() is 2 => CustomRoles.Medic, { if (role != CustomRoles.CopyCat) { - killer.RpcSetCustomRole(role); killer.RpcChangeRoleBasis(role); + killer.RpcSetCustomRole(role); killer.GetRoleClass()?.OnAdd(killer.PlayerId); killer.SyncSettings(); Main.PlayerStates[killer.PlayerId].InitTask(killer); diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 57016ed653..09c9f9e7ab 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -27,7 +27,7 @@ internal class Jailer : RoleBase private static OptionItem CKCanBeExe; private static OptionItem NotifyJailedOnMeetingOpt; - private static readonly Dictionary JailerTarget = []; + private static readonly Dictionary JailerTarget = []; private static readonly Dictionary JailerExeLimit = []; private static readonly Dictionary JailerHasExe = []; private static readonly Dictionary JailerDidVote = []; @@ -60,7 +60,7 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); JailerExeLimit.Add(playerId, MaxExecution.GetInt()); - JailerTarget.Add(playerId, byte.MaxValue); + JailerTarget[playerId] = byte.MaxValue; JailerHasExe.Add(playerId, false); JailerDidVote.Add(playerId, false); } @@ -144,26 +144,28 @@ public override void OnMeetingHudStart(PlayerControl pc) foreach (var targetId in JailerTarget.Values) { - if (targetId == byte.MaxValue) continue; - var tpc = targetId.GetPlayer(); + var targetIdByte = (byte)targetId; + if (targetIdByte == byte.MaxValue) continue; + + var tpc = targetIdByte.GetPlayer(); if (!tpc.IsAlive()) continue; - MeetingHudStartPatch.AddMsg(GetString("JailedNotifyMsg"), targetId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); + MeetingHudStartPatch.AddMsg(GetString("JailedNotifyMsg"), targetIdByte, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); } } public override void OnVote(PlayerControl voter, PlayerControl target) { - if (voter == null || target == null || !voter.Is(CustomRoles.Jailer)) return; + if (voter == null || target == null) return; if (JailerDidVote.TryGetValue(voter.PlayerId, out var didVote) && didVote) return; if (JailerTarget.TryGetValue(voter.PlayerId, out var jTarget) && jTarget == byte.MaxValue) return; JailerDidVote[voter.PlayerId] = true; - if (target.PlayerId == JailerTarget[voter.PlayerId]) + if (target.PlayerId == jTarget) { if (JailerExeLimit[voter.PlayerId] > 0) { - JailerExeLimit[voter.PlayerId] = JailerExeLimit[voter.PlayerId] - 1; + JailerExeLimit[voter.PlayerId]--; JailerHasExe[voter.PlayerId] = true; } else JailerHasExe[voter.PlayerId] = false; @@ -173,18 +175,18 @@ public override void OnVote(PlayerControl voter, PlayerControl target) public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting) { - return JailerTarget.TryGetValue(seer.PlayerId, out var targetID) && isForMeeting && seer != seen && seen.PlayerId == targetID ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), "⊠") : ""; + return seer.PlayerId != seen.PlayerId && JailerTarget.TryGetValue(seer.PlayerId, out var targetID) && seen.PlayerId == targetID ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailer), "⊠") : string.Empty; } private static bool CanBeExecuted(CustomRoles role) { - return ((role.IsNB() && NBCanBeExe.GetBool()) || + return (role.IsNB() && NBCanBeExe.GetBool()) || (role.IsNC() && NCCanBeExe.GetBool()) || (role.IsNE() && NECanBeExe.GetBool()) || (role.IsNK() && NKCanBeExe.GetBool()) || (role.IsNA() && NACanBeExe.GetBool()) || (role.IsCrewKiller() && CKCanBeExe.GetBool()) || - (role.IsImpostorTeamV3())); + role.IsImpostorTeamV3(); } public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo exiled) @@ -194,18 +196,18 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex if (targetId != byte.MaxValue && JailerHasExe[playerId]) { - var tpc = targetId.GetPlayer(); + var targetIdByte = (byte)targetId; + var tpc = targetIdByte.GetPlayer(); if (tpc != null) { if (tpc.IsAlive()) { - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Execution, targetId); + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Execution, targetIdByte); tpc.SetRealKiller(player); } if (!CanBeExecuted(tpc.GetCustomRole())) { JailerExeLimit[playerId] = 0; - SendRPC(playerId, setTarget: false); } } } diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index fb3e3112ac..8792a69ff6 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -68,7 +68,7 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - TrialLimitMeeting.Add(playerId, TrialLimitPerMeeting.GetInt()); + TrialLimitMeeting[playerId] = TrialLimitPerMeeting.GetInt(); AbilityLimit = TrialLimitPerGame.GetInt(); } public override void Remove(byte playerId) @@ -81,7 +81,6 @@ public override void OnReportDeadBody(PlayerControl party, NetworkedPlayerInfo d foreach (var pid in TrialLimitMeeting.Keys) { TrialLimitMeeting[pid] = TrialLimitPerMeeting.GetInt(); - SendRPC(pid, true); } } public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) @@ -92,7 +91,7 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) if (!GameStates.IsMeeting || pc == null || GameStates.IsExilling) return false; if (!pc.Is(CustomRoles.Judge)) return false; - int operate = 0; // 1:ID 2:猜测 + int operate = 0; msg = msg.ToLower().TrimStart().TrimEnd(); if (CheckCommond(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id||編號|玩家編號")) operate = 1; else if (CheckCommond(ref msg, "sp|jj|tl|trial|审判|判|审|審判|審", false)) operate = 2; @@ -212,24 +211,22 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) TrialLimitMeeting[pc.PlayerId]--; AbilityLimit--; SendSkillRPC(); - SendRPC(pc.PlayerId, true); if (!GameStates.IsProceeding) - - _ = new LateTask(() => - { - dp.SetDeathReason(PlayerState.DeathReason.Trialed); - dp.SetRealKiller(pc); - GuessManager.RpcGuesserMurderPlayer(dp); + _ = new LateTask(() => + { + dp.SetDeathReason(PlayerState.DeathReason.Trialed); + dp.SetRealKiller(pc); + GuessManager.RpcGuesserMurderPlayer(dp); - Main.PlayersDiedInMeeting.Add(dp.PlayerId); - MurderPlayerPatch.AfterPlayerDeathTasks(pc, dp, true); + Main.PlayersDiedInMeeting.Add(dp.PlayerId); + MurderPlayerPatch.AfterPlayerDeathTasks(pc, dp, true); - NotifyRoles(isForMeeting: false, NoCache: true); + NotifyRoles(isForMeeting: false, NoCache: true); - _ = new LateTask(() => { SendMessage(string.Format(GetString("Judge_TrialKill"), Name), 255, ColorString(GetRoleColor(CustomRoles.Judge), GetString("Judge_TrialKillTitle")), true); }, 0.6f, "Guess Msg"); + _ = new LateTask(() => { SendMessage(string.Format(GetString("Judge_TrialKill"), Name), 255, ColorString(GetRoleColor(CustomRoles.Judge), GetString("Judge_TrialKillTitle")), true); }, 0.6f, "Guess Msg"); - }, 0.2f, "Trial Kill"); + }, 0.2f, "Trial Kill"); } } return true; @@ -243,7 +240,7 @@ private static bool MsgToPlayerAndRole(string msg, out byte id, out string error string result = string.Empty; for (int i = 0; i < mc.Count; i++) { - result += mc[i];//匹配结果是完整的数字,此处可以不做拼接的 + result += mc[i]; } if (int.TryParse(result, out int num)) @@ -252,15 +249,11 @@ private static bool MsgToPlayerAndRole(string msg, out byte id, out string error } else { - //并不是玩家编号,判断是否颜色 - //byte color = GetColorFromMsg(msg); - //好吧我不知道怎么取某位玩家的颜色,等会了的时候再来把这里补上 id = byte.MaxValue; error = GetString("Judge_TrialHelp"); return false; } - //判断选择的玩家是否合理 PlayerControl target = GetPlayerById(id); if (target == null || target.Data.IsDead) { @@ -292,37 +285,29 @@ public static bool CheckCommond(ref string msg, string command, bool exact = tru return false; } - private static void SendRPC(byte playerId, bool syncLimit = false) + private static void SendRPC(byte targetId) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Judge, SendOption.Reliable, -1); - writer.Write(playerId); - writer.Write(syncLimit); - writer.WritePacked(TrialLimitMeeting[playerId]); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Judge, SendOption.Reliable); + writer.Write(targetId); AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) { - byte PlayerId = reader.ReadByte(); - var syncLimit = reader.ReadBoolean(); + byte targetId = reader.ReadByte(); - if (syncLimit) - { - var trialLimit = reader.ReadPackedInt32(); - TrialLimitMeeting[PlayerId] = trialLimit; - } - else if (pc.GetRoleClass() is Judge judge) + if (pc.GetRoleClass() is Judge judge) { - judge.TrialMsg(pc, $"/tl {PlayerId}", true); + judge.TrialMsg(pc, $"/tl {targetId}", true); } } - private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) + private static void JudgeOnClick(byte targetId /*, MeetingHud __instance*/) { - Logger.Msg($"Click: ID {playerId}", "Judge UI"); - var pc = GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; - if (AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.GetRoleClass() is Judge judge) judge.TrialMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); - else SendRPC(playerId, false); + Logger.Msg($"Click: ID {targetId}", "Judge UI"); + var target = targetId.GetPlayer(); + if (target == null || !target.IsAlive() || !GameStates.IsVoting) return; + if (AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.GetRoleClass() is Judge judge) judge.TrialMsg(PlayerControl.LocalPlayer, $"/tl {targetId}", true); + else SendRPC(targetId); } public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 6d414ad524..4a0b25dd20 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -46,7 +46,6 @@ public override void Add(byte playerId) playerIdList.Add(playerId); CanUseVent[playerId] = true; - if (!AmongUsClient.Instance.AmHost) return; if (ShowArrows.GetBool()) { CheckDeadBodyOthers.Add(CheckDeadBody); @@ -55,16 +54,10 @@ public override void Add(byte playerId) public override void Remove(byte playerId) { playerIdList.Remove(playerId); - - if (!AmongUsClient.Instance.AmHost) return; - if (ShowArrows.GetBool()) - { - CheckDeadBodyOthers.Remove(CheckDeadBody); - } } public override void ApplyGameOptions(IGameOptions opt, byte playerId) { - var player = Utils.GetPlayerById(playerId); + var player = playerId.GetPlayer(); if (player == null) return; if (player.Is(Custom_Team.Crewmate)) @@ -91,12 +84,12 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMeeting) { if (inMeeting || Main.MeetingIsStarted) return; - foreach (var pc in playerIdList.ToArray()) + foreach (var playerId in playerIdList.ToArray()) { - var player = Utils.GetPlayerById(pc); + var player = playerId.GetPlayer(); if (!player.IsAlive()) continue; - LocateArrow.Add(pc, target.transform.position); + LocateArrow.Add(playerId, target.Data.GetDeadBody().transform.position); } } @@ -110,14 +103,17 @@ public override string GetSuffix(PlayerControl seer, PlayerControl target, bool } else return string.Empty; } - + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + if (ShowArrows.GetBool()) + foreach (var apc in playerIdList.ToArray()) + { + LocateArrow.RemoveAllTarget(apc); + } + } public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPlayerInfo deadBody, PlayerControl killer) { var tar = deadBody.Object; - foreach (var apc in playerIdList.ToArray()) - { - LocateArrow.RemoveAllTarget(apc); - } if (__instance.Is(CustomRoles.Amnesiac)) { var tempRole = CustomRoles.Amnesiac; @@ -164,7 +160,7 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl case 3: // Maverick tempRole = CustomRoles.Maverick; break; - case 4: // Imitator..........................................................................kill me + case 4: // Imitator tempRole = CustomRoles.Imitator; break; } @@ -188,6 +184,13 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl }; Logger.Info($"player id: {__instance.PlayerId}, Can use vent: {CanUseVent[__instance.PlayerId]}", "Previous Amne Vent"); } + if (ShowArrows.GetBool()) + { + foreach (var apc in playerIdList.ToArray()) + { + LocateArrow.RemoveAllTarget(apc); + } + } return false; } return true; diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index a2e484bb60..3c622ff1b1 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -162,11 +162,12 @@ private void ChangeRole() var valueChanger = ChangeRolesAfterTargetKilled.GetValue(); var newCustomRole = CRoleChangeRoles[valueChanger]; + if (executioner.IsAlive()) + executioner.RpcChangeRoleBasis(newCustomRole); + executioner.GetRoleClass()?.OnRemove(executionerId); executioner.RpcSetCustomRole(newCustomRole); executioner.GetRoleClass().OnAdd(executionerId); - - executioner.RpcChangeRoleBasis(newCustomRole); Utils.NotifyRoles(SpecifySeer: executioner); } diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index b63113f870..f938ad2734 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -194,12 +194,13 @@ private void ChangeRole(bool inMeeting) var valueChanger = ChangeRolesAfterTargetKilled.GetValue(); var newCustomRole = CRoleChangeRoles[valueChanger]; + if (lawyer.IsAlive()) + lawyer.RpcChangeRoleBasis(newCustomRole); + lawyer.GetRoleClass()?.OnRemove(lawyer.PlayerId); lawyer.RpcSetCustomRole(newCustomRole); lawyer.GetRoleClass()?.OnAdd(lawyer.PlayerId); - lawyer.RpcChangeRoleBasis(newCustomRole); - if (inMeeting) { Utils.SendMessage(GetString("LawyerTargetDeadInMeeting"), sendTo: lawyer.PlayerId); From fe2a5ebb3e7c50a65a682fc5d88886197685eed8 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 6 Oct 2024 14:31:22 +0800 Subject: [PATCH 719/778] Fix missed string "GuessedAsMundane" --- Resources/Lang/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ab79079871..c17ac04575 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2420,6 +2420,7 @@ "NeutralCanBeGuesser": "Neutrals can become Guesser", "CrewCanBeMundane": "Crewmates can become Mundane", "NeutralCanBeMundane": "Neutrals can become Mundane", + "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", "ObliviousBaitImmune": "Immune to Bait", "ImpCanBeInLove": "Impostors can be in love", "CrewCanBeInLove": "Crewmates can be in love", From e5aeb0640343f4231ceebcc16384d085348eebb5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 6 Oct 2024 14:33:23 +0800 Subject: [PATCH 720/778] Change MonarchInfoLong --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index c17ac04575..df769ca9fa 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -825,7 +825,7 @@ "MorticianInfoLong": "(Crewmates):\nThe Mortician can see arrows pointing to all dead bodies, and if the Mortician reports a body, they will know the last player the victim had contact with. Note: Mortician won't be Oblivious or Seer.", "MediumInfoLong": "(Crewmates):\nThe Medium can establish contact with a dead player after someone reports a dead body. The player who reports doesn't have to be the Medium. The dead player can answer once with a YES or a NO to the Medium's question, which only the Medium will see (the dead player can use /ms yes or /ms no). Note: Medium won't be Oblivious.", "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all shield animations caused by other players after the first meeting. The shied animations typically indicate a role ability, so look out for this.", - "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has multiple votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or exiled.", + "MonarchInfoLong": "(Crewmates):\nAs the Monarch, you can knight players to give them an extra vote.\n\nYou cannot knight someone who already has multiple votes.\n\nKnighted players appear with a golden name.\nIf a knighted player is alive, the Monarch cannot be guessed or killed.", "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist vents, they will reset the kill cooldown for every player with a kill button. When they become a Madmate, this ability will only work on Crewmates.", "OverseerInfoLong": "(Crewmates):\nAs The Overseer, you have minimal vision, but you can use your kill button to reveal the role of a nearby player. A 「○」 will be displayed next to the revealed target after you use the kill button on them, and you will also be scanning them (only you can see this). Stay near the target for a defined time to reveal his role; if you move too far away, the reveal will cancel.", "CoronerInfoLong": "(Crewmates):\nAs a Coroner, you can't report corpses; instead, after trying to report the corpse, you will see an arrow leading you to the killer. If someone calls a meeting, the arrows disappear. Depending on the settings, players can't report the body you found.", From 8846534e5a4d96f20e369ccedefd13923c7dbd74 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 6 Oct 2024 15:14:11 +0800 Subject: [PATCH 721/778] Fix kill cd for Glitch --- Modules/ExtendedPlayerControl.cs | 11 +++++++-- Roles/Neutral/Glitch.cs | 39 ++++++++++++++++---------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 8249e80cea..f13d525591 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -489,8 +489,15 @@ public static void SetKillCooldown(this PlayerControl player, float time = -1f, else Main.AllPlayerKillCooldown[player.PlayerId] *= 2; if (player.GetRoleClass() is Glitch gc) { - gc.LastKill = Utils.GetTimeStamp() + ((int)(time / 2) - Glitch.KillCooldown.GetInt()); - gc.KCDTimer = (int)(time / 2); + if (gc.NotSetCD) + { + gc.NotSetCD = false; + } + else + { + gc.LastKill = Utils.GetTimeStamp() + ((int)(time / 2) - Glitch.KillCooldown.GetInt()); + gc.KCDTimer = (int)(time / 2); + } } else if (forceAnime || !player.IsModded() || !Options.DisableShieldAnimations.GetBool()) { diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index a94c2b5308..3fb21e3ee3 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -36,9 +36,9 @@ internal class Glitch : RoleBase public long LastKill; public long LastMimic; - - private bool isShifted = false; - private long lastRpcSend = 0; + public bool NotSetCD = false; + private bool IsShifted = false; + private long LastRpcSend = 0; public override void SetupCustomOption() { @@ -59,20 +59,20 @@ public override void SetupCustomOption() } public override void Add(byte playerId) { - HackCDTimer = 10; KCDTimer = 10; MimicCDTimer = 10; MimicDurTimer = 0; - isShifted = false; + NotSetCD = false; + IsShifted = false; var ts = Utils.GetTimeStamp(); LastKill = ts; LastHack = ts; LastMimic = ts; - lastRpcSend = ts; + LastRpcSend = ts; // Double Trigger var pc = Utils.GetPlayerById(playerId); @@ -86,7 +86,7 @@ public void Mimic(PlayerControl pc) if (pc == null) return; if (!pc.IsAlive()) return; if (MimicCDTimer > 0) return; - if (isShifted) return; + if (IsShifted) return; var playerlist = Main.AllAlivePlayerControls.Where(a => a.PlayerId != pc.PlayerId).ToList(); @@ -94,7 +94,7 @@ public void Mimic(PlayerControl pc) { pc.RpcShapeshift(playerlist[IRandom.Instance.Next(0, playerlist.Count)], false); - isShifted = true; + IsShifted = true; LastMimic = Utils.GetTimeStamp(); MimicCDTimer = MimicCooldown.GetInt(); MimicDurTimer = MimicDuration.GetInt(); @@ -110,8 +110,7 @@ public void Mimic(PlayerControl pc) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (killer == null) return false; - if (target == null) return false; + if (killer == null || target == null) return false; if (KCDTimer > 0 && HackCDTimer > 0) return false; @@ -135,7 +134,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (lowLoad) return; + if (lowLoad || !Main.IntroDestroyed) return; + if (HackCDTimer > 180 || HackCDTimer < 0) HackCDTimer = 0; if (KCDTimer > 180 || KCDTimer < 0) KCDTimer = 0; if (MimicCDTimer > 180 || MimicCDTimer < 0) MimicCDTimer = 0; @@ -151,9 +151,6 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } } - if (player == null) return; - if (!player.Is(CustomRoles.Glitch)) return; - if (change) { Utils.NotifyRoles(SpecifySeer: player, ForceLoop: false); } if (!player.IsAlive()) @@ -163,10 +160,10 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT MimicCDTimer = 0; MimicDurTimer = 0; - if (lastRpcSend <= nowTime + 500) + if (LastRpcSend <= nowTime + 500) { SendRPC(); - lastRpcSend += 9999; + LastRpcSend += 9999; } return; } @@ -177,12 +174,12 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT catch { MimicDurTimer = 0; } if (MimicDurTimer > 180) MimicDurTimer = 0; } - if ((MimicDurTimer <= 0 || !GameStates.IsInTask) && isShifted) + if ((MimicDurTimer <= 0 || !GameStates.IsInTask) && IsShifted) { try { player.RpcShapeshift(player, false); - isShifted = false; + IsShifted = false; } catch (System.Exception ex) { @@ -215,10 +212,10 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } if (player.IsNonHostModdedClient()) // For mooded non host players, sync kcd per second { - if (lastRpcSend < nowTime) + if (LastRpcSend < nowTime) { SendRPC(); - lastRpcSend = nowTime; + LastRpcSend = nowTime; } } } @@ -246,6 +243,8 @@ public override void AfterMeetingTasks() KCDTimer = 10; HackCDTimer = 10; MimicCDTimer = 10; + MimicDurTimer = 0; + NotSetCD = true; SendRPC(); } public override bool OnCoEnterVentOthers(PlayerPhysics physics, int ventId) From 763185bb6a3b48186baf4b43cf6ba6a1fee2b967 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 6 Oct 2024 15:29:30 +0800 Subject: [PATCH 722/778] Fix PhantomRole when meeting forced started --- Modules/ExtendedPlayerControl.cs | 2 +- Patches/PhantomRolePatch.cs | 16 +++++++++------- Patches/PlayerControlPatch.cs | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f13d525591..d9c4af6462 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1190,7 +1190,7 @@ public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPla if (Options.DisableMeeting.GetBool() && !force) return; SetUpRoleTextPatch.IsInIntro = false; - ReportDeadBodyPatch.AfterReportTasks(reporter, target); + ReportDeadBodyPatch.AfterReportTasks(reporter, target, true); MeetingRoomManager.Instance.AssignSelf(reporter, target); DestroyableSingleton.Instance.OpenMeetingRoom(reporter); reporter.RpcStartMeeting(target); diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index 885d783308..802652dbf0 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -129,7 +129,7 @@ private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool is Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); } - public static void OnReportDeadBody(PlayerControl seer) + public static void OnReportDeadBody(PlayerControl seer, bool force) { if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; @@ -141,19 +141,21 @@ public static void OnReportDeadBody(PlayerControl seer) continue; } - Main.Instance.StartCoroutine(CoRevertInvisible(phantom, seer)); + Main.Instance.StartCoroutine(CoRevertInvisible(phantom, seer, force)); } } private static bool InValid(PlayerControl phantom, PlayerControl seer) => seer.GetClientId() == -1 || phantom == null; - private static System.Collections.IEnumerator CoRevertInvisible(PlayerControl phantom, PlayerControl seer) + private static System.Collections.IEnumerator CoRevertInvisible(PlayerControl phantom, PlayerControl seer, bool force) { // Set Scientist for meeting - yield return new WaitForSeconds(0.001f); + if (!force) { - if (InValid(phantom, seer)) yield break; - - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); + yield return new WaitForSeconds(0.0001f); } + if (InValid(phantom, seer)) yield break; + + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); + // Return Phantom in meeting yield return new WaitForSeconds(1f); { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index f2ae205813..d04b16d187 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -839,7 +839,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] Network // This is patched in CheckGameEndPatch return true; } - public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo target) + public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo target, bool force = false) { //============================================= // Hereinafter, it is assumed that the button is confirmed to be pressed @@ -910,7 +910,7 @@ public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo ta pc.FixMixedUpOutfit(); } - PhantomRolePatch.OnReportDeadBody(pc); + PhantomRolePatch.OnReportDeadBody(pc, force); Logger.Info($"Player {pc?.Data?.PlayerName}: Id {pc.PlayerId} - is alive: {pc.IsAlive()}", "CheckIsAlive"); } From a1547fc34d7e94048e064408b7a26fbf7cfeea88 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 6 Oct 2024 21:49:10 +0800 Subject: [PATCH 723/778] Fix --- Roles/AddOns/Common/DoubleShot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/AddOns/Common/DoubleShot.cs b/Roles/AddOns/Common/DoubleShot.cs index a277cd5c88..de2155975f 100644 --- a/Roles/AddOns/Common/DoubleShot.cs +++ b/Roles/AddOns/Common/DoubleShot.cs @@ -14,7 +14,7 @@ public class DoubleShot : IAddon public void SetupCustomOption() { - SetupAdtRoleOptions(19200, CustomRoles.DoubleShot, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); + SetupAdtRoleOptions(19200, CustomRoles.DoubleShot, canSetNum: true, tab: TabGroup.Addons); ImpCanBeDoubleShot = BooleanOptionItem.Create(19210, "ImpCanBeDoubleShot", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); CrewCanBeDoubleShot = BooleanOptionItem.Create(19211, "CrewCanBeDoubleShot", true, TabGroup.Addons, false) From 8331a03d15ae4553864b61247c580d26502fbbc9 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:25:34 -0400 Subject: [PATCH 724/778] Add Sleuth Settings back --- Roles/AddOns/Common/Sleuth.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Roles/AddOns/Common/Sleuth.cs b/Roles/AddOns/Common/Sleuth.cs index 869555ebc4..c6448881b1 100644 --- a/Roles/AddOns/Common/Sleuth.cs +++ b/Roles/AddOns/Common/Sleuth.cs @@ -5,6 +5,9 @@ public class Sleuth : IAddon private const int Id = 20100; public AddonTypes Type => AddonTypes.Helpful; + public static OptionItem ImpCanBeSleuth; + public static OptionItem CrewCanBeSleuth; + public static OptionItem NeutralCanBeSleuth; public static OptionItem SleuthCanKnowKillerRole; public static readonly Dictionary SleuthNotify = []; @@ -12,6 +15,9 @@ public class Sleuth : IAddon public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Sleuth, canSetNum: true); + ImpCanBeSleuth = BooleanOptionItem.Create(Id + 10, "ImpCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); + CrewCanBeSleuth = BooleanOptionItem.Create(Id + 11, "CrewCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); + NeutralCanBeSleuth = BooleanOptionItem.Create(Id + 12, "NeutralCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); SleuthCanKnowKillerRole = BooleanOptionItem.Create(Id + 13, "SleuthCanKnowKillerRole", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); } From 8fb013541880d5a9f8c724e184c31c6ca1bb4e4a Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:33:53 -0400 Subject: [PATCH 725/778] Miner can't be Circumvent --- Modules/CustomRolesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index fbc6f550f4..04c35c7445 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -887,7 +887,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Swooper) || pc.Is(CustomRoles.Wildling) || pc.Is(CustomRoles.KillingMachine) - || pc.Is(CustomRoles.Lurker)) + || pc.Is(CustomRoles.Lurker) + || pc.Is(CustomRoles.Miner)) return false; if (!pc.GetCustomRole().IsImpostor()) return false; From 7c93fdf220e90ce4abd84241f3af5cc518607600 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:04:25 -0400 Subject: [PATCH 726/778] Return sleuth back to normal --- Roles/AddOns/Common/Sleuth.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Roles/AddOns/Common/Sleuth.cs b/Roles/AddOns/Common/Sleuth.cs index c6448881b1..0e4a0bd3eb 100644 --- a/Roles/AddOns/Common/Sleuth.cs +++ b/Roles/AddOns/Common/Sleuth.cs @@ -4,20 +4,14 @@ public class Sleuth : IAddon { private const int Id = 20100; public AddonTypes Type => AddonTypes.Helpful; - - public static OptionItem ImpCanBeSleuth; - public static OptionItem CrewCanBeSleuth; - public static OptionItem NeutralCanBeSleuth; + public static OptionItem SleuthCanKnowKillerRole; public static readonly Dictionary SleuthNotify = []; - + public void SetupCustomOption() { Options.SetupAdtRoleOptions(Id, CustomRoles.Sleuth, canSetNum: true); - ImpCanBeSleuth = BooleanOptionItem.Create(Id + 10, "ImpCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); - CrewCanBeSleuth = BooleanOptionItem.Create(Id + 11, "CrewCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); - NeutralCanBeSleuth = BooleanOptionItem.Create(Id + 12, "NeutralCanBeSleuth", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); SleuthCanKnowKillerRole = BooleanOptionItem.Create(Id + 13, "SleuthCanKnowKillerRole", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); } From b150fbf8686bb75d5313b334f8f4c4c8bd82f118 Mon Sep 17 00:00:00 2001 From: WaterPanda <59753523+KingPanda360@users.noreply.github.com> Date: Sun, 6 Oct 2024 23:37:57 -0400 Subject: [PATCH 727/778] Add ApocolypseInfoLong --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index df769ca9fa..92aef3d42a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1021,7 +1021,7 @@ "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", - + "ApocolypseInfoLong": "(Apocolypse):\nApocolypse members are on a seperate team which work together and win together. If there are multiple apocolypse roles in the game, they can see each other's roles.\nDepending on the Host's settings, Apocolypse roles can guess or be guessed.", "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", From 3af4fb6ca1b8d6802dbbe43fd13e30bb1d137ef1 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 13:14:35 +0800 Subject: [PATCH 728/778] Fix arrows & Sleuth --- Modules/ExtendedPlayerControl.cs | 3 ++- Modules/LocateArrow.cs | 6 ++++-- Modules/TargetArrow.cs | 6 ++++-- Roles/AddOns/Common/Sleuth.cs | 2 +- Roles/Neutral/Vulture.cs | 27 ++++++--------------------- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index d9c4af6462..7023c2ab4f 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -13,6 +13,7 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -1198,7 +1199,7 @@ public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPla public static bool IsHost(this InnerNetObject innerObject) => innerObject.OwnerId == AmongUsClient.Instance.HostId; public static bool IsHost(this byte playerId) => playerId.GetPlayer()?.OwnerId == AmongUsClient.Instance.HostId; public static bool IsModded(this PlayerControl player) => player != null && (player.AmOwner || player.IsHost() || Main.playerVersion.ContainsKey(player.GetClientId())); - public static bool IsNonHostModdedClient(this PlayerControl pc) => !pc.IsHost() && Main.playerVersion.ContainsKey(pc.GetClientId()); + public static bool IsNonHostModdedClient(this PlayerControl pc) => pc != null && !pc.IsHost() && Main.playerVersion.ContainsKey(pc.GetClientId()); /// ///プレイヤーのRoleBehaviourのGetPlayersInAbilityRangeSortedを実行し、戻り値を返します。 /// diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index e380772c82..84418fcff7 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -28,8 +28,10 @@ public static void Init() public static void SendRPC(int index, byte seerId, Vector3 vector3) { - var seer = Utils.GetPlayerById(seerId); - if (!AmongUsClient.Instance.AmHost || seer == null || seer.AmOwner) return; + if (!AmongUsClient.Instance.AmHost) return; + + var seer = seerId.GetPlayer(); + if (!seer.IsNonHostModdedClient()) return; var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable, seer.GetClientId()); writer.Write(false); writer.WritePacked(index); diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index 4f2566e39d..f612dc84ea 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -28,8 +28,10 @@ public static void Init() public static void SendRPC(int index, byte seerId, byte targetId = byte.MaxValue) { - var seer = Utils.GetPlayerById(seerId); - if (!AmongUsClient.Instance.AmHost || seer == null || seer.AmOwner) return; + if (!AmongUsClient.Instance.AmHost) return; + + var seer = seerId.GetPlayer(); + if (!seer.IsNonHostModdedClient()) return; var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Arrow, SendOption.Reliable, seer.GetClientId()); writer.Write(true); writer.WritePacked(index); diff --git a/Roles/AddOns/Common/Sleuth.cs b/Roles/AddOns/Common/Sleuth.cs index 869555ebc4..c57ba9579d 100644 --- a/Roles/AddOns/Common/Sleuth.cs +++ b/Roles/AddOns/Common/Sleuth.cs @@ -11,7 +11,7 @@ public class Sleuth : IAddon public void SetupCustomOption() { - Options.SetupAdtRoleOptions(Id, CustomRoles.Sleuth, canSetNum: true); + Options.SetupAdtRoleOptions(Id, CustomRoles.Sleuth, canSetNum: true, teamSpawnOptions: true); SleuthCanKnowKillerRole = BooleanOptionItem.Create(Id + 13, "SleuthCanKnowKillerRole", true, TabGroup.Addons, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sleuth]); } diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index 7650fc97e8..7de01e4459 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -55,10 +55,10 @@ public override void Add(byte playerId) AbilityLeftInRound[playerId] = MaxEaten.GetInt(); LastReport[playerId] = GetTimeStamp(); + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); + if (AmongUsClient.Instance.AmHost) { - CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); - _ = new LateTask(() => { if (GameStates.IsInTask) @@ -161,7 +161,7 @@ private static void OnEatDeadBody(PlayerControl pc, NetworkedPlayerInfo target) { foreach (var apc in playerIdList) { - LocateArrow.Remove(apc, target.Object.transform.position); + LocateArrow.Remove(apc, target.GetDeadBody().transform.position); } } SendBodyRPC(pc.PlayerId); @@ -208,31 +208,16 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe if (inMeeting || target.IsDisconnected()) return; if (!ArrowsPointingToDeadBody.GetBool()) return; - Vector2 pos = target.transform.position; - float minDis = float.MaxValue; - - foreach (var pc in Main.AllAlivePlayerControls) - { - if (pc.PlayerId == target.PlayerId) continue; - var dis = GetDistance(pc.transform.position, pos); - if (dis < minDis && dis < 1.5f) - { - minDis = dis; - } - } - foreach (var pc in playerIdList.ToArray()) { - var player = GetPlayerById(pc); + var player = pc.GetPlayer(); if (player == null || !player.IsAlive()) continue; - LocateArrow.Add(pc, target.transform.position); + LocateArrow.Add(pc, target.Data.GetDeadBody().transform.position); } } public override string GetSuffix(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) { - if (seer == null || isForMeeting) return string.Empty; - if (!seer.Is(CustomRoles.Vulture)) return string.Empty; - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + if (isForMeeting || seer.PlayerId != target.PlayerId) return string.Empty; return ColorString(Color.white, LocateArrow.GetArrows(seer)); } From d5ba03902ffe64d4c655270a956e055e617ca0fa Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 15:21:09 +0800 Subject: [PATCH 729/778] Lot of fixes --- Modules/CustomRolesHelper.cs | 7 +++++-- Patches/LobbyPatch.cs | 4 ++++ Patches/MeetingHudPatch.cs | 2 +- Patches/PlayerControlPatch.cs | 4 ++-- Resources/Lang/en_US.json | 6 +++--- Roles/Crewmate/Swapper.cs | 20 ++++++-------------- Roles/Impostor/Inhibitor.cs | 3 ++- Roles/Impostor/Saboteur.cs | 12 ++++++++++-- Roles/Neutral/Executioner.cs | 6 +++--- Roles/Neutral/Innocent.cs | 6 ++++++ Roles/Neutral/Vector.cs | 13 ++++++++----- 11 files changed, 50 insertions(+), 33 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 04c35c7445..a2458126a8 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -888,9 +888,11 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Wildling) || pc.Is(CustomRoles.KillingMachine) || pc.Is(CustomRoles.Lurker) - || pc.Is(CustomRoles.Miner)) + || pc.Is(CustomRoles.Miner) + || pc.Is(CustomRoles.Prohibited) + || pc.Is(CustomRoles.DoubleAgent)) return false; - if (!pc.GetCustomRole().IsImpostor()) + if (!pc.Is(Custom_Team.Impostor)) return false; break; @@ -969,6 +971,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if (Prohibited.GetCountBlokedVents() <= 0 || !pc.CanUseVents()) return false; if (pc.Is(CustomRoles.Ventguard) + || pc.Is(CustomRoles.Circumvent) || pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool()) return false; break; diff --git a/Patches/LobbyPatch.cs b/Patches/LobbyPatch.cs index 7b86c2113a..a48ae5b1b2 100644 --- a/Patches/LobbyPatch.cs +++ b/Patches/LobbyPatch.cs @@ -91,6 +91,10 @@ public static void Update_Postfix(LobbyBehaviour __instance) public static class HostInfoPanelUpdatePatch { private static TextMeshPro HostText; + public static bool Prefix() + { + return GameStates.IsLobby; + } public static void Postfix(HostInfoPanel __instance) { try diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index cc50ba7e20..43e124e5fa 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -135,7 +135,7 @@ public static bool Prefix(MeetingHud __instance) { Aware.OnVoted(pc, pva); } - else if (voteTarget.Is(CustomRoles.Rebirth)) + if (voteTarget.Is(CustomRoles.Rebirth)) { Rebirth.CountVotes(voteTarget.PlayerId, pva.TargetPlayerId); } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index d04b16d187..773e135676 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -145,9 +145,9 @@ public static bool CheckForInvalidMurdering(PlayerControl killer, PlayerControl } // Is the target in a killable state? - if (target.Data == null // Check if PlayerData is not null - // Check target status + if (target.Data == null // Check if PlayerData is null || target.inVent + || target.onLadder || target.inMovingPlat // Moving Platform on Airhip and Zipline on Fungle || target.MyPhysics.Animations.IsPlayingEnterVentAnimation() || target.MyPhysics.Animations.IsPlayingAnyLadderAnimation() diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 92aef3d42a..1e67ced2bb 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -770,8 +770,8 @@ "PenguinInfoLong": "(Impostors):\nAs the Penguin, you can restrain the target by pressing the kill button and drag it around.\nWhile dragging, the target dies by pressing the kill button again or after a certain period.\nPress the kill button twice for a direct kill.", "ParasiteInfoLong": "(Team Impostor):\nAs the Parasite, you are an Impostor that does not know the other Impostors.\n\nYou may kill, vent, sabotage, whatever.\nJust know that you are an Impostor.", "DisperserInfoLong": "(Impostors):\nDisperser can use Shapeshift to teleport all players to random vents.", - "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Lights or Reactor), you cannot kill.", - "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf a critical sabotage is active (e.g., Comms or O2), then you can kill.", + "InhibitorInfoLong": "(Impostors):\nAs the Inhibitor, you can only kill when there is not a critical sabotage active.\n\nIf light or comms sabotage is active, then you can kill.", + "SaboteurInfoLong": "(Impostors):\nAs the Saboteur, you can only kill when there is a critical sabotage active.\n\nIf reactor or O2 sabotage is active, then you cannot kill.", "CouncillorInfoLong": "(Impostors):\nAs the Councillor, you can kill players during a meeting like a Judge.\nWhen killing in a meeting, those kills will appear as a trial from a Judge.\n\nThe kill command is /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nDepending on the settings, Councillor will suicide when he judge his teammates.\nConverted Councillor can judge freely.", "DazzlerInfoLong": "(Impostors):\nAs the Dazzler, you can reduce the vision of the target of your Shapeshift permanently. When you die, their vision will turn back to normal.", "DeathpactInfoLong": "(Impostors):\nAs the Deathpact, You shapeshift to mark your targets for a deathpact.\nIf you have enough players marked for a death pact, they must meet within a specific period; if they fail to do so, they die.\nIf a marked player dies before the death pact becomes complete, the pact is withdrawn.", @@ -975,7 +975,7 @@ "GravestoneInfoLong": "(Add-ons):\nAs the Gravestone, your role is revealed to everyone when you die.", "LazyInfoLong": "(Add-ons):\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.", "AutopsyInfoLong": "(Add-ons):\nAs the Autopsy, you can see how people died.\n\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.", - "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with someone and thrive once more. \n\nNotice: Rebirth will be removed from you if you exhausted all your rebirths.", + "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with a random crewmate who voted for you.\nNotice: The host vote never counts\nRebirth will be removed from you if you exhausted all your rebirths.", "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", diff --git a/Roles/Crewmate/Swapper.cs b/Roles/Crewmate/Swapper.cs index 3998fb993d..e47e827ea8 100644 --- a/Roles/Crewmate/Swapper.cs +++ b/Roles/Crewmate/Swapper.cs @@ -230,7 +230,6 @@ public static void CheckSwapperTarget(byte deadid) { if (deadid == 253) return; - foreach (var pid in PlayerIdList) { if (!Vote.TryGetValue(pid, out var tid1) || !VoteTwo.TryGetValue(pid, out var tid2)) continue; @@ -357,12 +356,12 @@ private static void SendSwapRPC(byte playerId) public static void ReceiveSwapRPC(MessageReader reader, PlayerControl pc) { byte PlayerId = reader.ReadByte(); - if (Main.PlayerStates[pc.PlayerId].RoleClass is Swapper sw) sw.SwapMsg(pc, $"/sw {PlayerId}"); + if (pc.GetRoleClass() is Swapper sw) sw.SwapMsg(pc, $"/sw {PlayerId}", true); } private void SwapperOnClick(byte playerId, MeetingHud __instance) { Logger.Msg($"Click: ID {playerId}", "Swapper UI"); - var pc = Utils.GetPlayerById(playerId); + var pc = playerId.GetPlayer(); if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; if (AmongUsClient.Instance.AmHost) SwapMsg(PlayerControl.LocalPlayer, $"/sw {playerId}", true); @@ -405,25 +404,18 @@ public void CreateSwapperButton(MeetingHud __instance) { foreach (var pva in __instance.playerStates) { - var pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pva.transform.Find("SwapButton") != null) UnityEngine.Object.Destroy(pva.transform.Find("SwapButton").gameObject); + + var pc = pva.TargetPlayerId.GetPlayer(); var local = PlayerControl.LocalPlayer; if (pc == null || !pc.IsAlive()) continue; GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; GameObject targetBox = UnityEngine.Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; + targetBox.name = "SwapButton"; targetBox.transform.localPosition = new Vector3(-0.35f, 0.03f, -1.31f); SpriteRenderer renderer = targetBox.GetComponent(); PassiveButton button = targetBox.GetComponent(); - //if (pc.PlayerId == pva.TargetPlayerId && (Vote[local.PlayerId] == pc.PlayerId || VoteTwo[local.PlayerId] == pc.PlayerId)) - //{ - // renderer.sprite = CustomButton.Get("SwapYes"); - //} - //else - //{ - // renderer.sprite = CustomButton.Get("SwapNo"); - //} - //Button state here doesnt work bcz vote and vote2 arent synced to clients renderer.sprite = CustomButton.Get("SwapNo"); button.OnClick.RemoveAllListeners(); diff --git a/Roles/Impostor/Inhibitor.cs b/Roles/Impostor/Inhibitor.cs index 149f2545a4..06243a3e63 100644 --- a/Roles/Impostor/Inhibitor.cs +++ b/Roles/Impostor/Inhibitor.cs @@ -31,5 +31,6 @@ public override void Add(byte playerId) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = InhibitorCD.GetFloat(); - public override bool CanUseKillButton(PlayerControl pc) => !Utils.AnySabotageIsActive(); + public override bool CanUseKillButton(PlayerControl pc) + => !Saboteur.IsCriticalSabotage(); } \ No newline at end of file diff --git a/Roles/Impostor/Saboteur.cs b/Roles/Impostor/Saboteur.cs index a89580cac3..3b374bb6ba 100644 --- a/Roles/Impostor/Saboteur.cs +++ b/Roles/Impostor/Saboteur.cs @@ -1,4 +1,6 @@ -namespace TOHE.Roles.Impostor; +using static TOHE.Utils; + +namespace TOHE.Roles.Impostor; internal class Saboteur : RoleBase { @@ -31,5 +33,11 @@ public override void Add(byte playerId) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = SaboteurCD.GetFloat(); - public override bool CanUseKillButton(PlayerControl pc) => Utils.AnySabotageIsActive(); + public override bool CanUseKillButton(PlayerControl pc) => IsCriticalSabotage(); + + public static bool IsCriticalSabotage() + => IsActive(SystemTypes.Laboratory) + || IsActive(SystemTypes.LifeSupp) + || IsActive(SystemTypes.Reactor) + || IsActive(SystemTypes.HeliSabotage); } \ No newline at end of file diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 3c622ff1b1..17ef45a56e 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -74,12 +74,12 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - + + CustomRoleManager.CheckDeadBodyOthers.Add(OnOthersDead); + var executioner = _Player; if (AmongUsClient.Instance.AmHost && executioner.IsAlive()) { - CustomRoleManager.CheckDeadBodyOthers.Add(OnOthersDead); - List targetList = []; var rand = IRandom.Instance; foreach (var target in Main.AllPlayerControls) diff --git a/Roles/Neutral/Innocent.cs b/Roles/Neutral/Innocent.cs index e4823b8465..fb92ff17c5 100644 --- a/Roles/Neutral/Innocent.cs +++ b/Roles/Neutral/Innocent.cs @@ -16,6 +16,7 @@ internal class Innocent : RoleBase //==================================================================\\ private static OptionItem InnocentCanWinByImp; + private bool TargetIsKilled = false; public override void SetupCustomOption() { @@ -26,20 +27,25 @@ public override void SetupCustomOption() public override void Init() { PlayerIds.Clear(); + TargetIsKilled = false; } public override void Add(byte playerId) { PlayerIds.Add(playerId); + TargetIsKilled = false; } public override bool CanUseKillButton(PlayerControl pc) => true; public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { + TargetIsKilled = true; target.RpcMurderPlayer(killer); return false; } public override void CheckExileTarget(NetworkedPlayerInfo exiled, ref bool DecidedWinner, bool isMeetingHud, ref string name) { + if (exiled == null || !TargetIsKilled) return; + var exiledRole = exiled.GetCustomRole(); var innocentArray = Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId); diff --git a/Roles/Neutral/Vector.cs b/Roles/Neutral/Vector.cs index a1f51a5c16..ad286fb678 100644 --- a/Roles/Neutral/Vector.cs +++ b/Roles/Neutral/Vector.cs @@ -41,19 +41,20 @@ public override void Init() } public override void Add(byte playerId) { - VectorVentCount.Add(playerId, 0); + VectorVentCount[playerId] = 0; PlayerIds.Add(playerId); } private void SendRPC() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + if (_Player.IsNonHostModdedClient()) return; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, _Player.GetClientId()); writer.WriteNetObject(_Player); - writer.Write(VectorVentCount[_Player.PlayerId]); + writer.WritePacked(VectorVentCount[_Player.PlayerId]); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { - int count = reader.ReadInt32(); + int count = reader.ReadPackedInt32(); VectorVentCount[_Player.PlayerId] = count; } public override string GetProgressText(byte playerId, bool comms) @@ -67,10 +68,12 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override void OnEnterVent(PlayerControl pc, Vent vent) { - VectorVentCount.TryAdd(pc.PlayerId, 0); VectorVentCount[pc.PlayerId]++; SendRPC(); NotifyRoles(SpecifySeer: pc); + + Logger.Info($"Vent count {VectorVentCount[pc.PlayerId]}", "Vector"); + if (VectorVentCount[pc.PlayerId] >= VectorVentNumWin.GetInt()) { if (!CustomWinnerHolder.CheckForConvertedWinner(pc.PlayerId)) From fb8ace6690e3b526cac85ba073ab492c03c32fba Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 16:02:12 +0800 Subject: [PATCH 730/778] Beta 2 --- main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cs b/main.cs index 3d017f6941..70749a2530 100644 --- a/main.cs +++ b/main.cs @@ -42,13 +42,13 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1004.210.010000"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Beta 1"; + public const string PluginVersion = "2024.1008.210.020000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Beta 2"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ public static readonly bool devRelease = false; // Latest: V2.1.0 Alpha 16 Hotfix 1 - public static readonly bool canaryRelease = true; // Latest: V2.1.0 Beta 1 + public static readonly bool canaryRelease = true; // Latest: V2.1.0 Beta 2 public static readonly bool fullRelease = false; // Latest: V2.0.3 public static bool hasAccess = true; From b6ad2251fb07f4f1a5d747fe82e6eaf5a077b0f7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 16:57:42 +0800 Subject: [PATCH 731/778] Some changes --- Modules/Utils.cs | 37 ++++++++++++++++++++------------- Patches/ShowHostMeetingPatch.cs | 22 +++++++------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index e56d016754..c163c30533 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1071,24 +1071,17 @@ public static string GetRegionName(IRegionInfo region = null) return name; } // From EHR by Gurge44 - public static void ThrowException(Exception ex, [CallerFilePath] string fileName = "", [CallerMemberName] string callerMemberName = "") + public static void ThrowException(Exception ex, [CallerFilePath] string fileName = "", [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string callerMemberName = "") { try { - // Get stack trace for the exception with source file information - var stEx = new StackTrace(ex, true); - // Get the top stack frame - var frame = stEx.GetFrame(0); - // Get the line number from the stack frame - var lineEx = frame.GetFileLineNumber(); - - StackTrace st = new(0, true); + StackTrace st = new(1, true); StackFrame[] stFrames = st.GetFrames(); StackFrame firstFrame = stFrames.FirstOrDefault(); var sb = new StringBuilder(); - sb.Append($" Exception: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {lineEx}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); + sb.Append($" {ex.GetType().Name}: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName.Split('\\')[^1]}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); bool skip = true; foreach (StackFrame sf in stFrames) @@ -1104,15 +1097,29 @@ public static void ThrowException(Exception ex, [CallerFilePath] string fileName string callerMethodName = callerMethod?.Name; string callerClassName = callerMethod?.DeclaringType?.FullName; - var line = $"line {sf.GetFileLineNumber()} ({sf.GetFileColumnNumber()}) in {sf.GetFileName()}"; - - sb.Append($"\n at {callerClassName}.{callerMethodName} ({line})"); + sb.Append($"\n at {callerClassName}.{callerMethodName}"); } sb.Append("\n------ End of Method Stack Trace ------"); - sb.Append("\n------ Exception Stack Trace ------"); + sb.Append("\n------ Exception ------\n "); + + sb.Append(ex.StackTrace?.Replace("\r\n", "\n").Replace("\\n", "\n").Replace("\n", "\n ")); + + sb.Append("\n------ End of Exception ------"); + sb.Append("\n------ Exception Stack Trace ------\n"); + + var stEx = new StackTrace(ex, true); + var stFramesEx = stEx.GetFrames(); - sb.Append(ex.StackTrace?.Replace("\r\n", "\n").Replace("\\n", "\n").Replace("\n", "\n ")); + foreach (StackFrame sf in stFramesEx) + { + var callerMethod = sf.GetMethod(); + + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + + sb.Append($"\n at {callerClassName}.{callerMethodName} in {sf.GetFileName()}, line {sf.GetFileLineNumber()}"); + } sb.Append("\n------ End of Exception Stack Trace ------"); diff --git a/Patches/ShowHostMeetingPatch.cs b/Patches/ShowHostMeetingPatch.cs index d091ce6e5d..97c6863e0a 100644 --- a/Patches/ShowHostMeetingPatch.cs +++ b/Patches/ShowHostMeetingPatch.cs @@ -9,8 +9,8 @@ namespace TOHE.Patches; public class ShowHostMeetingPatch { private static PlayerControl HostControl = null; - private static string hostName = string.Empty; - private static int hostColor = int.MaxValue; + private static string HostName = string.Empty; + private static int HostColor = int.MaxValue; [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.OnDestroy))] [HarmonyPostfix] @@ -21,14 +21,8 @@ public static void OnDestroy_Postfix() if (GameStates.IsInGame && HostControl == null) { HostControl = AmongUsClient.Instance.GetHost().Character; - hostName = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.PlayerName; - hostColor = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.ColorId; - - if (Main.OvverideOutfit.ContainsKey(AmongUsClient.Instance.GetHost().Character.PlayerId)) - { - hostName = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.PlayerName; - hostColor = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.ColorId; - } + HostName = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.PlayerName; + HostColor = Main.PlayerStates[AmongUsClient.Instance.GetHost().Character.Data.PlayerId].NormalOutfit.ColorId; } } catch { } @@ -39,8 +33,8 @@ public static void OnDestroy_Postfix() public static void ShowRole_Postfix() { HostControl = AmongUsClient.Instance.GetHost().Character; - hostName = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.PlayerName; - hostColor = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.ColorId; + HostName = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.PlayerName; + HostColor = AmongUsClient.Instance.GetHost().Character.CurrentOutfit.ColorId; } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] @@ -50,8 +44,8 @@ public static void Update_Postfix(MeetingHud __instance) // Not display in local game, because it will be impossible to complete the meeting if (!GameStates.IsOnlineGame) return; - PlayerMaterial.SetColors(hostColor, __instance.HostIcon); - __instance.ProceedButton.gameObject.GetComponentInChildren().text = string.Format(Translator.GetString("HostIconInMeeting"), hostName); + PlayerMaterial.SetColors(HostColor, __instance.HostIcon); + __instance.ProceedButton.gameObject.GetComponentInChildren().text = string.Format(Translator.GetString("HostIconInMeeting"), HostName); } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] From 1d9fa37e9e83e6e35bac8e36525b7c7f74923c1d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 18:47:49 +0800 Subject: [PATCH 732/778] Change size --- Patches/GameOptionsMenuPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index c09f1f0217..12c29374b5 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -651,7 +651,7 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) { var roleName = role.IsVanilla() ? role + "TOHE" : role.ToString(); var str = GetString($"{roleName}InfoLong"); - int size = str.Length > 510 ? 70 : 90; + int size = str.Length > 500 ? str.Length > 550 ? 65 : 70 : 100; var infoLong = str[(str.IndexOf('\n') + 1)..str.Length]; var ColorRole = Utils.ColorString(Utils.GetRoleColor(role), GetString(role.ToString())); var info = $"{ColorRole}: {infoLong}"; From c4b078938304194d5ca78c309fec438e302eb8ad Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 19:33:53 +0800 Subject: [PATCH 733/778] Action => UnityEngine.Events.UnityAction in AddListener() --- Modules/ClientOptionItem.cs | 6 +++--- Modules/GuessManager.cs | 8 ++++---- Modules/ModUpdater.cs | 4 ++-- Patches/GameOptionsMenuPatch.cs | 4 ++-- Patches/GameSettingMenuPatch.cs | 8 ++++---- Patches/GameStartManagerPatch.cs | 2 +- Patches/MainMenuManagerPatch.cs | 12 ++++++------ Patches/MapPickerMenuPatch.cs | 2 +- Roles/Crewmate/Judge.cs | 2 +- Roles/Crewmate/Retributionist.cs | 2 +- Roles/Crewmate/Swapper.cs | 2 +- Roles/Impostor/Councillor.cs | 2 +- Roles/Impostor/DoubleAgent.cs | 6 +++--- Roles/Impostor/Nemesis.cs | 2 +- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Modules/ClientOptionItem.cs b/Modules/ClientOptionItem.cs index dc371a2557..44d9ee6cae 100644 --- a/Modules/ClientOptionItem.cs +++ b/Modules/ClientOptionItem.cs @@ -43,7 +43,7 @@ private ClientOptionItem( closeButton.Background.color = Palette.DisabledGrey; var closePassiveButton = closeButton.GetComponent(); closePassiveButton.OnClick = new(); - closePassiveButton.OnClick.AddListener(new Action(() => + closePassiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { CustomBackground.gameObject.SetActive(false); })); @@ -69,7 +69,7 @@ private ClientOptionItem( modOptionsButton.Background.color = new Color32(255, 192, 203, byte.MaxValue); var modOptionsPassiveButton = modOptionsButton.GetComponent(); modOptionsPassiveButton.OnClick = new(); - modOptionsPassiveButton.OnClick.AddListener(new Action(() => + modOptionsPassiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { CustomBackground.gameObject.SetActive(true); })); @@ -91,7 +91,7 @@ private ClientOptionItem( ToggleButton.Text.text = Translator.GetString(name); var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new(); - passiveButton.OnClick.AddListener(new Action(() => + passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { if (config != null) config.Value = !config.Value; UpdateToggle(); diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index a713b89422..b89db6b70d 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -621,7 +621,7 @@ public static void CreateGuesserButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("TargetIcon"); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => GuesserOnClick(pva.TargetPlayerId, __instance))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => GuesserOnClick(pva.TargetPlayerId, __instance))); } } @@ -694,7 +694,7 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) exitButtonParent.transform.localScale = new Vector3(0.22f, 0.9f, 1f); exitButtonParent.transform.SetAsFirstSibling(); exitButton.GetComponent().OnClick.RemoveAllListeners(); - exitButton.GetComponent().OnClick.AddListener((Action)(() => + exitButton.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { __instance.playerStates.ToList().ForEach(x => { @@ -825,7 +825,7 @@ static void CreatePage(bool IsNext, MeetingHud __instance, Transform container) 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())); + Pagebutton.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => ClickEvent())); void ClickEvent() { if (IsNext) Page += 1; @@ -993,7 +993,7 @@ void CreateRole(CustomRoles role) //int copiedIndex = info[(int)role.GetCustomRoleTeam()]; button.GetComponent().OnClick.RemoveAllListeners(); - if (PlayerControl.LocalPlayer.IsAlive()) button.GetComponent().OnClick.AddListener((Action)(() => + if (PlayerControl.LocalPlayer.IsAlive()) button.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { if (selectedButton != button) { diff --git a/Modules/ModUpdater.cs b/Modules/ModUpdater.cs index 43c97d11d5..7b9e724f6a 100644 --- a/Modules/ModUpdater.cs +++ b/Modules/ModUpdater.cs @@ -68,7 +68,7 @@ public static void ResetUpdateButton() new(3.68f, -2.68f, 1f), new(255, 165, 0, byte.MaxValue), new(255, 200, 0, byte.MaxValue), - () => StartUpdate(downloadUrl), + (UnityEngine.Events.UnityAction)(() => StartUpdate(downloadUrl)), GetString("update")); updateButton.transform.localScale = Vector3.one; } @@ -305,7 +305,7 @@ private static void ShowPopup(string message, StringNames buttonText, bool showB button.GetChild(0).GetComponent().ResetText(); button.GetComponent().OnClick = new(); if (onClick != null) button.GetComponent().OnClick.AddListener(onClick); - else button.GetComponent().OnClick.AddListener((Action)(() => InfoPopup.Close())); + else button.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => InfoPopup.Close())); } } } diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 12c29374b5..18be5af405 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -66,7 +66,7 @@ private static void InitializePostfix() var menuDescriptionText = GameSettingMenu.Instance.MenuDescriptionText; menuDescriptionText.m_marginWidth = 2.5f; - }, 0.1f, "Set Menu", shoudLog: false); + }, 0.2f, "Set Menu", shoudLog: false); } [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings)), HarmonyPrefix] @@ -641,7 +641,7 @@ private static void SetupHelpIcon(CustomRoles role, StringOption __instance) icon.FindChild("ButtonSprite").GetComponent().color = clr; var GameOptionsButton = icon.GetComponent(); GameOptionsButton.OnClick = new(); - GameOptionsButton.OnClick.AddListener((Action)(() => { + GameOptionsButton.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index)) { diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 18c57c3a48..bf5d5764ef 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -71,7 +71,7 @@ public static void StartPostfix(GameSettingMenu __instance) var buttonComponent = button.GetComponent(); buttonComponent.OnClick = new(); buttonComponent.OnClick.AddListener( - (Action)(() => __instance.ChangeTab((int)tab + 3, false))); + (UnityEngine.Events.UnityAction)(() => __instance.ChangeTab((int)tab + 3, false))); ModSettingsButtons.Add(tab, button); } @@ -177,7 +177,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) var Minus = GMinus.GetComponent(); Minus.OnClick.RemoveAllListeners(); Minus.OnClick.AddListener( - (Action)(() => { + (UnityEngine.Events.UnityAction)(() => { if (PresetBehaviour == null) __instance.ChangeTab(3, false); PresetBehaviour.Decrease(); })); @@ -209,7 +209,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) var plus = PlusFab.GetComponent(); plus.OnClick.RemoveAllListeners(); plus.OnClick.AddListener( - (Action)(() => { + (UnityEngine.Events.UnityAction)(() => { if (PresetBehaviour == null) __instance.ChangeTab(3, false); PresetBehaviour.Increase(); })); @@ -258,7 +258,7 @@ private static void SetupAdittionalButtons(GameSettingMenu __instance) passiveButton.OnClick = new(); passiveButton.OnClick.AddListener( - (Action)(() => { + (UnityEngine.Events.UnityAction)(() => { SearchForOptions(TextField); })); diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs index f09c472332..dfbe8a5e1c 100644 --- a/Patches/GameStartManagerPatch.cs +++ b/Patches/GameStartManagerPatch.cs @@ -87,7 +87,7 @@ public static void Postfix(GameStartManager __instance) //cancelButton.transform.localPosition = new(2f, 0.13f, 0f); GameStartTextlocalPosition = __instance.GameStartText.transform.localPosition; cancelButton.OnClick = new(); - cancelButton.OnClick.AddListener((Action)(() => + cancelButton.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { __instance.ResetStartState(); })); diff --git a/Patches/MainMenuManagerPatch.cs b/Patches/MainMenuManagerPatch.cs index f713bcdb14..f5e3cdd730 100644 --- a/Patches/MainMenuManagerPatch.cs +++ b/Patches/MainMenuManagerPatch.cs @@ -231,7 +231,7 @@ public static void Start_Postfix(MainMenuManager __instance) new(-1.8f, -1.1f, 1f), new(0, 255, 255, byte.MaxValue), new(75, 255, 255, byte.MaxValue), - () => Application.OpenURL(Main.DonationInviteUrl), + (UnityEngine.Events.UnityAction)(() => Application.OpenURL(Main.DonationInviteUrl)), GetString("SupportUs")); //"Donation" } donationButton.gameObject.SetActive(Main.ShowDonationButton); @@ -244,7 +244,7 @@ public static void Start_Postfix(MainMenuManager __instance) new(-1.8f, -1.5f, 1f), new(153, 153, 153, byte.MaxValue), new(209, 209, 209, byte.MaxValue), - () => Application.OpenURL(Main.GitHubInviteUrl), + (UnityEngine.Events.UnityAction)(() => Application.OpenURL(Main.GitHubInviteUrl)), GetString("GitHub")); //"GitHub" } gitHubButton.gameObject.SetActive(Main.ShowGitHubButton); @@ -257,7 +257,7 @@ public static void Start_Postfix(MainMenuManager __instance) new(-1.8f, -1.9f, 1f), new(88, 101, 242, byte.MaxValue), new(148, 161, byte.MaxValue, byte.MaxValue), - () => Application.OpenURL(Main.DiscordInviteUrl), + (UnityEngine.Events.UnityAction)(() => Application.OpenURL(Main.DiscordInviteUrl)), GetString("Discord")); //"Discord" } discordButton.gameObject.SetActive(Main.ShowDiscordButton); @@ -270,7 +270,7 @@ public static void Start_Postfix(MainMenuManager __instance) new(-1.8f, -2.3f, 1f), new(251, 81, 44, byte.MaxValue), new(211, 77, 48, byte.MaxValue), - () => Application.OpenURL(Main.WebsiteInviteUrl), + (UnityEngine.Events.UnityAction)(() => Application.OpenURL(Main.WebsiteInviteUrl)), GetString("Website")); //"Website" } websiteButton.gameObject.SetActive(Main.ShowWebsiteButton); @@ -284,7 +284,7 @@ public static void Start_Postfix(MainMenuManager __instance) } - public static PassiveButton CreateButton(string name, Vector3 localPosition, Color32 normalColor, Color32 hoverColor, Action action, string label, Vector2? scale = null) + public static PassiveButton CreateButton(string name, Vector3 localPosition, Color32 normalColor, Color32 hoverColor, UnityEngine.Events.UnityAction action, string label, Vector2? scale = null) { var button = Object.Instantiate(template, MainMenuManagerStartPatch.ToheLogo.transform); button.name = name; @@ -321,7 +321,7 @@ public static PassiveButton CreateButton(string name, Vector3 localPosition, Col return button; } - public static void Modify(this PassiveButton passiveButton, Action action) + public static void Modify(this PassiveButton passiveButton, UnityEngine.Events.UnityAction action) { if (passiveButton == null) return; passiveButton.OnClick = new Button.ButtonClickedEvent(); diff --git a/Patches/MapPickerMenuPatch.cs b/Patches/MapPickerMenuPatch.cs index 5f5d77942e..07cb36442f 100644 --- a/Patches/MapPickerMenuPatch.cs +++ b/Patches/MapPickerMenuPatch.cs @@ -28,7 +28,7 @@ public static void Postfix_Initialize(GameOptionsMapPicker __instance) DleksButton = dlekS_ehT_MapButton; dlekS_ehT_MapButton.MapIcon.transform.localScale = new Vector3(-1f, 1f, 1f); dlekS_ehT_MapButton.Button.OnClick.RemoveAllListeners(); - dlekS_ehT_MapButton.Button.OnClick.AddListener((Action)(() => + dlekS_ehT_MapButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { __instance.SelectMap(__instance.AllMapIcons[0]); diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 8792a69ff6..5e0b59f1c3 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -352,7 +352,7 @@ public static void CreateJudgeButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("JudgeIcon"); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => JudgeOnClick(pva.TargetPlayerId/*, __instance*/))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => JudgeOnClick(pva.TargetPlayerId/*, __instance*/))); } } } diff --git a/Roles/Crewmate/Retributionist.cs b/Roles/Crewmate/Retributionist.cs index fa1b374686..af490ae92b 100644 --- a/Roles/Crewmate/Retributionist.cs +++ b/Roles/Crewmate/Retributionist.cs @@ -242,7 +242,7 @@ public static void CreateJudgeButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("MeetingKillButton"); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => RetributionistOnClick(pva.TargetPlayerId/*, __instance*/))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => RetributionistOnClick(pva.TargetPlayerId/*, __instance*/))); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Swapper.cs b/Roles/Crewmate/Swapper.cs index e47e827ea8..54971de43a 100644 --- a/Roles/Crewmate/Swapper.cs +++ b/Roles/Crewmate/Swapper.cs @@ -419,7 +419,7 @@ public void CreateSwapperButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("SwapNo"); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => { + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { SwapperOnClick(pva.TargetPlayerId, __instance); })); } diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index 6d3feeab70..2baa287df2 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -375,7 +375,7 @@ private static void CreateCouncillorButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("MeetingKillButton"); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => CouncillorOnClick(pva.TargetPlayerId/*, __instance*/))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => CouncillorOnClick(pva.TargetPlayerId/*, __instance*/))); } } diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 7fab68f270..2594e586a4 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -331,9 +331,9 @@ public static void CreatePlantBombButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("DoubleAgentPocketBomb"); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => DestroyButtons(targetBox))); - button.OnClick.AddListener((Action)(() => PlantBombOnClick(pva.TargetPlayerId /*, __instance*/))); - button.OnClick.AddListener((Action)(() => CustomSoundsManager.Play("Line"))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => DestroyButtons(targetBox))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => PlantBombOnClick(pva.TargetPlayerId /*, __instance*/))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => CustomSoundsManager.Play("Line"))); } } diff --git a/Roles/Impostor/Nemesis.cs b/Roles/Impostor/Nemesis.cs index 25a83a9e43..69dbaf32e6 100644 --- a/Roles/Impostor/Nemesis.cs +++ b/Roles/Impostor/Nemesis.cs @@ -252,7 +252,7 @@ public static void CreateJudgeButton(MeetingHud __instance) renderer.sprite = CustomButton.Get("MeetingKillButton"); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => NemesisOnClick(pva.TargetPlayerId/*, __instance*/))); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => NemesisOnClick(pva.TargetPlayerId/*, __instance*/))); } } } \ No newline at end of file From dc2a9e146c6c321985bec6908468fa87d9a5f356 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Tue, 8 Oct 2024 21:03:12 +0800 Subject: [PATCH 734/778] Fix Executioner & Lawyer Intro cutscene --- Patches/IntroPatch.cs | 22 ++++++++++------------ Roles/Neutral/Executioner.cs | 3 ++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index e48a5e7873..c0855c1005 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -327,28 +327,26 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections teamToDisplay.Add(PlayerControl.LocalPlayer); } - if (PlayerControl.LocalPlayer.Is(CustomRoles.Executioner)) + if (PlayerControl.LocalPlayer.GetRoleClass() is Executioner ex) { var exeTeam = new Il2CppSystem.Collections.Generic.List(); exeTeam.Add(PlayerControl.LocalPlayer); - foreach (var executionId in Executioner.TargetList) - { - PlayerControl executing = executionId.GetPlayer(); - if (executing == null) continue; + + PlayerControl executing = ex.GetTargetId().GetPlayer(); + if (executing != null) exeTeam.Add(executing); - } + teamToDisplay = exeTeam; } - if (PlayerControl.LocalPlayer.Is(CustomRoles.Lawyer)) + if (PlayerControl.LocalPlayer.GetRoleClass() is Lawyer lw) { var lawyerTeam = new Il2CppSystem.Collections.Generic.List(); lawyerTeam.Add(PlayerControl.LocalPlayer); - foreach (var lawyerTargetId in Lawyer.TargetList) - { - PlayerControl lawyerTarget = lawyerTargetId.GetPlayer(); - if (lawyerTarget == null) continue; + + PlayerControl lawyerTarget = lw.GetTargetId().GetPlayer(); + if (lawyerTarget != null) lawyerTeam.Add(lawyerTarget); - } + teamToDisplay = lawyerTeam; } diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 17ef45a56e..4d0d12b6c8 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -68,7 +68,7 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - TargetList.Remove(TargetId); + TargetList.Clear(); TargetId = byte.MaxValue; } public override void Add(byte playerId) @@ -147,6 +147,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) } public bool IsTarget(byte playerId) => TargetId == playerId; + public byte GetTargetId() => TargetId; public override bool HasTasks(NetworkedPlayerInfo player, CustomRoles role, bool ForRecompute) => !(ChangeRolesAfterTargetKilled.GetValue() is 6 or 7) && !ForRecompute; From 0bb413afb7489c53d773b861c9e0d48ec1b78376 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 15:15:59 +0800 Subject: [PATCH 735/778] Some fix & add DefaultOutfit.SequenceIs += 10 to prevent bugs which player outfits --- Modules/ExtendedPlayerControl.cs | 1 + Modules/OutfitManager.cs | 24 +++++++++++++++++++----- Modules/RPC.cs | 14 ++++++++++++++ Patches/PlayerControlPatch.cs | 3 +-- Roles/Core/CustomRoleManager.cs | 12 +++++++++--- Roles/Crewmate/Mortician.cs | 21 ++------------------- Roles/Impostor/DollMaster.cs | 6 +++++- Roles/Neutral/Baker.cs | 5 ++--- Roles/Neutral/PlagueBearer.cs | 2 +- 9 files changed, 54 insertions(+), 34 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 7023c2ab4f..0992f8b879 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -266,6 +266,7 @@ public static void RpcSetPetDesync(this PlayerControl player, string petId, Play player.SetPet(petId); return; } + player.Data.DefaultOutfit.PetSequenceId += 10; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetPetStr, SendOption.Reliable, clientId); writer.Write(petId); writer.Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)); diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index 427c75c74e..8fdbf1914f 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -26,30 +26,35 @@ void Setoutfit() .EndRpc(); player.SetHat(Outfit.HatId, Outfit.ColorId); + player.Data.DefaultOutfit.HatSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); player.SetSkin(Outfit.SkinId, Outfit.ColorId); + player.Data.DefaultOutfit.SkinSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(Outfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); player.SetVisor(Outfit.VisorId, Outfit.ColorId); + player.Data.DefaultOutfit.VisorSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(Outfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); player.SetPet(Outfit.PetId); + player.Data.DefaultOutfit.PetSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(Outfit.PetId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) .EndRpc(); player.SetNamePlate(Outfit.NamePlateId); + player.Data.DefaultOutfit.NamePlateSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) .Write(Outfit.NamePlateId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) @@ -90,7 +95,7 @@ void Setoutfit() public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit newOutfit, bool setName = true, bool setNamePlate = true, uint newLevel = 500) { // Start to set Outfit - var sender = CustomRpcSender.Create(name: $"SetOutfit({player.Data.PlayerName})"); + var sender = CustomRpcSender.Create(name: $"SetOutfit({player.Data.PlayerName})", sendOption: Hazel.SendOption.Reliable); if (setName) { @@ -105,6 +110,8 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P RPC.SyncAllPlayerNames(); } + // Set SequenceId += 10 because stupid code sometimes not sets outfit players + player.SetColor(newOutfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetColor) .Write(player.Data.NetId) @@ -112,32 +119,37 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P .EndRpc(); player.SetHat(newOutfit.HatId, newOutfit.ColorId); + player.Data.DefaultOutfit.HatSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) - .Write(newOutfit.HatId) + .Write(newOutfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); player.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + player.Data.DefaultOutfit.SkinSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) - .Write(newOutfit.SkinId) + .Write(newOutfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); player.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + player.Data.DefaultOutfit.VisorSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(newOutfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); player.SetPet(newOutfit.PetId); + player.Data.DefaultOutfit.PetSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(newOutfit.PetId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); + .EndRpc(); if (setNamePlate) { player.SetNamePlate(newOutfit.NamePlateId); + player.Data.DefaultOutfit.NamePlateSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) .Write(newOutfit.NamePlateId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) @@ -149,9 +161,11 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P player.SetLevel(newLevel); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetLevel) .WritePacked(newLevel) - .EndRpc(); + .EndRpc(); } sender.SendMessage(); + + player.Data.MarkDirty(); } } diff --git a/Modules/RPC.cs b/Modules/RPC.cs index b9d0a8b612..35000860ae 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -123,6 +123,20 @@ public enum Sounds Test, } +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ShouldProcessRpc))] +class ShouldProcessRpcPatch +{ + /* + * Sinse stupid AU code added check process rpc for outfit players, so need patch this + * Always return true because the check is absolutely pointless + */ + public static bool Prefix(PlayerControl __instance, RpcCalls rpc, byte sequenceId, ref bool __result) + { + Logger.Info($"{__instance.PlayerId} old skin sequenceId {__instance.Data.DefaultOutfit.SkinSequenceId} - new skin sequenceId {rpc} - sequenceId {sequenceId}", "Test"); + __result = true; + return false; + } +} [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] internal class RPCHandlerPatch { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 773e135676..94a60a27a6 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -27,7 +27,7 @@ class CheckProtectPatch public static bool Prefix(PlayerControl __instance, PlayerControl target) { if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek) return false; - Logger.Info("CheckProtect occurs: " + __instance.GetNameWithRole() + "=>" + target.GetNameWithRole(), "CheckProtect"); + Logger.Info($"{ __instance.GetNameWithRole()} => {target.GetNameWithRole()}", "CheckProtect"); var angel = __instance; if (AntiBlackout.SkipTasks) @@ -278,7 +278,6 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, // Check murder on others targets if (CustomRoleManager.OnCheckMurderAsTargetOnOthers(killer, target) == false) { - Logger.Info("Cancels because for others target need cancel kill", "OnCheckMurderAsTargetOnOthers"); return false; } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 4385eafe30..883652964a 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -74,9 +74,15 @@ public static RoleBase CreateRoleClass(this CustomRoles role) /// public static bool OnCheckMurderAsTargetOnOthers(PlayerControl killer, PlayerControl target) { - // return true when need to cancel the kill target - // "Any()" defines a function that returns true, and converts to false to cancel the kill - return !AllEnabledRoles.Any(RoleClass => RoleClass.CheckMurderOnOthersTarget(killer, target) == true); + foreach (var roleClass in AllEnabledRoles.ToArray()) + { + if (roleClass.CheckMurderOnOthersTarget(killer, target) == true) + { + Logger.Info($"Role class cancels kill: {roleClass}", "OnCheckMurderAsTargetOnOthers"); + return false; + } + } + return true; } /// diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index cd269cb1d8..236bb0e71d 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -34,10 +34,7 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); - if (AmongUsClient.Instance.AmHost) - { - CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); - } + CustomRoleManager.CheckDeadBodyOthers.Add(CheckDeadBody); } public override void Remove(byte playerId) { @@ -47,25 +44,11 @@ private void CheckDeadBody(PlayerControl killer, PlayerControl target, bool inMe { if (inMeeting || target.IsDisconnected()) return; - //Vector2 pos = target.transform.position; - //float minDis = float.MaxValue; - //string minName = ""; - //foreach (var pc in Main.AllAlivePlayerControls) - //{ - // if (pc.PlayerId == target.PlayerId || playerIdList.Any(p => p == pc.PlayerId)) continue; - // var dis = Utils.GetDistance(pc.transform.position, pos); - // if (dis < minDis && dis < 0.5f) - // { - // minDis = dis; - // minName = pc.GetRealName(clientData: true); - // } - //} - foreach (var pc in playerIdList.ToArray()) { var player = pc.GetPlayer(); if (player == null || !player.IsAlive()) continue; - LocateArrow.Add(pc, target.transform.position); + LocateArrow.Add(pc, target.Data.GetDeadBody().transform.position); } } public override void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target) diff --git a/Roles/Impostor/DollMaster.cs b/Roles/Impostor/DollMaster.cs index a96a5d08d4..796bc0fd04 100644 --- a/Roles/Impostor/DollMaster.cs +++ b/Roles/Impostor/DollMaster.cs @@ -379,6 +379,7 @@ private static void UnPossess(PlayerControl pc, PlayerControl target) pc?.RpcShapeshift(pc, false); RpcChangeSkin(pc); RpcChangeSkin(target); + RPC.SyncAllPlayerNames(); IsControllingPlayer = false; ResetPlayerSpeed = true; @@ -439,7 +440,6 @@ void Setoutfit() .Write(Outfit.PlayerName) .EndRpc(); - Main.AllPlayerNames[player.PlayerId] = Outfit.PlayerName; player.SetColor(Outfit.ColorId); @@ -450,6 +450,7 @@ void Setoutfit() .EndRpc(); player.SetHat(Outfit.HatId, Outfit.ColorId); + player.Data.DefaultOutfit.HatSequenceId += 10; player.Data.DefaultOutfit.HatId = Main.PlayerStates[player.PlayerId].NormalOutfit.HatId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) @@ -457,6 +458,7 @@ void Setoutfit() .EndRpc(); player.SetSkin(Outfit.SkinId, Outfit.ColorId); + player.Data.DefaultOutfit.SkinSequenceId += 10; player.Data.DefaultOutfit.SkinId = Main.PlayerStates[player.PlayerId].NormalOutfit.SkinId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(Outfit.SkinId) @@ -464,6 +466,7 @@ void Setoutfit() .EndRpc(); player.SetVisor(Outfit.VisorId, Outfit.ColorId); + player.Data.DefaultOutfit.VisorSequenceId += 10; player.Data.DefaultOutfit.VisorId = Main.PlayerStates[player.PlayerId].NormalOutfit.VisorId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(Outfit.VisorId) @@ -471,6 +474,7 @@ void Setoutfit() .EndRpc(); player.SetPet(Outfit.PetId); + player.Data.DefaultOutfit.PetSequenceId += 10; player.Data.DefaultOutfit.PetId = Main.PlayerStates[player.PlayerId].NormalOutfit.PetId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(Outfit.PetId) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 72a40eaebc..2e3679a50c 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -237,9 +237,8 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { - var baker = Utils.GetPlayerListByRole(CustomRoles.Baker); - if (killer == null || target == null || baker == null || !baker.Any()) return true; - if (!BarrierList[playerIdList.First()].Contains(target.PlayerId)) return false; + if (_Player == null || !_Player.IsAlive() || killer == null || target == null) return false; + if (!BarrierList[_Player.PlayerId].Contains(target.PlayerId)) return false; killer.RpcGuardAndKill(target); killer.ResetKillCooldown(); diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 96f3a4482d..4628ff9a76 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -245,8 +245,8 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { - killer.SetRealKiller(target); target.RpcMurderPlayer(killer); + killer.SetRealKiller(target); return false; } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) From 0820747a16e26b22969ea8e5334384f81f3f4f31 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 15:19:13 +0800 Subject: [PATCH 736/778] Remove & Change --- Modules/OutfitManager.cs | 4 +--- Modules/RPC.cs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index 8fdbf1914f..b81707d4ab 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -95,7 +95,7 @@ void Setoutfit() public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit newOutfit, bool setName = true, bool setNamePlate = true, uint newLevel = 500) { // Start to set Outfit - var sender = CustomRpcSender.Create(name: $"SetOutfit({player.Data.PlayerName})", sendOption: Hazel.SendOption.Reliable); + var sender = CustomRpcSender.Create(name: $"SetOutfit({player.Data.PlayerName})"); if (setName) { @@ -165,7 +165,5 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P } sender.SendMessage(); - - player.Data.MarkDirty(); } } diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 35000860ae..3e8ac854b2 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -132,7 +132,9 @@ class ShouldProcessRpcPatch */ public static bool Prefix(PlayerControl __instance, RpcCalls rpc, byte sequenceId, ref bool __result) { - Logger.Info($"{__instance.PlayerId} old skin sequenceId {__instance.Data.DefaultOutfit.SkinSequenceId} - new skin sequenceId {rpc} - sequenceId {sequenceId}", "Test"); + if (rpc is RpcCalls.SetSkinStr) + Logger.Info($"Player Id: {__instance.PlayerId} - Old skin sequenceId {__instance.Data.DefaultOutfit.SkinSequenceId} - New skin sequenceId {sequenceId}", "ShouldProcessRpc"); + __result = true; return false; } From dc94095bce31f527874f213d5d3106cb5b8e2a3f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 16:09:34 +0800 Subject: [PATCH 737/778] Check SnapTo for modded & Some patch & Fix RpcDesyncTeleport --- Modules/ExtendedPlayerControl.cs | 11 +++++++---- Patches/RandomSpawnPatch.cs | 11 +++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 0992f8b879..65954d9a97 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -771,13 +771,15 @@ public static void RpcDesyncTeleport(this PlayerControl player, Vector2 position if (player == null) return; var netTransform = player.NetTransform; var clientId = seer.GetClientId(); - ushort addSid = GameStates.IsLocalGame ? (ushort)4 : (ushort)40; if (AmongUsClient.Instance.ClientId == clientId) { - netTransform.SnapTo(position, (ushort)(netTransform.lastSequenceId + addSid)); + netTransform.SnapTo(position, (ushort)(6 + netTransform.lastSequenceId)); return; } - ushort newSid = (ushort)(netTransform.lastSequenceId + addSid); + netTransform.lastSequenceId += 326; + netTransform.SetDirtyBit(uint.MaxValue); + + ushort newSid = (ushort)(8 + netTransform.lastSequenceId); MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(netTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable, clientId); NetHelpers.WriteVector2(position, writer); writer.Write(newSid); @@ -821,11 +823,12 @@ public static void RpcTeleport(this PlayerControl player, Vector2 position, bool var netTransform = player.NetTransform; - if (AmongUsClient.Instance.AmClient) + if (AmongUsClient.Instance.AmHost) { // +328 because lastSequenceId has delay between the host and the vanilla client // And this cannot forced teleport the player netTransform.SnapTo(position, (ushort)(netTransform.lastSequenceId + 328)); + netTransform.SetDirtyBit(uint.MaxValue); } ushort newSid = (ushort)(netTransform.lastSequenceId + 8); diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs index 442b5d70e7..c9779ff629 100644 --- a/Patches/RandomSpawnPatch.cs +++ b/Patches/RandomSpawnPatch.cs @@ -9,6 +9,17 @@ namespace TOHE; // Thanks: https://github.com/tukasa0001/TownOfHost/blob/main/Patches/RandomSpawnPatch.cs class RandomSpawn { + [HarmonyPatch(typeof(CustomNetworkTransform), nameof(CustomNetworkTransform.SnapTo))] + [HarmonyPatch(new Type[] { typeof(Vector2), typeof(ushort) })] + public class SnapToPatch + { + public static void Prefix(CustomNetworkTransform __instance, [HarmonyArgument(1)] ushort minSid) + { + if (AmongUsClient.Instance.AmHost) return; + + Logger.Info($"Player Id {__instance.myPlayer.PlayerId} - old sequence {__instance.lastSequenceId} - new sequence {minSid}", "SnapToPatch"); + } + } [HarmonyPatch(typeof(CustomNetworkTransform), nameof(CustomNetworkTransform.HandleRpc))] public class CustomNetworkTransformHandleRpcPatch { From 483943bcc75adb5ad3cc7ec97d82c0edf4461203 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 16:23:50 +0800 Subject: [PATCH 738/778] Move code --- Modules/ExtendedPlayerControl.cs | 2 +- Modules/OutfitManager.cs | 20 ++++++++++---------- Roles/Impostor/DollMaster.cs | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 65954d9a97..b3941bdabc 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -261,12 +261,12 @@ public static void RpcSetPetDesync(this PlayerControl player, string petId, Play { var clientId = seer.GetClientId(); if (clientId == -1) return; + player.Data.DefaultOutfit.PetSequenceId += 10; if (AmongUsClient.Instance.ClientId == clientId) { player.SetPet(petId); return; } - player.Data.DefaultOutfit.PetSequenceId += 10; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetPetStr, SendOption.Reliable, clientId); writer.Write(petId); writer.Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)); diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index b81707d4ab..f47af71ea0 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -25,36 +25,36 @@ void Setoutfit() .Write((byte)Outfit.ColorId) .EndRpc(); + player.Data.DefaultOutfit.HatSequenceId += 10; player.SetHat(Outfit.HatId, Outfit.ColorId); - player.Data.DefaultOutfit.HatSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); - player.SetSkin(Outfit.SkinId, Outfit.ColorId); player.Data.DefaultOutfit.SkinSequenceId += 10; + player.SetSkin(Outfit.SkinId, Outfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(Outfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); - player.SetVisor(Outfit.VisorId, Outfit.ColorId); player.Data.DefaultOutfit.VisorSequenceId += 10; + player.SetVisor(Outfit.VisorId, Outfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(Outfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); - player.SetPet(Outfit.PetId); player.Data.DefaultOutfit.PetSequenceId += 10; + player.SetPet(Outfit.PetId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(Outfit.PetId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) .EndRpc(); - player.SetNamePlate(Outfit.NamePlateId); player.Data.DefaultOutfit.NamePlateSequenceId += 10; + player.SetNamePlate(Outfit.NamePlateId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) .Write(Outfit.NamePlateId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) @@ -118,29 +118,29 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P .Write((byte)newOutfit.ColorId) .EndRpc(); - player.SetHat(newOutfit.HatId, newOutfit.ColorId); player.Data.DefaultOutfit.HatSequenceId += 10; + player.SetHat(newOutfit.HatId, newOutfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(newOutfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); - player.SetSkin(newOutfit.SkinId, newOutfit.ColorId); player.Data.DefaultOutfit.SkinSequenceId += 10; + player.SetSkin(newOutfit.SkinId, newOutfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(newOutfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); - player.SetVisor(newOutfit.VisorId, newOutfit.ColorId); player.Data.DefaultOutfit.VisorSequenceId += 10; + player.SetVisor(newOutfit.VisorId, newOutfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(newOutfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); - player.SetPet(newOutfit.PetId); player.Data.DefaultOutfit.PetSequenceId += 10; + player.SetPet(newOutfit.PetId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(newOutfit.PetId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) @@ -148,8 +148,8 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P if (setNamePlate) { - player.SetNamePlate(newOutfit.NamePlateId); player.Data.DefaultOutfit.NamePlateSequenceId += 10; + player.SetNamePlate(newOutfit.NamePlateId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) .Write(newOutfit.NamePlateId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) diff --git a/Roles/Impostor/DollMaster.cs b/Roles/Impostor/DollMaster.cs index 796bc0fd04..8cb91638a4 100644 --- a/Roles/Impostor/DollMaster.cs +++ b/Roles/Impostor/DollMaster.cs @@ -449,32 +449,32 @@ void Setoutfit() .Write((byte)Outfit.ColorId) .EndRpc(); - player.SetHat(Outfit.HatId, Outfit.ColorId); player.Data.DefaultOutfit.HatSequenceId += 10; + player.SetHat(Outfit.HatId, Outfit.ColorId); player.Data.DefaultOutfit.HatId = Main.PlayerStates[player.PlayerId].NormalOutfit.HatId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); - player.SetSkin(Outfit.SkinId, Outfit.ColorId); player.Data.DefaultOutfit.SkinSequenceId += 10; + player.SetSkin(Outfit.SkinId, Outfit.ColorId); player.Data.DefaultOutfit.SkinId = Main.PlayerStates[player.PlayerId].NormalOutfit.SkinId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(Outfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); - player.SetVisor(Outfit.VisorId, Outfit.ColorId); player.Data.DefaultOutfit.VisorSequenceId += 10; + player.SetVisor(Outfit.VisorId, Outfit.ColorId); player.Data.DefaultOutfit.VisorId = Main.PlayerStates[player.PlayerId].NormalOutfit.VisorId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(Outfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); - player.SetPet(Outfit.PetId); player.Data.DefaultOutfit.PetSequenceId += 10; + player.SetPet(Outfit.PetId); player.Data.DefaultOutfit.PetId = Main.PlayerStates[player.PlayerId].NormalOutfit.PetId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(Outfit.PetId) From 4b9a04c7e43ada8a576a424534a18c430db8eb5c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 16:29:48 +0800 Subject: [PATCH 739/778] Some change --- Modules/DelayNetworkedData.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index 51997306d7..d89cc0a3be 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -198,7 +198,7 @@ public static void FixedUpdatePostfix(InnerNetClient __instance) { // Send a networked data pre 2 fixed update should be a good practice? if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return; - if (!__instance.AmHost || __instance.Streams == null) return; + if (!__instance.AmHost || GameStates.InGame || __instance.Streams == null) return; if (timer == 0) { @@ -239,8 +239,19 @@ public static void FixedUpdatePostfix(InnerNetClient __instance) } } } + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendOrDisconnect)), HarmonyPrefix] + public static void SendOrDisconnectPatch(InnerNetClient __instance, MessageWriter msg) + { + if (DebugModeManager.IsDebugMode) + { + Logger.Info($"Packet({msg.Length}), SendOption:{msg.SendOption}", "SendOrDisconnectPatch"); + } + else if (msg.Length > 1000) + { + Logger.Info($"Large Packet({msg.Length})", "SendOrDisconnectPatch"); + } + } } - [HarmonyPatch(typeof(GameData), nameof(GameData.DirtyAllData))] internal class DirtyAllDataPatch { From d31efa7db0d92b190c98c3b3b69c49df720aa053 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 16:35:45 +0800 Subject: [PATCH 740/778] hm --- Modules/OutfitManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index f47af71ea0..62cc0f6e6e 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -25,7 +25,7 @@ void Setoutfit() .Write((byte)Outfit.ColorId) .EndRpc(); - player.Data.DefaultOutfit.HatSequenceId += 10; + player.Data.DefaultOutfit.HatSequenceId += 10; player.SetHat(Outfit.HatId, Outfit.ColorId); sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) From a4b2e77ced383892a0c1652a359e92f1944fef8e Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 17:32:21 +0800 Subject: [PATCH 741/778] Revert --- Modules/DelayNetworkedData.cs | 2 +- Modules/ExtendedPlayerControl.cs | 2 +- Modules/OutfitManager.cs | 20 ++++++++++---------- Roles/Impostor/DollMaster.cs | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index d89cc0a3be..d4f6bdcc61 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -198,7 +198,7 @@ public static void FixedUpdatePostfix(InnerNetClient __instance) { // Send a networked data pre 2 fixed update should be a good practice? if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return; - if (!__instance.AmHost || GameStates.InGame || __instance.Streams == null) return; + if (!__instance.AmHost || __instance.Streams == null) return; if (timer == 0) { diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index b3941bdabc..65954d9a97 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -261,12 +261,12 @@ public static void RpcSetPetDesync(this PlayerControl player, string petId, Play { var clientId = seer.GetClientId(); if (clientId == -1) return; - player.Data.DefaultOutfit.PetSequenceId += 10; if (AmongUsClient.Instance.ClientId == clientId) { player.SetPet(petId); return; } + player.Data.DefaultOutfit.PetSequenceId += 10; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetPetStr, SendOption.Reliable, clientId); writer.Write(petId); writer.Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)); diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index 62cc0f6e6e..b81707d4ab 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -25,36 +25,36 @@ void Setoutfit() .Write((byte)Outfit.ColorId) .EndRpc(); - player.Data.DefaultOutfit.HatSequenceId += 10; player.SetHat(Outfit.HatId, Outfit.ColorId); + player.Data.DefaultOutfit.HatSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); - player.Data.DefaultOutfit.SkinSequenceId += 10; player.SetSkin(Outfit.SkinId, Outfit.ColorId); + player.Data.DefaultOutfit.SkinSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(Outfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); - player.Data.DefaultOutfit.VisorSequenceId += 10; player.SetVisor(Outfit.VisorId, Outfit.ColorId); + player.Data.DefaultOutfit.VisorSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(Outfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); - player.Data.DefaultOutfit.PetSequenceId += 10; player.SetPet(Outfit.PetId); + player.Data.DefaultOutfit.PetSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(Outfit.PetId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) .EndRpc(); - player.Data.DefaultOutfit.NamePlateSequenceId += 10; player.SetNamePlate(Outfit.NamePlateId); + player.Data.DefaultOutfit.NamePlateSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) .Write(Outfit.NamePlateId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) @@ -118,29 +118,29 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P .Write((byte)newOutfit.ColorId) .EndRpc(); - player.Data.DefaultOutfit.HatSequenceId += 10; player.SetHat(newOutfit.HatId, newOutfit.ColorId); + player.Data.DefaultOutfit.HatSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(newOutfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); - player.Data.DefaultOutfit.SkinSequenceId += 10; player.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + player.Data.DefaultOutfit.SkinSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(newOutfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); - player.Data.DefaultOutfit.VisorSequenceId += 10; player.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + player.Data.DefaultOutfit.VisorSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(newOutfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); - player.Data.DefaultOutfit.PetSequenceId += 10; player.SetPet(newOutfit.PetId); + player.Data.DefaultOutfit.PetSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(newOutfit.PetId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) @@ -148,8 +148,8 @@ public static void SetNewOutfit(this PlayerControl player, NetworkedPlayerInfo.P if (setNamePlate) { - player.Data.DefaultOutfit.NamePlateSequenceId += 10; player.SetNamePlate(newOutfit.NamePlateId); + player.Data.DefaultOutfit.NamePlateSequenceId += 10; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) .Write(newOutfit.NamePlateId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) diff --git a/Roles/Impostor/DollMaster.cs b/Roles/Impostor/DollMaster.cs index 8cb91638a4..796bc0fd04 100644 --- a/Roles/Impostor/DollMaster.cs +++ b/Roles/Impostor/DollMaster.cs @@ -449,32 +449,32 @@ void Setoutfit() .Write((byte)Outfit.ColorId) .EndRpc(); - player.Data.DefaultOutfit.HatSequenceId += 10; player.SetHat(Outfit.HatId, Outfit.ColorId); + player.Data.DefaultOutfit.HatSequenceId += 10; player.Data.DefaultOutfit.HatId = Main.PlayerStates[player.PlayerId].NormalOutfit.HatId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) .Write(Outfit.HatId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); - player.Data.DefaultOutfit.SkinSequenceId += 10; player.SetSkin(Outfit.SkinId, Outfit.ColorId); + player.Data.DefaultOutfit.SkinSequenceId += 10; player.Data.DefaultOutfit.SkinId = Main.PlayerStates[player.PlayerId].NormalOutfit.SkinId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) .Write(Outfit.SkinId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); - player.Data.DefaultOutfit.VisorSequenceId += 10; player.SetVisor(Outfit.VisorId, Outfit.ColorId); + player.Data.DefaultOutfit.VisorSequenceId += 10; player.Data.DefaultOutfit.VisorId = Main.PlayerStates[player.PlayerId].NormalOutfit.VisorId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) .Write(Outfit.VisorId) .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); - player.Data.DefaultOutfit.PetSequenceId += 10; player.SetPet(Outfit.PetId); + player.Data.DefaultOutfit.PetSequenceId += 10; player.Data.DefaultOutfit.PetId = Main.PlayerStates[player.PlayerId].NormalOutfit.PetId; sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) .Write(Outfit.PetId) From c4a0562954a6a15020efce71b249466d90e2dbcc Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 17:45:15 +0800 Subject: [PATCH 742/778] Some change --- Modules/OutfitManager.cs | 19 ++++--- Patches/PlayerControlPatch.cs | 2 +- Roles/Impostor/DollMaster.cs | 94 +++-------------------------------- 3 files changed, 21 insertions(+), 94 deletions(-) diff --git a/Modules/OutfitManager.cs b/Modules/OutfitManager.cs index b81707d4ab..b7db36043c 100644 --- a/Modules/OutfitManager.cs +++ b/Modules/OutfitManager.cs @@ -2,12 +2,14 @@ public static class OutfitManager { - public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, uint newLevel = 500, bool force = false) + public static void ResetPlayerOutfit(this PlayerControl player, NetworkedPlayerInfo.PlayerOutfit Outfit = null, bool setNamePlate = false, uint newLevel = 500, bool force = false) { Outfit ??= Main.PlayerStates[player.PlayerId].NormalOutfit; void Setoutfit() { + if (player == null || Outfit == null) return; + var sender = CustomRpcSender.Create(name: $"Reset PlayerOufit for 『{player.Data.PlayerName}』"); player.SetName(Outfit.PlayerName); @@ -53,12 +55,15 @@ void Setoutfit() .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) .EndRpc(); - player.SetNamePlate(Outfit.NamePlateId); - player.Data.DefaultOutfit.NamePlateSequenceId += 10; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) - .Write(Outfit.NamePlateId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) - .EndRpc(); + if (setNamePlate) + { + player.SetNamePlate(Outfit.NamePlateId); + player.Data.DefaultOutfit.NamePlateSequenceId += 10; + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetNamePlateStr) + .Write(Outfit.NamePlateId) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) + .EndRpc(); + } if (newLevel != 500) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 94a60a27a6..e6d0dade9e 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1156,7 +1156,7 @@ public static Task DoPostfix(PlayerControl __instance) var randomPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != UnShapeshifter); UnShapeshifter.RpcShapeshift(randomPlayer, false); UnShapeshifter.RpcRejectShapeshift(); - UnShapeshifter.ResetPlayerOutfit(); + UnShapeshifter.ResetPlayerOutfit(setNamePlate: true); Utils.NotifyRoles(SpecifyTarget: UnShapeshifter); Logger.Info($"Revert to shapeshifting state for: {player.GetRealName()}", "UnShapeShifer_FixedUpdate"); } diff --git a/Roles/Impostor/DollMaster.cs b/Roles/Impostor/DollMaster.cs index 796bc0fd04..a2f2465c21 100644 --- a/Roles/Impostor/DollMaster.cs +++ b/Roles/Impostor/DollMaster.cs @@ -366,9 +366,10 @@ private static void Possess(PlayerControl pc, PlayerControl target) { (target.MyPhysics.FlipX, pc.MyPhysics.FlipX) = (pc.MyPhysics.FlipX, target.MyPhysics.FlipX); // Copy the players directions that they are facing, Note this only works for modded clients! pc?.RpcShapeshift(target, false); - RpcChangeSkin(pc, target); - RpcChangeSkin(target, pc); - RPC.SyncAllPlayerNames(); + + pc?.ResetPlayerOutfit(Main.PlayerStates[target.PlayerId].NormalOutfit); + target?.ResetPlayerOutfit(Main.PlayerStates[pc.PlayerId].NormalOutfit); + pc?.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DollMaster), GetString("DollMaster_PossessedTarget"))); } @@ -377,16 +378,16 @@ private static void UnPossess(PlayerControl pc, PlayerControl target) { (target.MyPhysics.FlipX, pc.MyPhysics.FlipX) = (pc.MyPhysics.FlipX, target.MyPhysics.FlipX); // Copy the players directions that they are facing, Note this only works for modded clients! pc?.RpcShapeshift(pc, false); - RpcChangeSkin(pc); - RpcChangeSkin(target); - RPC.SyncAllPlayerNames(); + + pc?.ResetPlayerOutfit(); + target?.ResetPlayerOutfit(); IsControllingPlayer = false; ResetPlayerSpeed = true; _ = new LateTask(() => { ReducedVisionPlayers.Clear(); - if (TargetDiesAfterPossession.GetBool() && !GameStates.IsMeeting) target.RpcMurderPlayer(target); + if (TargetDiesAfterPossession.GetBool() && !GameStates.IsMeeting) target?.RpcMurderPlayer(target); }, 0.45f); } @@ -423,85 +424,6 @@ private static void SwapPlayersPositions(PlayerControl pc) pc?.RpcTeleport(controllingTargetPos); } - // Set players cosmetics. - private static void RpcChangeSkin(PlayerControl player, PlayerControl target = null, NetworkedPlayerInfo.PlayerOutfit Outfit = null) - { - target ??= player; - Outfit ??= Main.PlayerStates[target.PlayerId].NormalOutfit; - - void Setoutfit() - { - var sender = CustomRpcSender.Create(name: $"Reset PlayerOufit for 『{player.Data.PlayerName}』"); - - player.SetName(Outfit.PlayerName); - player.Data.DefaultOutfit.PlayerName = Main.PlayerStates[player.PlayerId].NormalOutfit.PlayerName; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(Outfit.PlayerName) - .EndRpc(); - - Main.AllPlayerNames[player.PlayerId] = Outfit.PlayerName; - - player.SetColor(Outfit.ColorId); - player.Data.DefaultOutfit.ColorId = Main.PlayerStates[player.PlayerId].NormalOutfit.ColorId; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetColor) - .Write(player.Data.NetId) - .Write((byte)Outfit.ColorId) - .EndRpc(); - - player.SetHat(Outfit.HatId, Outfit.ColorId); - player.Data.DefaultOutfit.HatSequenceId += 10; - player.Data.DefaultOutfit.HatId = Main.PlayerStates[player.PlayerId].NormalOutfit.HatId; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetHatStr) - .Write(Outfit.HatId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - player.SetSkin(Outfit.SkinId, Outfit.ColorId); - player.Data.DefaultOutfit.SkinSequenceId += 10; - player.Data.DefaultOutfit.SkinId = Main.PlayerStates[player.PlayerId].NormalOutfit.SkinId; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetSkinStr) - .Write(Outfit.SkinId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - player.SetVisor(Outfit.VisorId, Outfit.ColorId); - player.Data.DefaultOutfit.VisorSequenceId += 10; - player.Data.DefaultOutfit.VisorId = Main.PlayerStates[player.PlayerId].NormalOutfit.VisorId; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetVisorStr) - .Write(Outfit.VisorId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - player.SetPet(Outfit.PetId); - player.Data.DefaultOutfit.PetSequenceId += 10; - player.Data.DefaultOutfit.PetId = Main.PlayerStates[player.PlayerId].NormalOutfit.PetId; - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetPetStr) - .Write(Outfit.PetId) - .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - sender.SendMessage(); - - //cannot use currentoutfit type because of mushroom mixup . . - var OutfitTypeSet = player.CurrentOutfitType != PlayerOutfitType.Shapeshifted ? PlayerOutfitType.Default : PlayerOutfitType.Shapeshifted; - - //Used instead of GameData.Instance.DirtyAllData(); - foreach (var innerNetObject in GameData.Instance.AllPlayers) - { - innerNetObject.SetDirtyBit(uint.MaxValue); - } - } - if (player.CheckCamoflague()) - { - Main.LateOutfits[target.PlayerId] = Setoutfit; - } - else - { - Setoutfit(); - } - } - // Set name Suffix for Doll and Main Body under name. public override string GetSuffix(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) { From d2483a69ca192e71b2e230aed22eaffedf39180a Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 17:55:44 +0800 Subject: [PATCH 743/778] Change --- Patches/PlayerJoinAndLeftPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index eea8825540..f6a18e5efa 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -18,7 +18,7 @@ class OnGameJoinedPatch public static void Postfix(AmongUsClient __instance) { while (!Options.IsLoaded) System.Threading.Tasks.Task.Delay(1); - Logger.Info($"{__instance.GameId} Joining room", "OnGameJoined"); + Logger.Info($"{__instance.GameId} Joining room - Room code: {GameCode.IntToGameName(AmongUsClient.Instance.GameId) ?? string.Empty}", "OnGameJoined"); Main.IsHostVersionCheating = false; Main.playerVersion = []; From a3fddfbf2e7daa5bbe2a5e3af7411e8fb109a7ba Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 19:07:05 +0800 Subject: [PATCH 744/778] Return --- Modules/CustomRolesHelper.cs | 3 +-- Modules/EAC.cs | 18 ++++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index a2458126a8..1ffe3649d9 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -46,7 +46,6 @@ public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill butt : RoleTypes.GuardianAngel; } - /* Needs recode, awaiting phantom role base*/ public static bool HasImpKillButton(this PlayerControl player, bool considerVanillaShift = false) { if (player == null) return false; @@ -57,7 +56,7 @@ public static bool HasImpKillButton(this PlayerControl player, bool considerVani return ModSideHasKillButton; bool vanillaSideHasKillButton = EAC.OriginalRoles.TryGetValue(player.PlayerId, out var OriginalRole) ? - (OriginalRole.GetDYRole() == RoleTypes.Impostor || OriginalRole.GetVNRole() is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom) : ModSideHasKillButton; + (OriginalRole.GetDYRole() is RoleTypes.Impostor or RoleTypes.Shapeshifter || OriginalRole.GetVNRole() is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom) : ModSideHasKillButton; return vanillaSideHasKillButton; } diff --git a/Modules/EAC.cs b/Modules/EAC.cs index f8652cea51..790678b099 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -366,16 +366,14 @@ public static bool RpcUpdateSystemCheck(PlayerControl player, SystemTypes system if (systemType == SystemTypes.Sabotage) //Normal sabotage using buttons { - //if (!player.HasImpKillButton(true)) - //{ - // WarnHost(); - // Report(player, "Bad Sabotage A : Non Imp"); - // HandleCheat(player, "Bad Sabotage A : Non Imp"); - // Logger.Fatal($"玩家【{player.GetClientId()}:{player.GetRealName()}】Bad Sabotage A,已驳回", "EAC"); - // return true; - //} - - // Disable this check since haskillbutton needs rework + if (!player.HasImpKillButton(true)) + { + WarnHost(); + Report(player, "Bad Sabotage A : Non Imp"); + HandleCheat(player, "Bad Sabotage A : Non Imp"); + Logger.Fatal($"玩家【{player.GetClientId()}:{player.GetRealName()}】Bad Sabotage A,已驳回", "EAC"); + return true; + } } //Cheater directly send 128 systemtype rpc else if (systemType == SystemTypes.LifeSupp) { From 0f2f69c7827221e63f1667a0efcd25df0420c76c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 22:56:38 +0800 Subject: [PATCH 745/778] NotificationPopper support TOHE settings --- Modules/RPC.cs | 20 ++++++- Patches/GameOptionsMenuPatch.cs | 14 ++++- Patches/NotificationPopperPatch.cs | 86 ++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 Patches/NotificationPopperPatch.cs diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 3e8ac854b2..cbfdff8ba2 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -53,6 +53,7 @@ enum CustomRPC : byte // 185/255 USED SyncGeneralOptions, SyncSpeedPlayer, Arrow, + NotificationPopper, //Roles SetBountyTarget, @@ -72,11 +73,11 @@ enum CustomRPC : byte // 185/255 USED SetEvilTrackerTarget, SetDrawPlayer, SetCrewpostorTasksDone, - SetCurrentDrawTarget, // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! BetterCheck = 150, + SetCurrentDrawTarget, RpcPassBomb, SyncRomanticTarget, SyncVengefulRomanticTarget, @@ -436,8 +437,23 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c { if (reader.ReadBoolean()) TargetArrow.ReceiveRPC(reader); else LocateArrow.ReceiveRPC(reader); - break; } + break; + case CustomRPC.NotificationPopper: + { + var typeId = reader.ReadByte(); + var item = reader.ReadPackedInt32(); + var customRole = reader.ReadPackedInt32(); + var playSound = reader.ReadBoolean(); + + var key = OptionItem.AllOptions[item]; + + if (typeId is 0) + NotificationPopperPatch.AddSettingsChangeMessage(item, key, playSound); + else if (typeId is 1) + NotificationPopperPatch.AddRoleSettingsChangeMessage(item, key, (CustomRoles)customRole, playSound); + } + break; case CustomRPC.SetBountyTarget: BountyHunter.ReceiveRPC(reader); break; diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 18be5af405..0ea52b1c1e 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -2,6 +2,7 @@ using System; using TMPro; using UnityEngine; +using TOHE.Patches; using static TOHE.Translator; using Object = UnityEngine.Object; @@ -449,6 +450,7 @@ private static bool UpdateValuePrefix(ToggleOption __instance) var item = OptionItem.AllOptions[index]; //Logger.Info($"{item.Name}, {index}", "ToggleOption.UpdateValue.TryGetValue"); item.SetValue(__instance.GetBool() ? 1 : 0); + NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); return false; } return true; @@ -522,7 +524,7 @@ private static bool UpdateValuePrefix(NumberOption __instance) { floatOptionItem.SetValue(floatOptionItem.Rule.GetNearestIndex(__instance.GetFloat())); } - + NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); return false; } return true; @@ -675,6 +677,16 @@ private static bool UpdateValuePrefix(StringOption __instance) //Logger.Info($"{item.Name}, {index}", "StringOption.UpdateValue.TryAdd"); item.SetValue(__instance.GetInt()); + var name = item.GetName(); + if (Enum.GetValues().Find(x => GetString($"{x}") == name.RemoveHtmlTags(), out var role)) + { + NotificationPopperPatch.AddRoleSettingsChangeMessage(index, item, role, true); + } + else + { + NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); + } + if (item is PresetOptionItem || (item is StringOptionItem && item.Name == "GameMode")) { if (Options.GameMode.GetInt() == 2 && !GameStates.IsHideNSeek) //Hide And Seek diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs new file mode 100644 index 0000000000..d01b6b07ef --- /dev/null +++ b/Patches/NotificationPopperPatch.cs @@ -0,0 +1,86 @@ +using Hazel; +using TOHE.Modules; +using UnityEngine; + +namespace TOHE.Patches; + +[HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.Awake))] +public class NotificationPopperAwakePatch +{ + public static void Prefix(NotificationPopper __instance) + { + // not use ??= because exceptions may occur + NotificationPopperPatch.Instance = __instance; + } +} +internal class NotificationPopperPatch +{ + public static NotificationPopper Instance; + + public static void AddSettingsChangeMessage( + int index, + OptionItem key, + bool playSound = false) + { + SendRpc(0, index, playSound: playSound); + var haveParent = key.Parent != null; + string str; + if (haveParent && System.Enum.GetValues().Find(x => Translator.GetString($"{x}") == key.Parent.GetName().RemoveHtmlTags(), out var role)) + { + str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + ""); + } + else if (haveParent) + { + str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + ""); + } + else + { + str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); + } + SettingsChangeMessageLogic(key, str, playSound); + } + + public static void AddRoleSettingsChangeMessage( + int index, + OptionItem key, + CustomRoles customRole, + bool playSound = false) + { + SendRpc(1, index, customRole, playSound); + var roleColor = Utils.GetRoleColor(customRole); + string str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); + SettingsChangeMessageLogic(key, str, playSound); + } + + private static void SettingsChangeMessageLogic(OptionItem key, string item, bool playSound) + { + if (Instance.lastMessageKey == key.Id && Instance.activeMessages.Count > 0) + { + Instance.activeMessages[^1].UpdateMessage(item); + } + else + { + Instance.lastMessageKey = key.Id; + LobbyNotificationMessage newMessage = Object.Instantiate(Instance.notificationMessageOrigin, Vector3.zero, Quaternion.identity, Instance.transform); + newMessage.transform.localPosition = new Vector3(0.0f, 0.0f, -2f); + newMessage.SetUp(item, Instance.settingsChangeSprite, Instance.settingsChangeColor, (Il2CppSystem.Action)(() => Instance.OnMessageDestroy(newMessage))); + Instance.ShiftMessages(); + Instance.AddMessageToQueue(newMessage); + } + if (!playSound) + return; + SoundManager.Instance.PlaySoundImmediate(Instance.settingsChangeSound, false); + } + private static void SendRpc(byte typeId, int index, CustomRoles customRole = CustomRoles.NotAssigned, bool playSound = true) + { + if (!AmongUsClient.Instance.AmHost) return; + if (!Main.AllPlayerControls.Any(pc => pc.IsNonHostModdedClient())) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.NotificationPopper, SendOption.Reliable); + writer.Write(typeId); + writer.WritePacked(index); + writer.WritePacked((int)customRole); + writer.Write(playSound); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } +} From 962d3f0f08cc05a7f97a4a4daa09522e7a7318e7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 23:15:11 +0800 Subject: [PATCH 746/778] Add check Hide Game Settings --- Patches/NotificationPopperPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs index d01b6b07ef..b468c37d79 100644 --- a/Patches/NotificationPopperPatch.cs +++ b/Patches/NotificationPopperPatch.cs @@ -73,7 +73,7 @@ private static void SettingsChangeMessageLogic(OptionItem key, string item, bool } private static void SendRpc(byte typeId, int index, CustomRoles customRole = CustomRoles.NotAssigned, bool playSound = true) { - if (!AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost || Options.HideGameSettings.GetBool()) return; if (!Main.AllPlayerControls.Any(pc => pc.IsNonHostModdedClient())) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.NotificationPopper, SendOption.Reliable); From b8d47d3aa224176a1b6482d5e7d3f68b3bc5c1ae Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 9 Oct 2024 23:42:33 +0800 Subject: [PATCH 747/778] Clear code --- Modules/ExtendedPlayerControl.cs | 1 - Patches/MapPickerMenuPatch.cs | 3 +-- Patches/NotificationPopperPatch.cs | 8 +------- Roles/Crewmate/Retributionist.cs | 1 - Roles/Impostor/DoubleAgent.cs | 1 - Roles/Impostor/Nemesis.cs | 1 - 6 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 65954d9a97..e675751565 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -13,7 +13,6 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; diff --git a/Patches/MapPickerMenuPatch.cs b/Patches/MapPickerMenuPatch.cs index 07cb36442f..1789b0bbfa 100644 --- a/Patches/MapPickerMenuPatch.cs +++ b/Patches/MapPickerMenuPatch.cs @@ -1,5 +1,4 @@ -using System; -using UnityEngine; +using UnityEngine; namespace TOHE.Patches; diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs index b468c37d79..d8cfff1944 100644 --- a/Patches/NotificationPopperPatch.cs +++ b/Patches/NotificationPopperPatch.cs @@ -1,5 +1,4 @@ using Hazel; -using TOHE.Modules; using UnityEngine; namespace TOHE.Patches; @@ -25,11 +24,7 @@ public static void AddSettingsChangeMessage( SendRpc(0, index, playSound: playSound); var haveParent = key.Parent != null; string str; - if (haveParent && System.Enum.GetValues().Find(x => Translator.GetString($"{x}") == key.Parent.GetName().RemoveHtmlTags(), out var role)) - { - str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + ""); - } - else if (haveParent) + if (haveParent) { str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + ""); } @@ -47,7 +42,6 @@ public static void AddRoleSettingsChangeMessage( bool playSound = false) { SendRpc(1, index, customRole, playSound); - var roleColor = Utils.GetRoleColor(customRole); string str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); SettingsChangeMessageLogic(key, str, playSound); } diff --git a/Roles/Crewmate/Retributionist.cs b/Roles/Crewmate/Retributionist.cs index af490ae92b..db0b634aaa 100644 --- a/Roles/Crewmate/Retributionist.cs +++ b/Roles/Crewmate/Retributionist.cs @@ -1,5 +1,4 @@ using Hazel; -using System; using TOHE.Modules; using TOHE.Roles.Double; using UnityEngine; diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 2594e586a4..7388bb4757 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -1,5 +1,4 @@ using Hazel; -using System; using InnerNet; using UnityEngine; using TOHE.Modules; diff --git a/Roles/Impostor/Nemesis.cs b/Roles/Impostor/Nemesis.cs index 69dbaf32e6..cc57315e14 100644 --- a/Roles/Impostor/Nemesis.cs +++ b/Roles/Impostor/Nemesis.cs @@ -1,6 +1,5 @@ using AmongUs.GameOptions; using Hazel; -using System; using TOHE.Modules; using TOHE.Roles.Double; using UnityEngine; From ea27ba891c03e09bcad2dfe72f8e3c42c923fb56 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:40:26 -0400 Subject: [PATCH 748/778] so much work to remove 3 lines of code and move 3 other lines of code --- Modules/OptionHolder.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index f91b5b2b3d..c285046f65 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1696,25 +1696,22 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetHeader(true); CrewmatesCanGuess = BooleanOptionItem.Create(60681, "CrewmatesCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); + CrewCanGuessCrew = BooleanOptionItem.Create(60686, "CrewCanGuessCrew", true, TabGroup.ModifierSettings, false) + .SetParent(CrewmatesCanGuess); ImpostorsCanGuess = BooleanOptionItem.Create(60682, "ImpostorsCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); + ImpCanGuessImp = BooleanOptionItem.Create(60687, "ImpCanGuessImp", true, TabGroup.ModifierSettings, false) + .SetParent(ImpostorsCanGuess); NeutralKillersCanGuess = BooleanOptionItem.Create(60683, "NeutralKillersCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); NeutralApocalypseCanGuess = BooleanOptionItem.Create(60690, "NeutralApocalypseCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); + ApocCanGuessApoc = BooleanOptionItem.Create(60691, "ApocCanGuessApoc", false, TabGroup.ModifierSettings, false) + .SetParent(NeutralApocalypseCanGuess); PassiveNeutralsCanGuess = BooleanOptionItem.Create(60684, "PassiveNeutralsCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); CanGuessAddons = BooleanOptionItem.Create(60685, "CanGuessAddons", true, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); - CrewCanGuessCrew = BooleanOptionItem.Create(60686, "CrewCanGuessCrew", true, TabGroup.ModifierSettings, false) - .SetHidden(true) - .SetParent(GuesserMode); - ImpCanGuessImp = BooleanOptionItem.Create(60687, "ImpCanGuessImp", true, TabGroup.ModifierSettings, false) - .SetHidden(true) - .SetParent(GuesserMode); - ApocCanGuessApoc = BooleanOptionItem.Create(60691, "ApocCanGuessApoc", false, TabGroup.ModifierSettings, false) - .SetHidden(true) - .SetParent(GuesserMode); HideGuesserCommands = BooleanOptionItem.Create(60688, "GuesserTryHideMsg", true, TabGroup.ModifierSettings, false) .SetParent(GuesserMode) .SetColor(Color.green); From 2dbddee7f81734c4d22b1e503fe6dfd271ce73cb Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 14:03:09 +0800 Subject: [PATCH 749/778] Fix bugs --- Modules/BanManager.cs | 4 +-- Modules/CustomRolesHelper.cs | 3 ++- Modules/GuessManager.cs | 2 -- Modules/Utils.cs | 28 ++++++++++++++++----- Patches/MeetingHudPatch.cs | 1 - Patches/PlayerJoinAndLeftPatch.cs | 5 +++- Patches/onGameStartedPatch.cs | 2 +- Roles/Core/AssignManager/GhostRoleAssign.cs | 17 ++++++++++++- Roles/Crewmate/Judge.cs | 2 -- Roles/Impostor/Councillor.cs | 2 -- Roles/Neutral/Lawyer.cs | 1 + Roles/Neutral/PlagueDoctor.cs | 11 +++++--- 12 files changed, 55 insertions(+), 23 deletions(-) diff --git a/Modules/BanManager.cs b/Modules/BanManager.cs index 2a708ba4be..f2f382e638 100644 --- a/Modules/BanManager.cs +++ b/Modules/BanManager.cs @@ -160,7 +160,7 @@ public static void CheckBanPlayer(ClientData player) { AmongUsClient.Instance.KickPlayer(player.Id, true); Logger.SendInGame(string.Format(GetString("Message.BannedByBanList"), player.PlayerName)); - Logger.Info($"{player.PlayerName}は過去にBAN済みのためBANされました。", "BAN"); + Logger.Info($"{player.PlayerName} found in BanList, so player is banned", "BAN"); return; } // Check EAC list from API @@ -168,7 +168,7 @@ public static void CheckBanPlayer(ClientData player) { AmongUsClient.Instance.KickPlayer(player.Id, true); Logger.SendInGame(string.Format(GetString("Message.BannedByEACList"), player.PlayerName)); - Logger.Info($"{player.PlayerName}存在于EAC封禁名单", "BAN"); + Logger.Info($"{player.PlayerName} found in EAC block list, so player is banned", "BAN"); return; } if (TempBanWhiteList.Contains(player?.GetHashedPuid())) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 1ffe3649d9..18076cdd33 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -955,7 +955,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if (pc.Is(CustomRoles.SuperStar) || pc.Is(CustomRoles.Innocent) || pc.Is(CustomRoles.Solsticer) - || pc.Is(CustomRoles.NiceMini)) + || pc.Is(CustomRoles.NiceMini) + || pc.Is(CustomRoles.Marshall)) return false; break; diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index b89db6b70d..bf45293a9a 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -361,8 +361,6 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) Main.PlayersDiedInMeeting.Add(dp.PlayerId); MurderPlayerPatch.AfterPlayerDeathTasks(pc, dp, true); - Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); - _ = new LateTask(() => { Utils.SendMessage(string.Format(GetString("GuessKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("GuessKillTitle")), true); }, 0.6f, "Guess Msg"); var doomsayers = Utils.GetPlayerListByRole(CustomRoles.Doomsayer); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index c163c30533..f529570f0a 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1880,9 +1880,17 @@ public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => private static readonly StringBuilder TargetMark = new(20); public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro) return; - if (Main.MeetingIsStarted && !(isForMeeting || GameEndCheckerForNormal.GameIsEnded)) return; - if (Main.AllPlayerControls == null) return; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || Main.AllPlayerControls == null || SetUpRoleTextPatch.IsInIntro) return; + if (GameStates.IsMeeting) + { + // When the meeting window is active and game is not ended + if (!GameEndCheckerForNormal.GameIsEnded) return; + } + else + { + // When some one press report button but NotifyRoles is not for meeting + if (Main.MeetingIsStarted && !isForMeeting) return; + } //var caller = new System.Diagnostics.StackFrame(1, false); //var callerMethod = caller.GetMethod(); @@ -1894,9 +1902,17 @@ public static async void NotifyRoles(PlayerControl SpecifySeer = null, PlayerCon } public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool isForMeeting = false, bool NoCache = false, bool ForceLoop = true, bool CamouflageIsForMeeting = false, bool MushroomMixupIsActive = false) { - if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || SetUpRoleTextPatch.IsInIntro) return Task.CompletedTask; - if (Main.MeetingIsStarted && !(isForMeeting || GameEndCheckerForNormal.GameIsEnded)) return Task.CompletedTask; - if (Main.AllPlayerControls == null) return Task.CompletedTask; + if (!AmongUsClient.Instance.AmHost || GameStates.IsHideNSeek || Main.AllPlayerControls == null || SetUpRoleTextPatch.IsInIntro) return Task.CompletedTask; + if (GameStates.IsMeeting) + { + // When the meeting window is active and game is not ended + if (!GameEndCheckerForNormal.GameIsEnded) return Task.CompletedTask; + } + else + { + // When some one press report button but NotifyRoles is not for meeting + if (Main.MeetingIsStarted && !isForMeeting) return Task.CompletedTask; + } //var logger = Logger.Handler("DoNotifyRoles"); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 43e124e5fa..e68d184a53 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -53,7 +53,6 @@ public static bool Prefix(MeetingHud __instance) Main.MadmateNum++; pc.RpcSetCustomRole(CustomRoles.Madmate); ExtendedPlayerControl.RpcSetCustomRole(pc.PlayerId, CustomRoles.Madmate); - NotifyRoles(isForMeeting: true, SpecifySeer: pc, NoCache: true); Logger.Info($"Assign in meeting by self vote: {pc?.Data?.PlayerName} = {pc.GetCustomRole()} + {CustomRoles.Madmate}", "Madmate"); } } diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index f6a18e5efa..3ee65b4bf0 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -307,7 +307,10 @@ class OnPlayerLeftPatch static void Prefix([HarmonyArgument(0)] ClientData data) { StartingProcessing = true; - LeftPlayerId = data.Character.PlayerId; + LeftPlayerId = data?.Character?.PlayerId ?? byte.MaxValue; + + if (data != null && data.Character != null) + StartGameHostPatch.DataDisconnected[data.Character.PlayerId] = true; if (GameStates.IsInGame) { diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index de8d5d19bd..5ece156be9 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -625,7 +625,7 @@ private static void SetRoleSelf(PlayerControl target) target.RpcSetRoleDesync(roleType, targetClientId); } - private static readonly Dictionary DataDisconnected = []; + public static readonly Dictionary DataDisconnected = []; public static void RpcSetDisconnected(bool disconnected) { foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 44fcc424b7..33cdb06717 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -38,7 +38,22 @@ public static void GhostAssignPatch(PlayerControl player) } var getplrRole = player.GetCustomRole(); - if (getplrRole is CustomRoles.GM or CustomRoles.Nemesis or CustomRoles.Retributionist or CustomRoles.NiceMini) return; + + // Neutral Apocalypse can't get ghost roles + if (getplrRole.IsNA() || getplrRole.IsTNA()) return; + + // Roles can win after death, should not get ghost roles + if (getplrRole is CustomRoles.GM + or CustomRoles.Nemesis + or CustomRoles.Retributionist + or CustomRoles.NiceMini + or CustomRoles.Romantic + or CustomRoles.Follower + or CustomRoles.Specter + or CustomRoles.Sunnyboy + or CustomRoles.Innocent + or CustomRoles.Workaholic + or CustomRoles.PlagueDoctor) return; var IsNeutralAllowed = !player.IsAnySubRole(x => x.IsConverted()) || Options.ConvertedCanBecomeGhost.GetBool(); var CheckNeutral = player.GetCustomRole().IsNeutral() && Options.NeutralCanBecomeGhost.GetBool(); diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 5e0b59f1c3..39ec8fc33b 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -222,8 +222,6 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) Main.PlayersDiedInMeeting.Add(dp.PlayerId); MurderPlayerPatch.AfterPlayerDeathTasks(pc, dp, true); - NotifyRoles(isForMeeting: false, NoCache: true); - _ = new LateTask(() => { SendMessage(string.Format(GetString("Judge_TrialKill"), Name), 255, ColorString(GetRoleColor(CustomRoles.Judge), GetString("Judge_TrialKillTitle")), true); }, 0.6f, "Guess Msg"); }, 0.2f, "Trial Kill"); diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index 2baa287df2..50bca244de 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -253,8 +253,6 @@ public bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) Main.PlayersDiedInMeeting.Add(dp.PlayerId); MurderPlayerPatch.AfterPlayerDeathTasks(pc, dp, true); - Utils.NotifyRoles(isForMeeting: false, NoCache: true); - _ = new LateTask(() => { if (!MakeEvilJudgeClear.GetBool()) { diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index f938ad2734..3023ed0f56 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -82,6 +82,7 @@ public override void Add(byte playerId) foreach (var target in Main.AllPlayerControls) { if (playerId == target.PlayerId) continue; + else if (TargetList.Contains(target.PlayerId)) continue; else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index e6570f7200..0cd4a9db28 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -271,7 +271,7 @@ private void DirectInfect(PlayerControl target, PlayerControl plague) } private void CheckWin() { - if (!HasEnabled) return; + if (_Player == null) return; if (!AmongUsClient.Instance.AmHost) return; // Invalid if someone's victory is being processed if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; @@ -280,10 +280,13 @@ private void CheckWin() { InfectActive = false; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.PlagueDoctor); - foreach (var plagueDoctor in Main.AllPlayerControls.Where(p => p.Is(CustomRoles.PlagueDoctor)).ToArray()) + if (!CustomWinnerHolder.CheckForConvertedWinner(_Player.PlayerId)) { - CustomWinnerHolder.WinnerIds.Add(plagueDoctor.PlayerId); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.PlagueDoctor); + foreach (var plagueDoctor in Main.AllPlayerControls.Where(p => p.Is(CustomRoles.PlagueDoctor)).ToArray()) + { + CustomWinnerHolder.WinnerIds.Add(plagueDoctor.PlayerId); + } } foreach (PlayerControl player in Main.AllAlivePlayerControls) From 83e8b5e6991ca29f7b45bff8819040d029a7d319 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 14:29:22 +0800 Subject: [PATCH 750/778] Clear code in NotificationPopperAwakePatch --- Modules/RPC.cs | 7 +------ Patches/GameOptionsMenuPatch.cs | 10 +--------- Patches/NotificationPopperPatch.cs | 17 ++--------------- main.cs | 6 +++--- 4 files changed, 7 insertions(+), 33 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index cbfdff8ba2..65d31477a3 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -441,17 +441,12 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c break; case CustomRPC.NotificationPopper: { - var typeId = reader.ReadByte(); var item = reader.ReadPackedInt32(); - var customRole = reader.ReadPackedInt32(); var playSound = reader.ReadBoolean(); var key = OptionItem.AllOptions[item]; - if (typeId is 0) - NotificationPopperPatch.AddSettingsChangeMessage(item, key, playSound); - else if (typeId is 1) - NotificationPopperPatch.AddRoleSettingsChangeMessage(item, key, (CustomRoles)customRole, playSound); + NotificationPopperPatch.AddSettingsChangeMessage(item, key, playSound); } break; case CustomRPC.SetBountyTarget: diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 0ea52b1c1e..58717cd67d 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -677,15 +677,7 @@ private static bool UpdateValuePrefix(StringOption __instance) //Logger.Info($"{item.Name}, {index}", "StringOption.UpdateValue.TryAdd"); item.SetValue(__instance.GetInt()); - var name = item.GetName(); - if (Enum.GetValues().Find(x => GetString($"{x}") == name.RemoveHtmlTags(), out var role)) - { - NotificationPopperPatch.AddRoleSettingsChangeMessage(index, item, role, true); - } - else - { - NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); - } + NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); if (item is PresetOptionItem || (item is StringOptionItem && item.Name == "GameMode")) { diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs index d8cfff1944..6ec4a335f2 100644 --- a/Patches/NotificationPopperPatch.cs +++ b/Patches/NotificationPopperPatch.cs @@ -21,7 +21,7 @@ public static void AddSettingsChangeMessage( OptionItem key, bool playSound = false) { - SendRpc(0, index, playSound: playSound); + SendRpc(index, playSound); var haveParent = key.Parent != null; string str; if (haveParent) @@ -35,17 +35,6 @@ public static void AddSettingsChangeMessage( SettingsChangeMessageLogic(key, str, playSound); } - public static void AddRoleSettingsChangeMessage( - int index, - OptionItem key, - CustomRoles customRole, - bool playSound = false) - { - SendRpc(1, index, customRole, playSound); - string str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); - SettingsChangeMessageLogic(key, str, playSound); - } - private static void SettingsChangeMessageLogic(OptionItem key, string item, bool playSound) { if (Instance.lastMessageKey == key.Id && Instance.activeMessages.Count > 0) @@ -65,15 +54,13 @@ private static void SettingsChangeMessageLogic(OptionItem key, string item, bool return; SoundManager.Instance.PlaySoundImmediate(Instance.settingsChangeSound, false); } - private static void SendRpc(byte typeId, int index, CustomRoles customRole = CustomRoles.NotAssigned, bool playSound = true) + private static void SendRpc(int index, bool playSound = false) { if (!AmongUsClient.Instance.AmHost || Options.HideGameSettings.GetBool()) return; if (!Main.AllPlayerControls.Any(pc => pc.IsNonHostModdedClient())) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.NotificationPopper, SendOption.Reliable); - writer.Write(typeId); writer.WritePacked(index); - writer.WritePacked((int)customRole); writer.Write(playSound); AmongUsClient.Instance.FinishRpcImmediately(writer); } diff --git a/main.cs b/main.cs index 70749a2530..4c473ecb91 100644 --- a/main.cs +++ b/main.cs @@ -42,13 +42,13 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1008.210.020000"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Beta 2"; + public const string PluginVersion = "2024.1011.210.030000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0 Beta 3"; public const string SupportedVersionAU = "2024.8.13"; /******************* Change one of the three variables to true before making a release. *******************/ public static readonly bool devRelease = false; // Latest: V2.1.0 Alpha 16 Hotfix 1 - public static readonly bool canaryRelease = true; // Latest: V2.1.0 Beta 2 + public static readonly bool canaryRelease = true; // Latest: V2.1.0 Beta 3 public static readonly bool fullRelease = false; // Latest: V2.0.3 public static bool hasAccess = true; From 8f155e6e16c7b3c3f4a0c20cd716fd63de342377 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 14:31:33 +0800 Subject: [PATCH 751/778] Add try {} catch {} in case of any exceptions --- Patches/NotificationPopperPatch.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs index 6ec4a335f2..3d94ceb7b4 100644 --- a/Patches/NotificationPopperPatch.cs +++ b/Patches/NotificationPopperPatch.cs @@ -21,18 +21,25 @@ public static void AddSettingsChangeMessage( OptionItem key, bool playSound = false) { - SendRpc(index, playSound); - var haveParent = key.Parent != null; - string str; - if (haveParent) + try { - str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + ""); + SendRpc(index, playSound); + var haveParent = key.Parent != null; + string str; + if (haveParent) + { + str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + ""); + } + else + { + str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); + } + SettingsChangeMessageLogic(key, str, playSound); } - else + catch (System.Exception error) { - str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); + Utils.ThrowException(error); } - SettingsChangeMessageLogic(key, str, playSound); } private static void SettingsChangeMessageLogic(OptionItem key, string item, bool playSound) From dc6cb715f6cfd51c6b6fb0d589e24aa071a38091 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 19:41:16 +0800 Subject: [PATCH 752/778] Add Timer icon --- Resources/Images/Skills/Timer.png | Bin 0 -> 16840 bytes Roles/Impostor/BountyHunter.cs | 11 ++++------- Roles/Impostor/Mercenary.cs | 1 + Roles/Impostor/Penguin.cs | 1 + 4 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 Resources/Images/Skills/Timer.png diff --git a/Resources/Images/Skills/Timer.png b/Resources/Images/Skills/Timer.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc8f11f953cc60c2d6f25422c83b37e1ba80bfe GIT binary patch literal 16840 zcmb_k1y>wR*TjR%;%`+A1_lEPkx~O*W8W@hB;b3L;e8440_&zGD+yCQPId@< zK(LZflz@S$i${AhK?FXdI74*ZU|=wN-Y(byr|;%4FcKf3QW6?oh9~WxGYsau&t8{y zuesW7qhz@M1A&;Ss7*e=>2_0vJ5vYsJ{{k^*dHT2($`%m-eSa2D{cGu#m1f>pwo9r zu(?YhXh}0_+$6TTg7>6C3JE+H(8D`G`Ul6_V)W0YZy=Y4JE)|ysuQpC9UhG1 z2Uz>Sth%M%7yaTOaKi9nl4$_C^}FRjip_6SQ$wrSzWV1b;4$7#kZ}Q(V%41w9X2j# zzl}7Q;58{E6_5v$Dd%;KD+hXfnrm()g9HD?44Z zVvkS$V5%yckdM^ei5y$bArJut0T4YX4m(mRqR$i^9er|jRY6;uaLYNPw3Hr`Od#Qr z+qD2~$c^X@X_6W_WnA>4C&GpF@gst;3OFo3U_ul!K55*rl)-IdDlBAeez&^OriO2y>9ZgYYG9G+pePPd|T7+KfW4!VT-1Hh^L>#WrCDW z&5#r~CQ2wLCgKs>qb3c>3nSU5>nG%9{GH)Of{cz(e@iLU;bNZI+C@JQ_JU z`O~qL_|Aa%3umVn0#`?!?w!vctfzP1cJsU%FHGph@?irI@%AXF%BJ`oR|hLJe@yaV zq>otS7L`rAA1}(UH*R*RZ2JoQIu8n2dnM1Z$YY4DIklvOOq$WI*gq zbSd1FEoYOeV(T|FjOq9p6Se%MqS{DmH+F%#9La@Ov8b^zb#Kpd{mT(6h4){aL25rF zh0AN04cHgD+mIizb)ODwyWja@+?{=}IPFq6Ui+QCy!^>mYy(->J$D^`K%m18D>s)F zAugBx@*hee{JXxR|31)Qp$JbmBebX-99SQKCy5E9;Iy|Io3+Qg#Xk$-KaOB5cIv{2zc2K$n%-ecZYi=^kTW~oa>ePbfUI{vXzsaedtuTasmdS z92I{I$J}xj$l!Az;mgJd)L@hx_#=~rMOj*Q!T2oh7fr>G2$M+zUJVA*a!~mGyz(F9vfk}-_5XQkl$h@B$v;>D< zTAW$sI&nZDxOleflrW@r^hyfsVz zKg-1~@K~?`7+zz!rI9~uNZj^Dx9SEccE*LzezHx|aDExj+j;u;+4{@VzB_pU8Vu>2 zE~wA9`6m@r4!e(PbkGvpGDKjB%OZp$Nt#H|Dp`eFY=2!smKORRX1BqqWJ}pUzns77 zTbn>AZL9g>oon8q6qz<~2zmU_e_n~z(t=g#moADu`fB8PQ z>a!&cfm^E^eD7UfF4RFW5=Xv1uQ|0QGV z>dZvlR&{WKWEUeciC!iO+aR~1z3 zI*VzB%;sPcPu^W|Y?!|L1IMrH{Hm_*1XAzlpt$r(83+$VJj6jz zFk;`4)Qykoni5_*Zr|32UrUMhsP&apL;VKs2C~X_*5!DgXrMwA4CK*?alH_vp5m`K z?rT3iWda)5toQok2;cFgsCKBx(ki}>?zmJW@Y{*LV;+Hp&?g@+H%4bW{SiERLN5+g zKH8`*j^ln{VyCp95;I<@;~QI#=!w?QA8sTy1Xb?eL86YG_<%R|5WHOj+_I>4y%(HE{!yy< zkCDNEA`u?{otX7S;R{;nMZgCz81|}WUF~+Jg2xu5nZK4?TbkaUuCeGhV~6)_&fJs# zUh>?}=j8E4LXq8rAbp4spavE9$DY_g0u|ttU04y(tkgNIki0#3o1>ytL@DeY2`fe- z#|}TZ2uO@4z*@2}y*DgLjvIjXZ5J1+CM7ZsK|*w1sk-*G9yWygjCWilbFHRob-!w` zY2dXjUYJfC!eQhJ-2buh-JUyB`@G|tKt&Nhv$!}|Z!y9(J|-#k^F(}?qM58&t@r|7 z9PX6+vw7Uq@6TtJTfv9|IZBWc!3(1fliOF{n1Uj`#P9WC;E%1==(u!B!_#_PCSKM> z0_Q5eYaVz8-0uPms;G5KH8uyYXqlpL8VRyfaO_jSD7tA?8)Y`1s@Y&I!K*P*b|jI; z5h^|mLGzx$tPatwvNOo@0h)tYpJcQKamJI{GDjew21fns++Hm z!I$dABC}KQk-(*Y(#0QB#Eb4aoAV}WBrrVaQIRRxM&@+2C0RO}KsQA2VVLyz`SFa) z%bL4hBUw5F85$Oe!@wKGM&d#hlPxJ%VGgGwYLAUZV7#Q zu)fY>^<$dFdxpK)a`j$Q&Q-^zUGHC1P}+s}s8JP#Lb<>KI$zLI=kY;$X(iG3P_JEj zwsxQfePI`Bjc_BbXtHS?fhNUlc<$Xbw z$=<;)%={JqG&%D1y+oE1j1g!?hNWz+s%d>VSy^o8X2Hk_13F-|()%aCHz%k)TV?hW zc#Me6-CYuljKd(oET6~&7eSrFh{vv@v_|Qlb@+J$+BYqI_oHlc^omrjx{ydQYvdOh znV4_Jf>A&&xv(dPfB?kMkOChcKQot`4~!zMsY>Pvtgq@|-^((^66Ba&4f<&tGXSYllQ(>fiL1`oB)VTI5Cx`=XW zb#?UR`7x8EmCA|-Xx>3ff=y1XqTw6KF1gB*nv zl(btVb0EwGvZh+OjQn|Q)hrs=WQiI&W2=f6T2aBw=d?b$)e~Oz5AQsBF&F80{7E+* zl%CAvn3$&Pa9X=$Up!+&!1}58BbI1yrEUYv!D2u}Q^!AKKVc(@G`oapB;M(?kjO+C zQtB84n=ltD)jCg6kI$6_U;4P@_C|YkRbcAvbvSksLdm)_@*f``-(vndEQ+6TtU9%U z0C$ilN&1+^8Tm16opxlg-B5&%&Py)JMJt8o5j7%LL^cTqq_jmf3rRu(Yv9q66$F9z zacun4yOP`S*mJ(t)RXC^0SGEbz$G{?4%41buM~U@%N-cdu0KD!3mzj35>~ugc5F&k zpx146XL`LN-7r3B_4r;PS6h`l_PVifxO9aC!iTg{e^cy6i2RhN4=+RlEAsNm_OvDX zL3v$nH_7fosr*CTid$+A%RwRS7?KtqqO@x5%85X(l4Jq)KpgqV@P$E(D;*jkTk*GV zFxg{ag^X#lQ#`D8izMkE0`1l7_V3SA)3=#erP-Lk^MVRAv#>OpAK^j835hac%m#42 zQp@pNUd_2#jr?2ZkKfCyN-B#(fUG#Pyd1UWI1v-~qcbq$=jS1p7RU~wKeek5ETn9P z#h^7MoYFr-c%f$C&^ZAaVd?PzOW>;e=$mi?*zEiC6p9s2e@K`V*~F~w>}!*-_1)j9 zl+o;1QSph|kbdeID0qfTO%wn3THMM>|IWqc&6lB(5!y@z*W*P_&ohd|bix6q!F zmbUHM;w(V6c;cy{DLp(&Iewp~I_Uw{?cTfCWMb{t)(YFc!X}SOI6i{EFg^H?gvV9C zi<|-`uEjk)ke+d8PGj#rbWsP80TjuFYZLpb)1QTllAVd|osGuk=a9zYgsF&lu?NP_ z8-Dzl07zj`x=PEne;wYJxFrdhf{o_j5eXh96n&W$q$aq;}8CtqJNN{aY*O)If8hhX4w?#@kqIZ<+caO`;YOk+2So7nHM zY}j%a0_ZBi7FX2|ccJGMy7+2@NpY-@R#NWP3tCbo8W;*y1 zRlUZp{_Dx|()LHKFgXto&!*>Z?~6V{w~h(6J+0j4SwVrK#@~^&?EKg^o`#y5saq#= z9$_oO-dE$igR>---yI<-j7;6&dJiIZur8gGr|8UC>(|!CrtNYT$|-~XKtcEK6;S}; z{rNLxdRh$?4Q*&*V&Lco7<^!@X^FY0s^aYKhM`d!_s0;wP09_4E~({d9h5>{5}$xT zO-)VG#)j$s{yr^^Gq11YNC*NtO`@@N`W;G~|AGhPR@xCyYDJ7)0fk^jrXqazk1L7g zyJN}$Nn7)tlQB95nwoQiezL~6&k5-*S8AJT@+88}Xy>n1vCN%e7^Ka+X?j36LcTmK zUfm2F2zh?;^1%Q@ak2d~>`~zVgq1581qiE)x1fH8;v`MJcMV5ScT&~F2WDYnzo@nr zyJI7M=1@v=j+2!-WCNH0RUMs(G0UAipHq{Y|K@q7sE}5#Ap7zqo+d7u%{es+Yq;MzS~aWv$h@}|3hSVc+OUP75Gw&#^!4jk1UOi`MJ|+-a)sz)a(~?GOL|D_IPDJq_{Vy<_{^vp85k>n{F~1kU86py-I@7T zIF?>s88cC%l&=RW$t-N?cKYQ!6bej56&1X5 z`~=ZcD=YGPdZd1Se#L4`_IxW5Wx7c!$DY0b>li^8cgMlLF#NL5~RN=yf z%G-A6R}sVfJx>}JE=P=BxNg0#f~q<*{q(>MvRy`wrxmraww(4S3BSEs+v>bB?P9+1*bWQ@WekM-KY&kfbIv63m% zb4@B8r&@A*6ODV=)Iz<-5etSsu_}zcncP-D@cg^KudbyPHn7uT&153E;GtHop%re( z;%>kIK+^T~@jrf#i@zL5SAIF{^hV-BlB9s$UQ$vbkh_=S4v*PE+2=@`76TG-A;QqS zbB>9TWcU|DXqR!&1v`AI6WP&*9^TUxL(J_3#G{5&Cd0p<1Irb)vOoDIVEcB?6Jy;noE;`=51$%Nb^j5#tMe;HsSAt5aSX??oZl0?Iup6$g zuWJKd{eRgnbc%Mv`cmyqw%mLjq-co#A0 z@~L)*_A4?ATV}<76t;^1zu|E33&W;=Fo zIZrNmrS!y$k2%la;H@Qa2?>4d4SCepmqkql^L9M`o?ZJ1r`#8TVlN3wivUA`xU#&m z!W!D#)RYcLiUce=ffM_rKQ7h@-!uSa@n;@VKKpOKe`f=M#MYLrS}dvhNQwqWDMz5I zKbCaqr%m#%Z#JOL$fvVe0E99nCB;0H0mfZX)25a)aM zNg|neLBZ&Q1 z|M04uM8XwG4g(QvOaOF2iP3H=pW0} zn2_GRgMvYXtTtic;Y%F>;>w~ocmR{xyBMI{U#v4TQ_S@At|YDgOM!pL_ns;G7eT=+B*3yTxsxn5@qlKEX?}HIE*W{wm1>r|1iag3ie~2sS z&;uk8_#ZF7I#&_wP-&d&`XPduYH8uNTq>jeV4>!JBXhz0!6`BCJ45O1z1E2`| zr(r25*Xmql+j`*;-eU+uKp9f^e;^GhhGR{{Ui~z!6^J(i5(9Xcwt)730!a-J8 zF*Dw*mili`UdPKxZo_Kqi^|@6yvfE#7(ZF1KxegTqYi(TT>F-T8WfXR192EfI5Bnw zVHgn#tTzAxgbi6$Jf)7t)Z~DB_ToC~XgF*pp0EhZRT)_tnXQ~7o92BbRMOH|RJzPq zyQY;Yl@9=?=r1iustgjjWCW)?^28- zTzN)ZU0;86x<6!BJqFx)1$UlJUczbdkZEbE16_gD+_dyXU} zsX*I&$X=tQQ;fI?4HlRf88=JP4TrO)S!g!j_2raf#YEf9)o}_hWNNFZbS^jA0ciMO zh^M{X0DUTqhF;b7KniN9pw7)g&%dj&?n#?hC>cYxmcy@d!0F#XS+ihwZ;XnHs#9wW z3(TO^$#NrrZS;9e`g`7Sb|^~w0W1BDxJ~$-x~&GhK3D401^@S-tv(xJRH2RuvY;>n zJ(^NSe$?Fi^F2igLPr9FpZQD0S{XXSljH5roW|NEUbZe5 zpP0ZhouNI{xtaIT{giK7h)2fdQVPr-d|lIJ2W6#J<<}z5--%^7K$P26NqCG~1S|?~}=rz$Q&A z7mv?j8sl?L?&L$IB;`d7q_wfVfzX6uxvKObtGjzYcXDnS3TToJAp3iGc+B&{SuU+& z#YFykiqeOw8yQVliCq!DH=J2nfku2BG+j>$lQGqh%v5@r4aFPJ3mV%cTX_8IEO726 zQiR(s{+CETDnmsRzh*&r`1;qkuiMTl0zOEg3!}2b31lcq)~16=7dYG4?d`c#r;nKv zzF`j!s*)Ce4w`^FCRu8%88(=~(vXWH2>+G>?LcG<^^ZBJ6TwdF?xK!cdVdG%_?{%&6`A-TBU@beH1v{Lg`<*EFaVg= zt^3yXix0G)o!G0OuP9dqmYAmzr)-V1$i0Q1dA(i;I{PFdQN)un7R`D-T6@^ zqjfTmoeGNl1Osve{*u_UmuKf4LCUkOT$XB2 zGLwy@gMoFIvzC`UAumpS7z4ClQzK&R1g9t20~^6cL$jTxI1<26btFkcf4}>sqXA>W zq)LY}x7)3$5oq5ORQq8JwS-n>GS~!Yh~rOjodGNK>Q9flFJmBqqD}(_I&q=iI3s*N z0+(!1VpW=*pbH`tPzD5Dwh_vd^T(G1p6*=YC`7aLR&MJ*sHWUf#|p~N#M`Tf7AJ<} z5PRd$ky%e6Tu{?;iDTMSFQ4;7@p%3wS4OhAwib~mI_wS}4h9#r8fdj}nWb}<4(zx9 zFv;4%VRyB~Ek`^cuezFzCUKCCoR3FwXE2f0e&tuJ-{U_E@|6ZFtT!ZecY%T!HV~5O zybj1bEpGePHZ}zSR`C)Md$@OpLCIdh7#>73FAlQo4x@4{)sm+_as;Y`GmyDN!CK1`^_H=jc^F~S8z$TO3TW^-Gy@LJisV;;p8dDjw6oRh@zP^__4v&JLGz0sx@g! z+{1UDf3M*Ykk}a)Z=+mm?1iLUP-ixpA=qqR2ml>;8;<=vpSah%_}9%c@n@zt5F>s+ zqfuEnB$%KL8*SyxJPoLhZFC`_RKutL>;Kh4+c^RlS>n`|V zAJE`(Btl|*I{y9h#3UE$1^O!BeF^^f@gwfE3%~|*b939C|E=VRcqgT$5!j`saa)bo z0*c?^Ie17UySAtsN}UgrgcR4`0o1;fXBDazX{#(+*}?d2gYfCFWa&I z#B*lBE9ckqBlO=tnyp$mdrlDXkTywF&(Lh+59jn0qG*@MxSvb*e|L9*Ak0I+Za9j; zzuE~vpq86?J>4cp@@N9q!k<6gHF^wLiesP-Z-Xf&@(;9}2nhkemfL10?DXMJUiXYR zS?=g_N#p*ksHmt6;m>RUbz%jSTTy@wTkQ^~vgrMa0dhTnQe;Gpip$ck^nN8C@L3s? z@J5k;KL}JJ^YLbCB1QVwfh&e)GQP=CNVem{z5B-m>VmE5Rkh7q^98DzzU3q z)ld@MJWVMY0Sl1Nwv!+vA?8<)&j6v2lb65n62ZgASJl>*_3+@WsQ7f1Z(2tL=$n5> zGr0i{J$=X%Rk-Oa(C;}K3(NhZrlKMWsO4FXW%cnc==VI`UuE*z5%BWzYT>*gSee?A z+&yw(=s(Zs^r`QP_PBhJN_ak+bRPLS(2HQfH+p3=L;n_{ynhrk*8Jjh+YdFRDQ?ll z8?QTVA?CK!K@1FLJ;~W<50_dI1dGtvtq(4e;#3RO+ZH`N^m&z~6bT&@6qu{humEZq zApVvr<;qD*BLXP4dF@oyX;W&;^EcD`_kbJ_)!Hf){T=WZ6crZ2Nvi@Ph^8y-!^6X3 zqaESc*jU1rS<7T+1(O3XEsU(Jjd^2-?w_bb^x;ZPUBa@)r(1on%7b8k{47Ef=9@|t zg3a-m$?#ucMzvHb0ys$1`j__$7TEBfutoH;2FUc&m+u4VGE4RR{MvbOspk1V%#7#u zj<<&^=ss5#vIO+aRbfHV;nFBcRhh!UV$5r?Hg~3urBUhc2<_x^UcDW{t490siqG4 zh#B`r3T#N!#%GBQW*|u22rEL9G@L&eZwLSLl#xFQ+`ihrKPhf$rc4itOrTHFbL9bS zL73sN33$Z$Bhjc@kTIPgRyS%C6wy?A=gP=l=SiJ)0jq;4X~b|&hEXF4M$^3MpEfhi zfH2T}_UG{qu$YYa-L4IcX3|2P*VfhmYthvFybN<{tFcYdkx-;m_ZLAf6k1{iY%C-q zv4t3dxFE(@CygO4?c!+_*!&SY-NG$rFP2yJO8|PXO!w|wu&`H&cMzq1szacKn0gJ* zLAraaBCh|D_RJ7HL@_y-)-57_?%oXq2bYD0hK}ZmW;fa`T;5Lv!Mt%$`I6qrdy2*$SExH7>cv4%;D6D7ecBVO%>Kqwgog1>Hu zzv%6%8y5eDFSmNB!hO|za-_SEA`eMp%Jb^JG6M)9OOIH{kH8?r(%qd*uy_h(J407` zXf<)`@_4BM(ERjX2@XI&f1FK}dHI9}m|qr_mTWLo`>w3Es&h>zdl<`tY0@f(2s%+e zzEkFMegY3!^NbwJ@Z^-*07Xk+p5D2x87X})JU&igQvY6FUeMfp7hE#x%P{j-d3O+Q)Jd$61#PqRbNV!B~3aeN&`n`KM=XlgCpI!pv^V$r$!D8{k6P!OoIX{+&P3Y zI;FtvqH1!{XcXxh1k{n@6r}P^g6kM?Lpy^=hZ@bfmzz$&=U8U34(*GZ(+}=2tAo?QO=qXs+$d6Y$iX8rhTf3xLM;~>%xYztr zdOl7g3IHuvOB4PCK&De{3u{`yVzo=W=3m?1B8W+V$`*E z?{kJXD*#mAY;bSNHBdC4oSO?>Ir$YFQ?$N4f~-Ew!^c-|(u4HNX+z1(jCRYJD1N{! zksc2f2;&3jopSXGLL?{uvq{_KjTk&A&G2AShztUQ64|%&cOploZyJ%t!dj}HanLk# z3kT91F)6d3=XXsmti()0O6M@j?*@p2x9dLfz=E!G*^ycI+QC@&-Y3VT6cZ^JZH@de z%;-jm8p?9pQ>vKlby$+V2yh(+y4^<8qJ zpvkp3R+7hz(-Vc5Joj$N|8J7W6aH?qe6(!lR%`Ve{Ys>jT?8CP0MRP}NF5Lny}bp2 zf{xjGoA=%Gi+rBX)e03#Qve6iUmy%3d<}EE0$do)`fP9bikX>YCcS9UK14{!X1y+Y z@6|Wei^hD6JUNk(h|c1Zjn^i%rJpUv`lT9N5Db?1)42}xlC^UtJ(wq5avCOwE7adL9b^* zT*@IcNNr+Tf3GvG@=U&UP~v6QhkYqs|3V3^T1seyGdy0Dwm^}B4@&Q>>-lN#lU@BB zTU1%@F66+vW(F#4Ys(>ZYz|6&udQZe6mJE0M5}<11m3#3f1?vWv}^X>41`I4<>ln+ zZI3PVL((9VZhwhb^*lM6o%kbSWKn91%&~Ss;6_W}mf{u|H{S-)uWzqLxhG_q<8o6|E#q3-1sO#~tqklak_rj4+7I37X)otIw*7 zkV^7$!(!6xH&BZy`-z|%^2Do(F9M3xxo4A4V=+0LE*Z_@jh>#K#!4X|iS9c%IQRy{ zjS!uyL({V$A~E%HJV5UE{9RsNZqgNu2za71mKvi&XJh^+4FeIw08fZ__$zb~f=lPx`ZXo3 zTTc}6_e3;{R=9favVx`bIUrNKjrhd=h^>Gw3z;Xiz?W-A;AS;mTcLzR6cnZ3zej9u zqp_=|AMTQqk%3d?bcq82{d0D1PS*yP3ar32Kg#DWAPIlM^xPiNzz=h+)i zVSt7!!6kk4N5-(#gvq8N0l|q*^_x`&QKBkZ8-3kQ!Ix)S%hdpW#2a;01Ep!EqOS^Q zeU*Ava!9%<<~KmOccLFWN#sOZrxTO^^q0XGkP{mJi2}<&{J$`n3@Tyy@mNhl~KB=Hc27Mzmnwool{dcuOC(We&PV$X}UZsOuAM^Udrgc%AcqP z8Vg*kWVlJ-skODSUk)p8TevX*K<)q|9gsWX5#B)82AFB#6l2hb&QJav+#{2|{#U}R z5YVAla2L1L{~pBehQGHPzFUo}8(vAnSD<#^9MSrx>&Ze#-}Uc#)2n7dpFI}#wmZ0$ zCZ`EM;v`dyYWkSV59tW$Sv(|@Xe@~Tap9kwtTZ)Pj*(c7a@Zmgj^}pW8=X*#@nq1Q zYZPn{S#)vPEkTMQAQA5{4VIvWVj`XX0YR1u&&*xK2}MjB(jqgycODc?7gQD&$|7c?tJ)@AI^Y}5wx7$00q zfPxVCYTku!#Q#Uq@n0D@$Dy7@kK!)`EGaM4b$=dIQR{yto$l@Kzx-|#U zVI%9*6*XtL(nKz0?K~53{EU*Kf=p}`<$QTT9PhYfW>Rc0Br|9e@NRaFr%@nTIyGNI z6EF3$6iq60{&%d)2l(fK`K=$vle({md~F7|#zc-tkdRvwCLdOCFTH6@{~aA+xSV-z zv9Zh4tsDxt60w4~=JyEz_XG_tLuw;Y1*~w-{sfM-BfNC~sf*cF^8ibHexw2%w0j{L z0*;U=0+#s~A0Hcg`@fyAW`3}NfX`4>UEQq2BEu%L^iPs^$1QC%c*Hsqq3ajsr_aaR zcMToic23p~26aPJL*OS;pZ^r#$})}bw1&Ti5a0Tv_L(pcp}*mXmu>bJnj?Ljb5j|-K$J#F4sGEB8+ zvIz>1`j|X997?j%-xtV=HP3`r{Lf(pw{fUtxl@Q?A&HE=tig%2lTd2hGS9iH)w_v+ z(6JL5Z1l_7WaZ1o8o#k5S7KzC0L+}xRqt}=7RXm;?G5_0u4L?a1)SXn)oluIzL0-=@MQ(6?{KvUE_n6fxfTp@Tti*m7jfF+CXtjSx>vik#W) zBYL?Ttfgn^3vdd6<+&6936H10Y~I2CBuq(FB7Pr{6tD(z@QCqP#ZtAEjv2??uMf$K!^H%PJ?o=nqtZIR=tigq)>L zhX0ui_3naW*|hSEkW#4DEul%sNM9H2`Lrr^OcdH~9~oObDc}vL!`4^g%qL_)E;7)V zi!j83=o>bS|A_vO3EPOu6z}o`C6h~;nIvs7mP7CW3*Mujj7bpMffWo;AkGnVYX$_* z79hWuf9IGFeX2?TgLP#HgcOT^ga;)nx>E>9jr_~~+5RW9pyw<>fiBS*Z5p1YU^rg> z?_kV_Ci!A@R*ki1T}S`x8pg_e)DM6oW#jeb*86ttbE50Wb`Q!{dZu%XjflR|G?iBV z8EB->JhfMuJ{f>ZKw01T@1GRBME=IGf-V7g*`|U;G?s0u2(w~`q4`2ydXEnhvUJ3x zVBh~(ddfZ_V_ql5$Uqs?i;Fm~HAt393m5HW$VO5~Vjzf8D9wP49nd7tAD_QhWbK?1 zf@BXk>sd%2?VM(2uL%{Kl$%BE?dzo74d&9rF!ZoG;te#C{S|1?xQLA`|QL={rtSU8?Sw#umVF zSib*vd)okLeZcd407OWj&JbXMxc(n1jY)1-k6uD*VgafI87imK}|+R zh`_+WB`2d_UXlG?61#c9jWEs&!ly5a;g_F6B*!Oy$0xGr{7J9_4rKiWl#6Lgc?XjJ z^CUZ~--H0BopAyr&sC2FgM=A>IB__NRD5rCjOqIR$R4#}AKZ(@*oB+YVNVL!>v!bT|#D$yAbX4jPDO3Pe4b zoH#nNG6ZgTiYJ4MW*N_x1KD;1F|$bge|i8N^b1+bM>ceZ;1E(_iep{QVw?kO^qudO zMgM+fH*aMJ$h>Hn**egh^s9WqG0E-#p=fC%XiJt2NtuQ1u>Vjh2SY^wRY*VJ`3A5{ zkRA!+|5g2MOBYo+y)rM;IuUbh^*Jsk1Q(y5FHy}z#mt?MWH}y+YT%U-tCMb$Ne4-! zf(AD)g20eOu%g_7DhYCZ#>`m*irLdh#^_$31T33AapdL_HkYj>YjnrC?CSaFfUvSr zWGny}yy>(+J?ZcQdFGuj{$2bkd^)a~Xy0xa`YP-b3%}@WZ?(a1oj<rbl-$T@;e9wT0pQ-DQEj?0c|`#r3gQaT3VuN)hCvo5Nbn(DBXyca^|bau&KO9y9X zWx#o+D!r!5)EZy9TN>P+PWa9r>0CtK)XmdLNK=9>oxDcRTuq|S2B=o^v@06HT~rfdk1V$v0pfre4js#C>-`fl^}{tKQ(mgcU$U0birNBY!GCo5gok6yWDdgeIR3FuxkiyD4ZqX`V4tGYMo^Jq;dV+bTF~{JW8}Q=12MZ8w zh$$#2EQU}}+$d`Wn?Osc-QSJeLrvbz!zu4oZ`#If0UFGi-DKl5Rb=eGYN80%v!cONyCtC#wo4-tLoEi9$JvrT2MJR$f8iC6_sc~gk431G0o{#t z46MFh=vHVOhBJr*{IaV{0w`y^@szul+YV(Q$3zvz@IIGf-HDv{&(WY$!c9}T=e*MA zsOct6*f#?>@MX{G1xZu$?eu2V&X942ae#O`L$>w9{YZ}go=-2Vus_~qz|l@@0MFIn zqC1g~6TSO1dLmUI?)TSGZR@+QHz{LT68^eONi$uO=NxMW25r~V@_p>v7gOV;@0#)j-h_4D=fwgb}d&!0S|@io9v3AG((D*slD@9aSb*Jx&| zcNd5j?-~DC(e?4O06Z|G{t)BtznDwP0_O~FMa8#MiMR|(=ecLT47a~p1Xzkku3Fwz z(~H>`bGp==Q%nqsQdv}5z?@&tD7yhQz3mGx#`hypk<$Tp2G4b{k?C;pI)>_u}zJDYu|_af?*i}2c=i2tSB2xDbUk+qiSFSwp$2jIAe z%YU;>KzZ5g;jqjbSe_rGrnyJrkgZKu-iH;IiCYstjKpGb+bBGVoi|%gCZ=v9g6lw0 zx@u;?IUwKXBcuIaD~>(;R|Skwoaw$l4^f4rgj$h3t`mq*v9ZD+(q_dVlp^e3EJe#F zz*#WNd_tc;lui~*r^A=3tYz>viC%efB>*67yWh)xI?R7z0?f=DVgB^SQ4JqyV*h4$ zhx10t2Wobge4 zyVg>>hsTdI9tJbK!0+fSWZeRmWPn-x{sS14pDAB2C~uCcf3qEZh9vR)*A&Uhe4VB( z9kwG2XV&IVX*<|y%9-i+gx`)}UY8WzVp`cEqGVw~2N?bHC#$+@zhB1lc+mtSJ@YEBUqS zW2{BsHstneOI~tIb9zg;DYb_tONZGWceY#ZsL8;Sy(3U8nl&vy`^v}x)5d6tiXP?n zm&- ziBl}fP&^__0vy9PFO*|U^)SV0utQ9&Mt}=ud>7~!4BltgG|0e06N@Vzl0uxMY8%<5 zF{{-|@o9)YX#M!aD{e&y%aedTbirAdnU72JpFP1A1)h|k=v4g=N=x5WXmYeGX9&J! zLSO3D0*Y6N-lD$32srwF>4}t3O3;N#-dn*Gl$_BErss@={{Ota8{Bz?<#*Zm`wh{^ l3iuxjm=-!vOGQN(X5c+CaJ_v^t0%v`K&6$WswIts{|}7XWBdRB literal 0 HcmV?d00001 diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index 885646e095..5221918461 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -18,8 +18,6 @@ internal class BountyHunter : RoleBase public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorKilling; //==================================================================\\ - public override Sprite GetKillButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Handoff"); - private static OptionItem OptionTargetChangeTime; private static OptionItem OptionSuccessKillCooldown; private static OptionItem OptionFailureKillCooldown; @@ -249,13 +247,12 @@ public override string GetLowerText(PlayerControl seer, PlayerControl seen = nul } public override string GetSuffix(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { - if (seer == null) return string.Empty; - if (!seer.Is(CustomRoles.BountyHunter)) return string.Empty; - if (seen != null && seer.PlayerId != seen.PlayerId) return string.Empty; - - if (!ShowTargetArrow || isForMeeting) return string.Empty; + if (!ShowTargetArrow || isForMeeting || seer.PlayerId != seen.PlayerId) return string.Empty; var targetId = GetTarget(seer); return TargetArrow.GetArrows(seer, targetId); } + + public override Sprite GetKillButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Handoff"); + public override Sprite GetAbilityButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Timer"); } diff --git a/Roles/Impostor/Mercenary.cs b/Roles/Impostor/Mercenary.cs index 5eedb8b600..f7964eb60d 100644 --- a/Roles/Impostor/Mercenary.cs +++ b/Roles/Impostor/Mercenary.cs @@ -99,6 +99,7 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } } + public override Sprite GetAbilityButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Timer"); public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.AbilityButton.OverrideText(GetString("MercenarySuicideButtonText")); diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index 1f04f6b302..a74cb0f747 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -127,6 +127,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl resetCooldown = false; return false; } + public override Sprite GetAbilityButtonSprite(PlayerControl player, bool shapeshifting) => CustomButton.Get("Timer"); public override void SetAbilityButtonText(HudManager hud, byte playerId) { From 4c0bd35485ac0535bd263a2b47925617954ed731 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 20:41:12 +0800 Subject: [PATCH 753/778] Fix --- Roles/Core/AssignManager/GhostRoleAssign.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 33cdb06717..0c69a96de4 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -53,6 +53,7 @@ or CustomRoles.Specter or CustomRoles.Sunnyboy or CustomRoles.Innocent or CustomRoles.Workaholic + or CustomRoles.Cultist or CustomRoles.PlagueDoctor) return; var IsNeutralAllowed = !player.IsAnySubRole(x => x.IsConverted()) || Options.ConvertedCanBecomeGhost.GetBool(); From 7a1d8cae31ffa0651fd1e9a50c923697a96d3b5f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 21:46:59 +0800 Subject: [PATCH 754/778] Add --- Roles/Core/AssignManager/GhostRoleAssign.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 0c69a96de4..9cd798d446 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -54,6 +54,8 @@ or CustomRoles.Sunnyboy or CustomRoles.Innocent or CustomRoles.Workaholic or CustomRoles.Cultist + or CustomRoles.Lawyer + or CustomRoles.Provocateur or CustomRoles.PlagueDoctor) return; var IsNeutralAllowed = !player.IsAnySubRole(x => x.IsConverted()) || Options.ConvertedCanBecomeGhost.GetBool(); From 7a379967fc0299aff18de997443c8ffb39e8c097 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Fri, 11 Oct 2024 21:51:30 +0800 Subject: [PATCH 755/778] Fix bug when some players stuck in walls after meeting --- Patches/ShipStatusPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 3d76d32fa3..5080a306d5 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -288,7 +288,7 @@ public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPl Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); Vector2 position = __instance.MeetingSpawnCenter + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f); - player.RpcTeleport(position, sendInfoInLogs: false); + player.RpcTeleport(position, isRandomSpawn: true, sendInfoInLogs: false); return false; } } @@ -307,7 +307,7 @@ public static bool Prefix(PolusShipStatus __instance, PlayerControl player, int ? __instance.MeetingSpawnCenter2 + Vector2.right * (num2 - num1) * 0.6f : __instance.MeetingSpawnCenter + Vector2.right * num2 * 0.6f; - player.RpcTeleport(position, sendInfoInLogs: false); + player.RpcTeleport(position, isRandomSpawn: true, sendInfoInLogs: false); return false; } } From b67cdc92adbd6984acc92788745b0727373a94af Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:43:20 -0400 Subject: [PATCH 756/778] a --- Roles/Neutral/PlagueBearer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 4628ff9a76..6e83aaa396 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -245,6 +245,7 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { + if (killer.IsNeutralApocalypse()) return false; target.RpcMurderPlayer(killer); killer.SetRealKiller(target); return false; From f15729101021aa0a20fc66653f891093397804a0 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sat, 12 Oct 2024 17:32:52 +0800 Subject: [PATCH 757/778] Change RpcSetSpecificScanner --- Modules/ExtendedPlayerControl.cs | 16 +++++++++++----- Roles/Crewmate/Overseer.cs | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e675751565..3caa89fe13 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -636,14 +636,20 @@ public static void RpcSetVentInteraction(this PlayerControl player) } public static void RpcSetSpecificScanner(this PlayerControl target, PlayerControl seer, bool IsActive) { - if (!AmongUsClient.Instance.AmHost) return; - - byte cnt = ++PlayerControl.LocalPlayer.scannerCount; - - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.SetScanner, SendOption.Reliable, seer.GetClientId()); + var seerClientId = seer.GetClientId(); + if (seerClientId == -1) return; + byte cnt = ++target.scannerCount; + if (AmongUsClient.Instance.ClientId == seerClientId) + { + target.SetScanner(IsActive, cnt); + return; + } + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.SetScanner, SendOption.Reliable, seerClientId); messageWriter.Write(IsActive); messageWriter.Write(cnt); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + target.scannerCount = cnt; } public static void RpcCheckVanishDesync(this PlayerControl player, PlayerControl seer) { diff --git a/Roles/Crewmate/Overseer.cs b/Roles/Crewmate/Overseer.cs index fac7e40631..02c650c70e 100644 --- a/Roles/Crewmate/Overseer.cs +++ b/Roles/Crewmate/Overseer.cs @@ -195,12 +195,10 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT var playerId = player.PlayerId; if (!player.IsAlive() || Pelican.IsEaten(playerId)) { - data.Item1.RpcSetSpecificScanner(player, false); OverseerTimer.Remove(playerId); SendTimerRPC(2, playerId); NotifyRoles(SpecifySeer: player); - } else { @@ -225,6 +223,8 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT SetRevealtPlayerRPC(player, farTarget, true); NotifyRoles(SpecifySeer: player); + + Logger.Info($"Revealed: {player.GetNameWithRole()}", "Overseer"); } else { From e6a4c97be1d9fee6a963ee3260ea9eaa88a3ab10 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 13 Oct 2024 13:47:49 +0800 Subject: [PATCH 758/778] Fix some bugs --- Modules/CustomRolesHelper.cs | 3 ++- Patches/PlayerControlPatch.cs | 5 +++++ Roles/Crewmate/Benefactor.cs | 1 - Roles/Crewmate/Bodyguard.cs | 2 +- Roles/Crewmate/Deceiver.cs | 2 +- Roles/Crewmate/Medic.cs | 3 +-- Roles/Impostor/Mastermind.cs | 1 - Roles/Neutral/Baker.cs | 2 +- Roles/Neutral/Terrorist.cs | 6 ++++-- Roles/Neutral/Workaholic.cs | 4 ++-- 10 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 18076cdd33..00fff67294 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -447,7 +447,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Doomsayer) || pc.Is(CustomRoles.Nemesis) || pc.Is(CustomRoles.Councillor) - || pc.Is(CustomRoles.GuardianAngelTOHE)) + || pc.Is(CustomRoles.GuardianAngelTOHE) + || pc.Is(CustomRoles.PunchingBag)) return false; if ((pc.Is(CustomRoles.Specter) && !Specter.CanGuess.GetBool()) || (pc.Is(CustomRoles.Terrorist) && (!Terrorist.TerroristCanGuess.GetBool() || Terrorist.CanTerroristSuicideWin.GetBool())) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e6d0dade9e..c72c7d1110 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -237,6 +237,11 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, Logger.Info($"check: {check}", "RpcCheckAndMurder"); if (target == null) target = killer; + if (killer == null || target == null) + { + Logger.Info($"Killer: {killer == null} or Target: {target == null} is null", "RpcCheckAndMurder"); + return false; + } var killerRole = killer.GetCustomRole(); diff --git a/Roles/Crewmate/Benefactor.cs b/Roles/Crewmate/Benefactor.cs index c1be891a4e..8cc9aed028 100644 --- a/Roles/Crewmate/Benefactor.cs +++ b/Roles/Crewmate/Benefactor.cs @@ -192,7 +192,6 @@ public override void AfterMeetingTasks() public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { - if (target == null || killer == null) return true; if (!shieldedPlayers.ContainsKey(target.PlayerId)) return false; if (ShieldIsOneTimeUse.GetBool()) diff --git a/Roles/Crewmate/Bodyguard.cs b/Roles/Crewmate/Bodyguard.cs index 8fa4c06756..9290ac24ab 100644 --- a/Roles/Crewmate/Bodyguard.cs +++ b/Roles/Crewmate/Bodyguard.cs @@ -25,7 +25,7 @@ public override void SetupCustomOption() public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { var bodyguard = _Player; - if (!bodyguard.IsAlive() || killer?.PlayerId == target.PlayerId || bodyguard.PlayerId == target.PlayerId) return false; + if (!bodyguard.IsAlive() || killer.PlayerId == target.PlayerId || bodyguard.PlayerId == target.PlayerId) return false; var killerRole = killer.GetCustomRole(); // Not should kill diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index 85b4444a17..1bc0756acc 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -84,7 +84,7 @@ public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _ var killer = _Player; var target = pc; - if (killer == null) return true; + if (killer == null) return false; target.SetDeathReason(PlayerState.DeathReason.Misfire); target.RpcMurderPlayer(target); diff --git a/Roles/Crewmate/Medic.cs b/Roles/Crewmate/Medic.cs index 8d4dd7c84f..5e2a6b15d6 100644 --- a/Roles/Crewmate/Medic.cs +++ b/Roles/Crewmate/Medic.cs @@ -159,8 +159,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { - if (killer == null || target == null || _Player == null) return true; - if (!IsProtect(target.PlayerId)) return false; + if (_Player == null || !IsProtect(target.PlayerId)) return false; var medic = _Player; SendRPC(); diff --git a/Roles/Impostor/Mastermind.cs b/Roles/Impostor/Mastermind.cs index c221fbc546..2c975710fc 100644 --- a/Roles/Impostor/Mastermind.cs +++ b/Roles/Impostor/Mastermind.cs @@ -149,7 +149,6 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { - if (killer == null || target == null) return true; if (!PlayerIsManipulated(killer)) return false; ManipulatedPlayers.Remove(killer.PlayerId); diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 2e3679a50c..a8a7741aca 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -237,7 +237,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { - if (_Player == null || !_Player.IsAlive() || killer == null || target == null) return false; + if (_Player == null || !_Player.IsAlive()) return false; if (!BarrierList[_Player.PlayerId].Contains(target.PlayerId)) return false; killer.RpcGuardAndKill(target); diff --git a/Roles/Neutral/Terrorist.cs b/Roles/Neutral/Terrorist.cs index 33889dd1e3..85e2cc5824 100644 --- a/Roles/Neutral/Terrorist.cs +++ b/Roles/Neutral/Terrorist.cs @@ -10,7 +10,7 @@ internal class Terrorist : RoleBase private const int id = 15400; private static readonly HashSet PlayerIds = []; public static bool HasEnabled = PlayerIds.Any(); - + public override CustomRoles ThisRoleBase => CustomRoles.Engineer; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; //==================================================================\\ @@ -45,6 +45,8 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuicide) { + if (target.IsDisconnected()) return; + Logger.Info(target?.Data?.PlayerName + " was Terrorist", "AfterPlayerDeathTasks"); CheckTerroristWin(target.Data); } @@ -81,7 +83,7 @@ private static void CheckTerroristWin(NetworkedPlayerInfo terroristData) pc.SetDeathReason(PlayerState.DeathReason.Suicide); } } - else if (!pc.Data.IsDead) + else if (pc.IsAlive()) { pc.SetDeathReason(PlayerState.DeathReason.Bombed); Main.PlayerStates[pc.PlayerId].SetDead(); diff --git a/Roles/Neutral/Workaholic.cs b/Roles/Neutral/Workaholic.cs index a573c795e4..df19db45c1 100644 --- a/Roles/Neutral/Workaholic.cs +++ b/Roles/Neutral/Workaholic.cs @@ -62,8 +62,8 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { - var AllTasksCount = player.Data.Tasks.Count; - if (!((completedTaskCount) >= AllTasksCount && !(WorkaholicCannotWinAtDeath.GetBool() && !player.IsAlive()))) return true; + var taskState = player.GetPlayerTaskState(); + if (!taskState.IsTaskFinished || (WorkaholicCannotWinAtDeath.GetBool() && !player.IsAlive())) return true; Logger.Info("The Workaholic task is done", "Workaholic"); if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return true; From da20f117b0bdd80c81c0a822add199feb774cb9d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 13 Oct 2024 13:59:32 +0800 Subject: [PATCH 759/778] Add Shuffle for Common Tasks in RpcSetTasks --- Patches/TaskAssignPatch.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs index bd04033c49..486116aca1 100644 --- a/Patches/TaskAssignPatch.cs +++ b/Patches/TaskAssignPatch.cs @@ -193,6 +193,7 @@ public static void Prefix(NetworkedPlayerInfo __instance, [HarmonyArgument(0)] r int defaultCommonTasksNum = Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); if (hasCommonTasks) TasksList.RemoveRange(defaultCommonTasksNum, TasksList.Count - defaultCommonTasksNum); else TasksList.Clear(); + TasksList = Shuffle(TasksList); // A HashSet into which allocated tasks can be placed // Prevents multiple assignments of the same task @@ -204,13 +205,13 @@ public static void Prefix(NetworkedPlayerInfo __instance, [HarmonyArgument(0)] r Il2CppSystem.Collections.Generic.List LongTasks = new(); foreach (var task in ShipStatus.Instance.LongTasks) LongTasks.Add(task); - Shuffle(LongTasks); + LongTasks = Shuffle(LongTasks); // List of short tasks that can be assigned Il2CppSystem.Collections.Generic.List ShortTasks = new(); foreach (var task in ShipStatus.Instance.ShortTasks) ShortTasks.Add(task); - Shuffle(ShortTasks); + ShortTasks = Shuffle(ShortTasks); // Use the function to assign tasks that are actually used on the Among Us side ShipStatus.Instance.AddTasksFromList( @@ -236,15 +237,16 @@ public static void Prefix(NetworkedPlayerInfo __instance, [HarmonyArgument(0)] r } } - public static void Shuffle(Il2CppSystem.Collections.Generic.List list) + public static Il2CppSystem.Collections.Generic.List Shuffle(Il2CppSystem.Collections.Generic.List list) { - for (int i = 0; i < list.Count - 1; i++) + int listCount = list.Count; + while (listCount > 1) { - T obj = list[i]; - int rand = UnityEngine.Random.Range(i, list.Count); - list[i] = list[rand]; - list[rand] = obj; + listCount--; + int k = IRandom.Instance.Next(listCount + 1); + (list[listCount], list[k]) = (list[k], list[listCount]); } + return list; } } From db06baffd77a80c1c02dc4bfe3bacf82d8f66a65 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 13 Oct 2024 14:00:49 +0800 Subject: [PATCH 760/778] Ok --- Patches/PlayerControlPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index c72c7d1110..9eef09af79 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -237,7 +237,7 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, Logger.Info($"check: {check}", "RpcCheckAndMurder"); if (target == null) target = killer; - if (killer == null || target == null) + if (killer == null) { Logger.Info($"Killer: {killer == null} or Target: {target == null} is null", "RpcCheckAndMurder"); return false; From f21362e2c59b8f2c73580a6dd4a8643decd0f5c5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 13 Oct 2024 14:05:26 +0800 Subject: [PATCH 761/778] Check --- Roles/Core/AssignManager/GhostRoleAssign.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 9cd798d446..3a0ed54d5d 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -56,6 +56,7 @@ or CustomRoles.Workaholic or CustomRoles.Cultist or CustomRoles.Lawyer or CustomRoles.Provocateur + or CustomRoles.Virus or CustomRoles.PlagueDoctor) return; var IsNeutralAllowed = !player.IsAnySubRole(x => x.IsConverted()) || Options.ConvertedCanBecomeGhost.GetBool(); From d46960ccfcca79c25fc212bf6c31779104735762 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 13 Oct 2024 12:31:18 -0400 Subject: [PATCH 762/778] made settings actually used --- Modules/GuessManager.cs | 24 ++++++++++++++++++++++++ Roles/Neutral/Baker.cs | 2 ++ Roles/Neutral/Berserker.cs | 2 ++ Roles/Neutral/PlagueBearer.cs | 2 ++ Roles/Neutral/SoulCollector.cs | 2 ++ 5 files changed, 32 insertions(+) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index b89db6b70d..b9ed37e4ec 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -284,6 +284,30 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) return true; } } + if (role.IsImpostor() && !Options.ImpCanGuessImp.GetBool()) + { + if (Options.ImpostorsCanGuess.GetBool() && (pc.Is(Custom_Team.Impostor) || pc.GetCustomRole().IsMadmate()) && !(pc.Is(CustomRoles.EvilGuesser) || pc.Is(CustomRoles.Guesser))) + { + pc.ShowInfoMessage(isUI, GetString("GuessImpRole")); + return true; + } + } + if (role.IsCrewmate() && !Options.CrewCanGuessCrew.GetBool()) + { + if (Options.CrewmatesCanGuess.GetBool() && pc.Is(Custom_Team.Crewmate) && !(pc.Is(CustomRoles.NiceGuesser) || pc.Is(CustomRoles.Guesser))) + { + pc.ShowInfoMessage(isUI, GetString("GuessCrewRole")); + return true; + } + } + if (role.IsNA() && !Options.ApocCanGuessApoc.GetBool()) + { + if (Options.NeutralApocalypseCanGuess.GetBool() && pc.IsNeutralApocalypse() && !pc.Is(CustomRoles.Guesser)) + { + pc.ShowInfoMessage(isUI, GetString("GuessApocRole")); + return true; + } + } } if (pc.PlayerId == target.PlayerId) diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 2e3679a50c..392ad35153 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -263,6 +263,7 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT player.Notify(GetString("BakerToFamine")); player.RpcGuardAndKill(player); } + /* public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -272,6 +273,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl } return false; } + */ } internal class Famine : RoleBase { diff --git a/Roles/Neutral/Berserker.cs b/Roles/Neutral/Berserker.cs index 463d7e3122..5df3c23b1d 100644 --- a/Roles/Neutral/Berserker.cs +++ b/Roles/Neutral/Berserker.cs @@ -181,6 +181,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t SendRPC(); return noScav; } + /* public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -190,6 +191,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl } return false; } + */ } internal class War : RoleBase { diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 4628ff9a76..cc866444b7 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -215,6 +215,7 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.KillButton.OverrideText(GetString("InfectiousKillButtonText")); } + /* public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -224,6 +225,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl } return false; } + */ } internal class Pestilence : RoleBase diff --git a/Roles/Neutral/SoulCollector.cs b/Roles/Neutral/SoulCollector.cs index c5eb8cb157..7b07be1f22 100644 --- a/Roles/Neutral/SoulCollector.cs +++ b/Roles/Neutral/SoulCollector.cs @@ -157,6 +157,7 @@ public override void AfterMeetingTasks() _Player.RpcGuardAndKill(_Player); } } + /* public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl guesser, CustomRoles role, ref bool guesserSuicide) { if (!ApocCanGuessApoc.GetBool() && target.IsNeutralApocalypse() && guesser.IsNeutralApocalypse()) @@ -166,6 +167,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl } return false; } + */ } internal class Death : RoleBase { From dcd15d3238443385e3a39fee27d04a8a696933a5 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Mon, 14 Oct 2024 01:15:29 +0800 Subject: [PATCH 763/778] Add Check Infacted After Meeting For PlagueBearer --- Roles/Neutral/PlagueBearer.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index 6e83aaa396..a0b197103e 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -70,9 +70,8 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public static void SendRPC(PlayerControl player, PlayerControl target) { - MessageWriter writer; - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(Utils.GetPlayerById(playerIdList.First())); // setPlaguedPlayer + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(playerIdList.First().GetPlayer()); writer.Write(player.PlayerId); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -89,7 +88,7 @@ public static void CheckAndInfect(PlayerControl seer, PlayerControl target) bool needCheck = false; foreach (var (plagueBearerId, Targets) in PlaguedList) { - var plagueBearer = GetPlayerById(plagueBearerId); + var plagueBearer = plagueBearerId.GetPlayer(); if (plagueBearer == null || !plagueBearer.IsAlive()) continue; if (target.Is(CustomRoles.PlagueBearer) && !isDisconnectOrSelfKill) @@ -142,7 +141,7 @@ public static void CheckPlagueAllPlayers() { foreach (var PlagueId in PlaguedList.Keys) { - var plagueBearer = GetPlayerById(PlagueId); + var plagueBearer = PlagueId.GetPlayer(); if (plagueBearer == null) continue; if (IsPlaguedAll(plagueBearer)) @@ -195,6 +194,10 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadBody, bool inM CheckAndInfect(killer, deadBody); } } + public override void AfterMeetingTasks() + { + CheckPlagueAllPlayers(); + } public override string GetMark(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) => IsPlagued(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.PlagueBearer), "⦿") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) From d65abc1a38fb2d4d2692634016ff39b440fb3321 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:39:40 -0400 Subject: [PATCH 764/778] Update en_US.json --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index f65f8b88bd..b72b636492 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1021,7 +1021,7 @@ "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", - "ApocolypseInfoLong": "(Apocolypse):\nApocolypse members are on a seperate team which work together and win together. If there are multiple apocolypse roles in the game, they can see each other's roles.\nDepending on the Host's settings, Apocolypse roles can guess or be guessed.", + "ApocalypseInfoLong": "(Apocalypse):\nApocalypse members are on a separate team that works together and wins together. If there are multiple Apocalypse roles in the game, they can see each other's roles.\nDepending on the Host's settings, Apocalypse roles can guess or be guessed.", "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", From 50e9b4f0147c70168d90869daa0da070051cb018 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Wed, 16 Oct 2024 12:14:10 +0800 Subject: [PATCH 765/778] Some fix --- Modules/ExtendedPlayerControl.cs | 4 +- Patches/CheckGameEndPatch.cs | 5 ++- Patches/ExilePatch.cs | 2 + Roles/AddOns/Common/Avanger.cs | 2 +- Roles/Crewmate/Merchant.cs | 24 +++++------ Roles/Impostor/Penguin.cs | 4 +- Roles/Neutral/Baker.cs | 73 ++++++++++++++++++++++---------- Roles/Neutral/Pelican.cs | 2 +- 8 files changed, 71 insertions(+), 45 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 3caa89fe13..52561ad85f 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -82,9 +82,9 @@ public static void RpcRevive(this PlayerControl player) if (player.HasGhostRole()) { - player.GetRoleClass().Remove(player.PlayerId); + player.GetRoleClass().OnRemove(player.PlayerId); player.RpcSetCustomRole(player.GetRoleMap().CustomRole); - player.GetRoleClass().Add(player.PlayerId); + player.GetRoleClass().OnAdd(player.PlayerId); } if (Camouflage.IsCamouflage) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 289ce8e94c..a9c205bc21 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -356,9 +356,10 @@ public static bool Prefix() var lawyertarget = lawerClass.GetTargetId(); if (WinnerIds.Contains(lawyertarget) || (Main.PlayerStates.TryGetValue(lawyertarget, out var lawyerTargetPS) && WinnerRoles.Contains(lawyerTargetPS.MainRole))) + { WinnerIds.Add(pc.PlayerId); - - AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); + AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); + } } break; case CustomRoles.Follower when Follower.BetPlayer.TryGetValue(pc.PlayerId, out var followerTarget) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 60491525ed..a1436a0dd2 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -118,6 +118,8 @@ private static void WrapUpPostfix(NetworkedPlayerInfo exiled) { player.GetRoleClass()?.OnPlayerExiled(player, exiled); + player.ResetKillCooldown(); + // Check for remove pet player.RpcRemovePet(); } diff --git a/Roles/AddOns/Common/Avanger.cs b/Roles/AddOns/Common/Avanger.cs index d121f011b6..edbf1b64dc 100644 --- a/Roles/AddOns/Common/Avanger.cs +++ b/Roles/AddOns/Common/Avanger.cs @@ -24,7 +24,7 @@ public void Remove(byte playerId) public static void OnMurderPlayer(PlayerControl target) { - var pcList = Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != target.PlayerId && !Pelican.IsEaten(pc.PlayerId) && !Medic.IsProtected(pc.PlayerId) + var pcList = Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != target.PlayerId && !Pelican.IsEaten(pc.PlayerId) && !Guardian.CannotBeKilled(pc) && !Medic.IsProtected(pc.PlayerId) && !pc.Is(CustomRoles.Pestilence) && !pc.Is(CustomRoles.Necromancer) && !pc.Is(CustomRoles.PunchingBag) && !pc.Is(CustomRoles.Solsticer) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18)).ToList(); if (pcList.Any()) diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 9135cd1441..4d7a7e3115 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -88,8 +88,8 @@ public override void Init() public override void Add(byte playerId) { playerIdList.Add(playerId); - addonsSold.Add(playerId, 0); - bribedKiller.Add(playerId, []); + addonsSold[playerId] = 0; + bribedKiller.TryAdd(playerId, []); } public override void Remove(byte playerId) { @@ -161,13 +161,6 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount ).ToList(); } - if (AllAlivePlayer.Count == 0) - { - player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); - Logger.Info("All Alive Player Count = 0", "Merchant"); - return true; - } - PlayerControl target = AllAlivePlayer.RandomElement(); target.RpcSetCustomRole(addon); @@ -178,6 +171,12 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount addonsSold[player.PlayerId] += 1; } + else + { + player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); + Logger.Info("All Alive Player Count = 0", "Merchant"); + return true; + } return true; } @@ -193,7 +192,7 @@ public override bool OnRoleGuess(bool isUI, PlayerControl target, PlayerControl public static bool OnClientMurder(PlayerControl killer, PlayerControl target) { - if (bribedKiller[target.PlayerId].Contains(killer.PlayerId)) + if (IsBribedKiller(killer, target)) { NotifyBribery(killer, target); return true; @@ -209,10 +208,7 @@ public static bool OnClientMurder(PlayerControl killer, PlayerControl target) return false; } - public static bool IsBribedKiller(PlayerControl killer, PlayerControl target) - { - return bribedKiller[target.PlayerId].Contains(killer.PlayerId); - } + public static bool IsBribedKiller(PlayerControl killer, PlayerControl target) => bribedKiller.TryGetValue(target.PlayerId, out var targets) && targets.Contains(killer.PlayerId); private static void NotifyBribery(PlayerControl killer, PlayerControl target) { diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index a74cb0f747..f2b2d238dd 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -36,7 +36,7 @@ public override void SetupCustomOption() OptionAbductTimerLimit = FloatOptionItem.Create(Id + 11, "PenguinAbductTimerLimit", new(1f, 20f, 1f), 10f, TabGroup.ImpostorRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Penguin]) .SetValueFormat(OptionFormat.Seconds); - OptionMeetingKill = BooleanOptionItem.Create(Id + 12, "PenguinMeetingKill", false, TabGroup.ImpostorRoles, false) + OptionMeetingKill = BooleanOptionItem.Create(Id + 13, "PenguinMeetingKill", true, TabGroup.ImpostorRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Penguin]); } public override void Add(byte playerId) @@ -148,8 +148,8 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf if (!AmongUsClient.Instance.AmHost) return; if (AbductVictim == null) return; _Player?.RpcMurderPlayer(AbductVictim); - RemoveVictim(); } + RemoveVictim(); } public override void AfterMeetingTasks() { diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 98ceee7d1f..5989d8c136 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -29,7 +29,7 @@ internal class Baker : RoleBase public static readonly Dictionary> BreadList = []; private static readonly Dictionary> RevealList = []; private static readonly Dictionary> BarrierList = []; - public static readonly Dictionary> FamineList = []; + private static bool CanUseAbility; public static bool StarvedNonBreaded; @@ -48,7 +48,7 @@ public override void Init() BreadList.Clear(); RevealList.Clear(); BarrierList.Clear(); - FamineList.Clear(); + Famine.FamineList.Clear(); CanUseAbility = false; StarvedNonBreaded = false; } @@ -58,7 +58,7 @@ public override void Add(byte playerId) BreadList[playerId] = []; RevealList[playerId] = []; BarrierList[playerId] = []; - FamineList[playerId] = []; + Famine.FamineList[playerId] = []; CanUseAbility = true; StarvedNonBreaded = false; BreadID = 0; @@ -78,9 +78,9 @@ private static (int, int) BreadedPlayerCount(byte playerId) return (breaded, all); } public static byte CurrentBread() => BreadID; - public static void SendRPC(PlayerControl player, PlayerControl target) + private static void SendRPC(PlayerControl player, PlayerControl target) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); writer.WriteNetObject(player); writer.Write(player.PlayerId); writer.Write(target.PlayerId); @@ -90,7 +90,9 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { byte BakerId = reader.ReadByte(); byte BreadHolderId = reader.ReadByte(); + BreadList[BakerId].Add(BreadHolderId); + BarrierList[BakerId].Add(BreadHolderId); } public override string GetProgressText(byte playerId, bool comms) => ColorString(GetRoleColor(CustomRoles.Baker).ShadeColor(0.25f), $"({BreadedPlayerCount(playerId).Item1}/{BreadedPlayerCount(playerId).Item2})"); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); @@ -98,13 +100,11 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) { if (target.IsNeutralApocalypse() && seer.IsNeutralApocalypse()) return true; // i swear this isn't consigliere's code i swear - var revealed = false; - RevealList.Do(x => + if (seer.IsAlive() && RevealList.TryGetValue(seer.PlayerId, out var targets)) { - if (x.Value != null && seer.PlayerId == x.Key && x.Value.Contains(target.PlayerId) && GetPlayerById(x.Key).IsAlive()) - revealed = true; - }); - return revealed; + return targets.Contains(target.PlayerId); + } + return false; } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) { @@ -211,14 +211,18 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr else if (HasBread(killer.PlayerId, target.PlayerId)) killer.Notify(GetString("BakerAlreadyBreaded")); - else { + else + { BreadList[killer.PlayerId].Add(target.PlayerId); - SendRPC(killer, target); + NotifyRoles(SpecifySeer: killer); killer.Notify(GetString("BakerBreaded")); - Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); CanUseAbility = false; - if (BTOS2Baker.GetBool()) { + + Logger.Info($"Bread given to " + target.GetRealName(), "Baker"); + + if (BTOS2Baker.GetBool()) + { switch (BreadID) { case 0: // Reveal @@ -232,6 +236,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr break; } } + SendRPC(killer, target); } return false; } @@ -283,6 +288,9 @@ internal class Famine : RoleBase public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralApocalypse; //==================================================================\\ + + public static readonly Dictionary> FamineList = []; + public override void Add(byte playerId) { CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); @@ -297,17 +305,36 @@ public override void Add(byte playerId) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) => false; public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("FamineKillButtonText")); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => Baker.FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"⁂" : string.Empty; + => FamineList[seer.PlayerId].Contains(seen.PlayerId) ? $"⁂" : string.Empty; + + private static void SendRPC(PlayerControl player, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(player); + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte FamineId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + + FamineList[FamineId].Add(targetId); + } public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (target.IsNeutralApocalypse()) killer.Notify(GetString("FamineCantStarveApoc")); - else if (Baker.FamineList[killer.PlayerId].Contains(target.PlayerId)) + if (target.IsNeutralApocalypse()) + killer.Notify(GetString("FamineCantStarveApoc")); + + else if (FamineList[killer.PlayerId].Contains(target.PlayerId)) killer.Notify(GetString("FamineAlreadyStarved")); + else if (Baker.StarvedNonBreaded) { - Baker.FamineList[killer.PlayerId].Add(target.PlayerId); - Baker.SendRPC(killer, target); + FamineList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); NotifyRoles(SpecifySeer: killer); killer.Notify(GetString("FamineStarved")); Logger.Info(target.GetRealName() + $" has been starved", "Famine"); @@ -316,17 +343,17 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) { - foreach (var playerId in Baker.FamineList.Keys.ToArray()) + foreach (var playerId in FamineList.Keys.ToArray()) { if (deadPlayer.PlayerId == playerId) { - Baker.FamineList[playerId].Remove(playerId); + FamineList[playerId].Remove(playerId); } } } public override void OnReportDeadBody(PlayerControl sylveon, NetworkedPlayerInfo iscute) { - foreach (var pc in Baker.FamineList) + foreach (var pc in FamineList) { foreach (var tar in pc.Value) { diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index a94ccb728c..4f0911ae5d 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -114,7 +114,7 @@ public static bool CanEat(PlayerControl pc, byte id) } } - return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.IsProtected(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); + return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.IsProtected(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id); } public static Vector2 GetBlackRoomPSForPelican() { From 0632afb51b4d044eaa2948113fc77cfa75dd762b Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Thu, 17 Oct 2024 12:38:30 +0800 Subject: [PATCH 766/778] Some fix --- Patches/ExilePatch.cs | 8 +++++++- Roles/AddOns/Common/Eavesdropper.cs | 4 ---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index a1436a0dd2..bddba489b6 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -175,12 +175,18 @@ private static void WrapUpFinalizer(NetworkedPlayerInfo exiled) Main.AfterMeetingDeathPlayers.Clear(); Utils.AfterMeetingTasks(); - AntiBlackout.ResetAfterMeeting(); Utils.SyncAllSettings(); Utils.CheckAndSetVentInteractions(); Utils.NotifyRoles(NoCache: true); }, 1.2f, "AfterMeetingDeathPlayers Task"); } + _ = new LateTask(() => + { + if (GameStates.IsEnded) return; + + AntiBlackout.ResetAfterMeeting(); + }, 2f, "Reset Cooldown After Meeting"); + //This should happen shortly after the Exile Controller wrap up finished for clients //For Certain Laggy clients 0.8f delay is still not enough. The finish time can differ. //If the delay is too long, it will influence other normal players' view diff --git a/Roles/AddOns/Common/Eavesdropper.cs b/Roles/AddOns/Common/Eavesdropper.cs index 9816f12e2f..f26b7ae389 100644 --- a/Roles/AddOns/Common/Eavesdropper.cs +++ b/Roles/AddOns/Common/Eavesdropper.cs @@ -9,10 +9,6 @@ public class Eavesdropper : IAddon public static bool IsEnable = false; public AddonTypes Type => AddonTypes.Helpful; - public static OptionItem ImpCanBeEavesdropper; - public static OptionItem CrewCanBeEavesdropper; - public static OptionItem NeutralCanBeEavesDropper; - public static OptionItem EavesdropPercentChance; public void SetupCustomOption() From 496876048a025cdc58c9163aa5bf77399d9f2c15 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 18 Oct 2024 19:05:57 -0400 Subject: [PATCH 767/778] Update Bastion.cs --- Roles/Crewmate/Bastion.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Roles/Crewmate/Bastion.cs b/Roles/Crewmate/Bastion.cs index 5350d811bd..47cacc4670 100644 --- a/Roles/Crewmate/Bastion.cs +++ b/Roles/Crewmate/Bastion.cs @@ -90,6 +90,11 @@ public override bool OnCoEnterVentOthers(PlayerPhysics physics, int ventId) Logger.Info("DoubleAgent enter in bombed vent, bombed is cancel", "Bastion.OnCoEnterVentOther"); return false; } + else if (pc.IsTransformedNeutralApocalypse()) + { + Logger.Info("Horseman enter in bombed vent, bombed is cancel", "Bastion.OnCoEnterVentOther"); + return false; + } else { _ = new LateTask(() => From 2bf6349ea687e6fdf71df3efcbadc30731764d49 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 19 Oct 2024 12:11:38 -0400 Subject: [PATCH 768/778] cant get unlucky --- Modules/CustomRolesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 00fff67294..da437a02f7 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -707,7 +707,8 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Solsticer) || pc.Is(CustomRoles.Taskinator) || pc.Is(CustomRoles.NiceMini) - || pc.Is(CustomRoles.PunchingBag)) + || pc.Is(CustomRoles.PunchingBag) + || pc.IsTransformedNeutralApocalypse()) return false; break; From 46ed0c10145f3efb1ebf76fe60239e126c5eb937 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 19 Oct 2024 12:13:57 -0400 Subject: [PATCH 769/778] Update Unlucky.cs --- Roles/AddOns/Common/Unlucky.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Roles/AddOns/Common/Unlucky.cs b/Roles/AddOns/Common/Unlucky.cs index 4f5055cf73..a0a30815f1 100644 --- a/Roles/AddOns/Common/Unlucky.cs +++ b/Roles/AddOns/Common/Unlucky.cs @@ -55,7 +55,7 @@ public static bool SuicideRand(PlayerControl victim, StateSuicide state) _ => -1 }; - if (shouldBeSuicide) + if (shouldBeSuicide && !victim.IsTransformedNeutralApocalypse()) { victim.SetDeathReason(PlayerState.DeathReason.Suicide); victim.RpcMurderPlayer(victim); @@ -63,4 +63,4 @@ public static bool SuicideRand(PlayerControl victim, StateSuicide state) } return false; } -} \ No newline at end of file +} From 31b3f2922a51efbca74388150b45f5543b6a1243 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sun, 20 Oct 2024 14:50:21 +0800 Subject: [PATCH 770/778] Fix 100% amhost error --- Modules/DelayNetworkedData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/DelayNetworkedData.cs b/Modules/DelayNetworkedData.cs index d4f6bdcc61..fbb9dfae22 100644 --- a/Modules/DelayNetworkedData.cs +++ b/Modules/DelayNetworkedData.cs @@ -183,7 +183,7 @@ public static void Spawn_Postfix(InnerNetClient __instance, InnerNetObject netOb } } - if (__instance.AmClient) + if (!__instance.AmHost) { Debug.LogError("Tried to spawn while not host:" + (netObjParent?.ToString())); } From 081c3fd34bf14e6f8248b4e05abf344d2f4303d7 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 15:12:25 +0800 Subject: [PATCH 771/778] Changes --- Roles/Core/RoleBase.cs | 2 +- Roles/Crewmate/Mortician.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index d9b0c921f3..67600c9e1a 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -10,7 +10,7 @@ public abstract class RoleBase { public PlayerState _state; #pragma warning disable IDE1006 - public PlayerControl _Player => Utils.GetPlayerById(_state.PlayerId) ?? null; + public PlayerControl _Player => _state != null ? Utils.GetPlayerById(_state.PlayerId) ?? null : null; public List _playerIdList => Main.PlayerStates.Values.Where(x => x.MainRole == _state.MainRole).Select(x => x.PlayerId).Cast().ToList(); #pragma warning restore IDE1006 diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index 236bb0e71d..0f4cfa26d7 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -69,9 +69,9 @@ public override void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo targ if (name == string.Empty) msgToSend.TryAdd(pc.PlayerId, string.Format(GetString("MorticianGetNoInfo"), target.PlayerName)); else msgToSend.TryAdd(pc.PlayerId, string.Format(GetString("MorticianGetInfo"), target.PlayerName, name)); } - public override string GetSuffix(PlayerControl seer, PlayerControl target = null, bool isForMeeting = false) + public override string GetSuffix(PlayerControl seer, PlayerControl seen, bool isForMeeting = false) { - if (!ShowArrows.GetBool() || isForMeeting || seer.PlayerId != target.PlayerId) return string.Empty; + if (!ShowArrows.GetBool() || isForMeeting || seer.PlayerId != seen.PlayerId) return string.Empty; return Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); } From 9e1d757a009b02d1fbe7f73a39f5dee36558507f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 15:23:34 +0800 Subject: [PATCH 772/778] Fix Dark Theme in lasted version AU --- Patches/ChatBubblePatch.cs | 4 ++-- TOHE.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index 598f71b305..c1b0bff881 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -35,9 +35,9 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe if (Main.DarkTheme.Value) { if (isDead) - __instance.Background.color = new Color(0.0f, 0.0f, 0.0f, 0.6f); + __instance.Background.color = new(0.1f, 0.1f, 0.1f, 0.6f); else - __instance.Background.color = Color.black; + __instance.Background.color = new(0.1f, 0.1f, 0.1f, 1f); __instance.TextArea.color = Color.white; } diff --git a/TOHE.csproj b/TOHE.csproj index 9fb86b6925..dc2eb50790 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 73cc13ba19f3c8138758481f42eabdbb5378fc3d Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 15:31:15 +0800 Subject: [PATCH 773/778] Fix Double Shot assign NA when NA Can Guess is off --- Modules/CustomRolesHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index da437a02f7..44668dbab7 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -544,7 +544,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; if (DoubleShot.CrewCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && (pc.Is(Custom_Team.Crewmate) && !Options.CrewmatesCanGuess.GetBool())) return false; - if (DoubleShot.NeutralCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Doomsayer) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.GetCustomRole().IsNK() && !Options.NeutralKillersCanGuess.GetBool()))) + if (DoubleShot.NeutralCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Doomsayer) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.GetCustomRole().IsNK() && !Options.NeutralKillersCanGuess.GetBool()) || (pc.GetCustomRole().IsNA() && !Options.NeutralApocalypseCanGuess.GetBool()))) return false; } if ((pc.Is(Custom_Team.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool())) From c88973617fbbcbf79dbaef6310ff941b0ff30d01 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 15:58:26 +0800 Subject: [PATCH 774/778] Some fix --- Modules/AntiBlackout.cs | 5 ++++- Patches/ChatCommandPatch.cs | 2 +- Patches/ExilePatch.cs | 2 -- Patches/GameOptionsMenuPatch.cs | 6 +++--- Patches/NotificationPopperPatch.cs | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index f797c359e0..bcaa3ea1b4 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -262,8 +262,11 @@ private static void ResetAllCooldown() { if (seer.IsAlive()) { - seer.SetKillCooldown(); seer.RpcResetAbilityCooldown(); + seer.ResetKillCooldown(); + + if (Main.AllPlayerKillCooldown.TryGetValue(seer.PlayerId, out var kcd) && kcd >= 2f) + seer.SetKillCooldown(kcd - 2f); } else if (seer.HasGhostRole()) { diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 431fdd43db..b385a1f11a 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2041,7 +2041,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can Directory.CreateDirectory(vipTagsFiles); Directory.CreateDirectory(sponsorTagsFiles); - if (Blackmailer.CheckBlackmaile(player) && player.IsAlive()) + if (Blackmailer.CheckBlackmaile(player) && player.IsAlive() && !player.IsHost()) { Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); ChatManager.SendPreviousMessagesToAll(); diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index bddba489b6..01d6ae57f4 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -118,8 +118,6 @@ private static void WrapUpPostfix(NetworkedPlayerInfo exiled) { player.GetRoleClass()?.OnPlayerExiled(player, exiled); - player.ResetKillCooldown(); - // Check for remove pet player.RpcRemovePet(); } diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 58717cd67d..7bff9f9678 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -450,7 +450,7 @@ private static bool UpdateValuePrefix(ToggleOption __instance) var item = OptionItem.AllOptions[index]; //Logger.Info($"{item.Name}, {index}", "ToggleOption.UpdateValue.TryGetValue"); item.SetValue(__instance.GetBool() ? 1 : 0); - NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); + NotificationPopperPatch.AddSettingsChangeMessage(index, item, false); return false; } return true; @@ -524,7 +524,7 @@ private static bool UpdateValuePrefix(NumberOption __instance) { floatOptionItem.SetValue(floatOptionItem.Rule.GetNearestIndex(__instance.GetFloat())); } - NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); + NotificationPopperPatch.AddSettingsChangeMessage(index, item, false); return false; } return true; @@ -677,7 +677,7 @@ private static bool UpdateValuePrefix(StringOption __instance) //Logger.Info($"{item.Name}, {index}", "StringOption.UpdateValue.TryAdd"); item.SetValue(__instance.GetInt()); - NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); + NotificationPopperPatch.AddSettingsChangeMessage(index, item, false); if (item is PresetOptionItem || (item is StringOptionItem && item.Name == "GameMode")) { diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs index 3d94ceb7b4..8d66e6a596 100644 --- a/Patches/NotificationPopperPatch.cs +++ b/Patches/NotificationPopperPatch.cs @@ -23,7 +23,7 @@ public static void AddSettingsChangeMessage( { try { - SendRpc(index, playSound); + SendRpc(index, true); var haveParent = key.Parent != null; string str; if (haveParent) From 0bd97d25dd539e68bfb39a53d8a81a5c849e1b63 Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 16:07:10 +0800 Subject: [PATCH 775/778] Fix RPC not send Vector --- Roles/Neutral/Vector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Vector.cs b/Roles/Neutral/Vector.cs index ad286fb678..2b23cc9450 100644 --- a/Roles/Neutral/Vector.cs +++ b/Roles/Neutral/Vector.cs @@ -46,7 +46,7 @@ public override void Add(byte playerId) } private void SendRPC() { - if (_Player.IsNonHostModdedClient()) return; + if (!_Player.IsNonHostModdedClient()) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, _Player.GetClientId()); writer.WriteNetObject(_Player); writer.WritePacked(VectorVentCount[_Player.PlayerId]); From 077d9e89295771ac5ac356034223736e81807f6c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 16:24:36 +0800 Subject: [PATCH 776/778] Fix Rpc in SendGhostRoleInfo --- Roles/Core/AssignManager/GhostRoleAssign.cs | 35 ++++++++++----------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 3a0ed54d5d..51e0ae25d8 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -182,28 +182,25 @@ public static void CreateGAMessage(PlayerControl __instance) var writer = CustomRpcSender.Create("SendGhostRoleInfo", SendOption.None); writer.StartMessage(__instance.GetClientId()); - writer.StartRpc(host.NetId, (byte)RpcCalls.SetName) - .Write(host.Data.NetId) - .Write(Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString("GhostTransformTitle"))) - .EndRpc(); - writer.StartRpc(host.NetId, (byte)RpcCalls.SendChat) - .Write(sb.ToString()) - .EndRpc(); + { + writer.StartRpc(host.NetId, (byte)RpcCalls.SetName) + .Write(host.Data.NetId) + .Write(Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString("GhostTransformTitle"))) + .EndRpc(); + writer.StartRpc(host.NetId, (byte)RpcCalls.SendChat) + .Write(sb.ToString()) + .EndRpc(); + writer.StartRpc(host.NetId, (byte)RpcCalls.SendChat) + .Write(conf.ToString()) + .EndRpc(); + writer.StartRpc(host.NetId, (byte)RpcCalls.SetName) + .Write(host.Data.NetId) + .Write(name) + .EndRpc(); + } writer.EndMessage(); writer.SendMessage(); - var writer2 = CustomRpcSender.Create("SendGhostRoleConfig", SendOption.None); - writer2.StartMessage(__instance.GetClientId()); - writer2.StartRpc(host.NetId, (byte)RpcCalls.SendChat) - .Write(conf.ToString()) - .EndRpc(); - writer2.StartRpc(host.NetId, (byte)RpcCalls.SetName) - .Write(host.Data.NetId) - .Write(name) - .EndRpc(); - writer2.EndMessage(); - writer2.SendMessage(); - // Utils.SendMessage(sb.ToString(), __instance.PlayerId, Utils.ColorString(Utils.GetRoleColor(role), GetString("GhostTransformTitle"))); } From 55c1ae6862162f3f104133e32e776c2afe8e419c Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 16:38:17 +0800 Subject: [PATCH 777/778] Change Beta => Release --- main.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main.cs b/main.cs index 4c473ecb91..e778d3e6f6 100644 --- a/main.cs +++ b/main.cs @@ -42,14 +42,14 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1011.210.030000"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.1.0 Beta 3"; - public const string SupportedVersionAU = "2024.8.13"; + public const string PluginVersion = "2024.1027.210.030000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.1.0"; + public const string SupportedVersionAU = "2024.8.13"; // Also 2024.9.4 and 2024.10.29 /******************* Change one of the three variables to true before making a release. *******************/ public static readonly bool devRelease = false; // Latest: V2.1.0 Alpha 16 Hotfix 1 - public static readonly bool canaryRelease = true; // Latest: V2.1.0 Beta 3 - public static readonly bool fullRelease = false; // Latest: V2.0.3 + public static readonly bool canaryRelease = false; // Latest: V2.1.0 Beta 3 + public static readonly bool fullRelease = true; // Latest: V2.1.0 public static bool hasAccess = true; From 3089e3b7320bf81520a5b5d4bc15963a1d1c693f Mon Sep 17 00:00:00 2001 From: Tommy XL Date: Sun, 27 Oct 2024 16:38:56 +0800 Subject: [PATCH 778/778] PluginVersion --- main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cs b/main.cs index e778d3e6f6..add316adce 100644 --- a/main.cs +++ b/main.cs @@ -42,7 +42,7 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1027.210.030000"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginVersion = "2024.1027.210.9999"; // YEAR.MMDD.VERSION.CANARYDEV public const string PluginDisplayVersion = "2.1.0"; public const string SupportedVersionAU = "2024.8.13"; // Also 2024.9.4 and 2024.10.29