From 5136fd7f87c307a517cd0ccc157029fd127457ef Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Fri, 12 Sep 2025 10:54:51 +0200 Subject: [PATCH] Add Clerk (Tax Collector) --- CustomRoles.cs | 1 + GameMode/HotPotato.cs | 2 +- Main.cs | 2 + Modules/CustomRolesHelper.cs | 3 + Modules/FixedUpdateCaller.cs | 7 ++ .../PlayerGameOptionsSender.cs | 1 + Modules/GameState.cs | 3 + Modules/OptionHolder.cs | 9 ++ Modules/Utils.cs | 1 + Patches/ChatCommandPatch.cs | 34 ++++-- Patches/ChatControlPatch.cs | 3 - Patches/CheckGameEndPatch.cs | 2 + Patches/PlayerJoinAndLeftPatch.cs | 2 +- Resources/Lang/en_US.json | 13 ++- Roles/Coven/Banshee.cs | 7 +- Roles/Neutral/Clerk.cs | 105 ++++++++++++++++++ 16 files changed, 177 insertions(+), 18 deletions(-) create mode 100644 Roles/Neutral/Clerk.cs diff --git a/CustomRoles.cs b/CustomRoles.cs index 408d0cb71..1f22880c5 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -289,6 +289,7 @@ public enum CustomRoles Bubble, Chemist, Cherokious, + Clerk, Collector, Curser, Deathknight, diff --git a/GameMode/HotPotato.cs b/GameMode/HotPotato.cs index c7afbb5a3..47a69877a 100644 --- a/GameMode/HotPotato.cs +++ b/GameMode/HotPotato.cs @@ -48,7 +48,7 @@ internal static class HotPotato .SetValueFormat(OptionFormat.Multiplier) .SetColor(new Color32(232, 205, 70, byte.MaxValue)); - HolderCanPassViaKillButton = new BooleanOptionItem(69_213_003, "HotPotato_HolderCanPassViaKillButton", false, TabGroup.GameSettings) + HolderCanPassViaKillButton = new BooleanOptionItem(69_213_003, "HotPotato_HolderCanPassViaKillButton", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.HotPotato) .SetColor(new Color32(232, 205, 70, byte.MaxValue)) .SetHeader(true); diff --git a/Main.cs b/Main.cs index 7503aa4f6..453d46e9b 100644 --- a/Main.cs +++ b/Main.cs @@ -573,6 +573,7 @@ public class Main : BasePlugin { CustomRoles.Seamstress, "#BFE44C" }, { CustomRoles.Spirit, "#26B652" }, { CustomRoles.Starspawn, "#4CE4B4" }, + { CustomRoles.Clerk, "#26B6A9" }, { CustomRoles.RoomRusher, "#ffab1b" }, { CustomRoles.SchrodingersCat, "#616161" }, { CustomRoles.Shifter, "#777777" }, @@ -1117,6 +1118,7 @@ public enum AdditionalWinners Seamstress = CustomRoles.Seamstress, Spirit = CustomRoles.Spirit, Starspawn = CustomRoles.Starspawn, + Clerk = CustomRoles.Clerk, RoomRusher = CustomRoles.RoomRusher, Impartial = CustomRoles.Impartial, Gaslighter = CustomRoles.Gaslighter, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 9b6efb95d..4a763db6f 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -516,6 +516,7 @@ internal static class CustomRolesHelper CustomRoles.Postman => RoleTypes.Impostor, CustomRoles.Dealer => RoleTypes.Impostor, CustomRoles.Auditor => RoleTypes.Impostor, + CustomRoles.Clerk => RoleTypes.Impostor, CustomRoles.Seamstress => RoleTypes.Shapeshifter, CustomRoles.Spirit => RoleTypes.Shapeshifter, CustomRoles.Starspawn => RoleTypes.Impostor, @@ -889,6 +890,7 @@ internal static class CustomRolesHelper CustomRoles.Godfather when Options.GodfatherCancelVote.GetBool() => true, CustomRoles.Socialite when Socialite.CancelVote.GetBool() => true, CustomRoles.Negotiator when Negotiator.CancelVote.GetBool() => true, + CustomRoles.Clerk when Clerk.CancelVote.GetBool() => true, CustomRoles.President => true, @@ -1525,6 +1527,7 @@ internal static class CustomRolesHelper CustomRoles.Innocent => RoleOptionType.Neutral_Evil, CustomRoles.Curser => RoleOptionType.Neutral_Pariah, CustomRoles.Auditor => RoleOptionType.Neutral_Pariah, + CustomRoles.Clerk => RoleOptionType.Neutral_Pariah, CustomRoles.Magistrate => RoleOptionType.Neutral_Pariah, CustomRoles.Seamstress => RoleOptionType.Neutral_Pariah, CustomRoles.Spirit => RoleOptionType.Neutral_Pariah, diff --git a/Modules/FixedUpdateCaller.cs b/Modules/FixedUpdateCaller.cs index 8baa9364e..4bf8c6e4a 100644 --- a/Modules/FixedUpdateCaller.cs +++ b/Modules/FixedUpdateCaller.cs @@ -15,6 +15,7 @@ public static class FixedUpdateCaller private static int NonLowLoadPlayerIndex; private static long LastFileLoadTS; + private static long LastAutoMessageSendTS; // ReSharper disable once UnusedMember.Global public static void Postfix() @@ -48,6 +49,12 @@ public static class FixedUpdateCaller LastFileLoadTS = now; Options.LoadUserData(); } + + if (Options.EnableAutoMessage.GetBool() && now - LastAutoMessageSendTS > Options.AutoMessageSendInterval.GetInt()) + { + LastAutoMessageSendTS = now; + TemplateManager.SendTemplate("Notification", sendOption: Hazel.SendOption.None); + } } HudManager hudManager = FastDestroyableSingleton.Instance; diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 9cfec7608..fe9f9585f 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -320,6 +320,7 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS Wizard.OnAnyoneApplyGameOptions(opt, player.PlayerId); Curser.OnAnyoneApplyGameOptions(opt, player.PlayerId); Auditor.OnAnyoneApplyGameOptions(opt, player.PlayerId); + Clerk.OnAnyoneApplyGameOptions(opt, player.PlayerId); if (Sprayer.LowerVisionList.Contains(player.PlayerId)) { diff --git a/Modules/GameState.cs b/Modules/GameState.cs index ef067fa93..3b9807948 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -68,6 +68,7 @@ public class PlayerState(byte playerId) WipedOut, OutOfOxygen, Retribution, + Taxes, // Natural Disasters Meteor, @@ -456,6 +457,8 @@ public class TaskState if (AmongUsClient.Instance.AmHost) { + Clerk.DidTaskThisRound.Add(player.PlayerId); + bool alive = player.IsAlive(); if (alive && Options.CurrentGameMode == CustomGameMode.Speedrun) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index db92483c3..dc8f17863 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -729,6 +729,8 @@ public static class Options public static OptionItem AllCrewRolesHaveVanillaColor; public static OptionItem MessageRpcSizeLimit; public static OptionItem KickSlowJoiningPlayers; + public static OptionItem EnableAutoMessage; + public static OptionItem AutoMessageSendInterval; public static OptionItem DraftMaxRolesPerPlayer; public static OptionItem LargerRoleTextSize; public static OptionItem ShowTaskCountWhenAlive; @@ -1738,6 +1740,13 @@ public static class Options KickSlowJoiningPlayers = new BooleanOptionItem(19428, "KickSlowJoiningPlayers", false, TabGroup.SystemSettings) .SetHeader(true); + + EnableAutoMessage = new BooleanOptionItem(19429, "EnableAutoMessage", false, TabGroup.SystemSettings) + .SetHeader(true); + + AutoMessageSendInterval = new IntegerOptionItem(19430, "AutoMessageSendInterval", new(10, 300, 5), 60, TabGroup.SystemSettings) + .SetParent(EnableAutoMessage) + .SetValueFormat(OptionFormat.Seconds); RoleAssigningAlgorithm = new StringOptionItem(19409, "RoleAssigningAlgorithm", RoleAssigningAlgorithms, 4, TabGroup.SystemSettings, true) .SetHeader(true) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 0d69d6951..e8dc17197 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -871,6 +871,7 @@ public static class Utils case CustomRoles.Postman: case CustomRoles.Dealer: case CustomRoles.Auditor: + case CustomRoles.Clerk: case CustomRoles.Magistrate: case CustomRoles.Seamstress: case CustomRoles.Spirit: diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 901870bcf..7d3706859 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -164,7 +165,7 @@ internal static class ChatCommands new(["target", "цель", "腹语者标记", "alvo"], "{id}", GetString("CommandDescription.Target"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, TargetCommand, true, true, [GetString("CommandArgs.Target.Id")]), new(["chat", "сообщение", "腹语者发送消息"], "{message}", GetString("CommandDescription.Chat"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ChatCommand, true, true, [GetString("CommandArgs.Chat.Message")]), new(["check", "проверить", "检查", "veificar"], "{id} {role}", GetString("CommandDescription.Check"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, CheckCommand, true, true, [GetString("CommandArgs.Check.Id"), GetString("CommandArgs.Check.Role")]), - new(["ban", "kick", "бан", "кик", "забанить", "кикнуть", "封禁", "踢出", "banir", "expulsar"], "{id}", GetString("CommandDescription.Ban"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, BanKickCommand, true, false, [GetString("CommandArgs.Ban.Id")]), + new(["ban", "kick", "бан", "кик", "забанить", "кикнуть", "封禁", "踢出", "banir", "expulsar"], "{id} [reason]", GetString("CommandDescription.Ban"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, BanKickCommand, true, false, [GetString("CommandArgs.Ban.Id"), GetString("CommandArgs.Ban.Reason")]), new(["exe", "выкинуть", "驱逐", "executar"], "{id}", GetString("CommandDescription.Exe"), Command.UsageLevels.HostOrAdmin, Command.UsageTimes.Always, ExeCommand, true, false, [GetString("CommandArgs.Exe.Id")]), new(["kill", "убить", "击杀", "matar"], "{id}", GetString("CommandDescription.Kill"), Command.UsageLevels.Host, Command.UsageTimes.Always, KillCommand, true, false, [GetString("CommandArgs.Kill.Id")]), new(["colour", "color", "цвет", "更改颜色", "cor"], "{color}", GetString("CommandDescription.Colour"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ColorCommand, true, false, [GetString("CommandArgs.Colour.Color")]), @@ -1304,6 +1305,8 @@ internal static class ChatCommands return; } + DraftResult = []; + byte[] allPlayerIds = Main.AllPlayerControls.Select(x => x.PlayerId).ToArray(); List allRoles = Enum.GetValues().Where(x => x < CustomRoles.NotAssigned && x.IsEnable() && !x.IsForOtherGameMode() && !CustomHnS.AllHnSRoles.Contains(x) && !x.IsVanilla() && x is not CustomRoles.GM and not CustomRoles.Konan).ToList(); @@ -2088,13 +2091,30 @@ internal static class ChatCommands return; } - // Kick the specified Player - AmongUsClient.Instance.KickPlayer(kickedPlayer.OwnerId, args[0] == "/ban"); - string kickedPlayerName = kickedPlayer.GetRealName(); - var textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; - if (GameStates.IsInGame) textToSend += $"{GetString("KickCommandKickedRole")} {kickedPlayer.GetCustomRole().ToColoredString()}"; + try + { + string kickedPlayerName = kickedPlayer.GetRealName(); + var textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; + if (GameStates.IsInGame) textToSend += $"{GetString("KickCommandKickedRole")} {kickedPlayer.GetCustomRole().ToColoredString()}"; + if (args.Length >= 3) textToSend += $"\n{GetString("KickCommandKickedReason")} {string.Join(' ', args[2..])}"; - Utils.SendMessage(textToSend, sendOption: GameStates.IsInGame ? SendOption.Reliable : SendOption.None); + Utils.SendMessage(textToSend, sendOption: GameStates.IsInGame ? SendOption.Reliable : SendOption.None); + + string modLogFilePath = $"{Main.DataPath}/EHR_DATA/ModLogs/{DateTime.Now:yyyy-MM-dd}.txt"; + + if (!File.Exists(modLogFilePath)) + { + string directoryName = Path.GetDirectoryName(modLogFilePath); + if (!string.IsNullOrEmpty(directoryName)) Directory.CreateDirectory(directoryName); + File.WriteAllText(modLogFilePath, "=== Moderation Log ===\n"); + } + + string logEntry = $"[{DateTime.Now:HH:mm:ss}] {player.GetRealName()} {(args[0] == "/ban" ? "banned" : "kicked")} {kickedPlayerName} [{kickedPlayer.FriendCode}|{kickedPlayer.GetClient().GetHashedPuid()}] for {(args.Length >= 3 ? string.Join(' ', args[2..]) : "[no reason provided]")}\n"; + File.AppendAllText(modLogFilePath, logEntry); + } + catch (Exception e) { Utils.ThrowException(e); } + + AmongUsClient.Instance.KickPlayer(kickedPlayer.OwnerId, args[0] == "/ban"); } private static void CheckCommand(PlayerControl player, string text, string[] args) diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs index 5c469d713..3493e59f8 100644 --- a/Patches/ChatControlPatch.cs +++ b/Patches/ChatControlPatch.cs @@ -207,9 +207,6 @@ public static class ChatManager case CustomGameMode.FFA when GameStates.InGame && !message.StartsWith('/'): FreeForAll.UpdateLastChatMessage(player.GetRealName(), message); break; - case CustomGameMode.Standard when GameStates.InGame && operate != 1 && Banshee.On: - Banshee.OnReceiveChat(); - break; } } diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 6ae72f7bf..5f9b52913 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -236,6 +236,7 @@ internal static class GameEndChecker case CustomRoles.NoteKiller when !NoteKiller.CountsAsNeutralKiller && NoteKiller.Kills >= NoteKiller.NumKillsNeededToWin: case CustomRoles.NecroGuesser when (roleBase as NecroGuesser).GuessedPlayers >= NecroGuesser.NumGuessesToWin.GetInt(): case CustomRoles.RoomRusher when (roleBase as RoomRusher).Won: + case CustomRoles.Clerk when WinnerTeam != CustomWinner.Crewmate || reason == GameOverReason.CrewmatesByTask: case CustomRoles.Auditor when WinnerTeam != CustomWinner.Crewmate: case CustomRoles.Magistrate when WinnerTeam != CustomWinner.Crewmate: case CustomRoles.Seamstress when WinnerTeam != CustomWinner.Crewmate: @@ -461,6 +462,7 @@ internal static class GameEndChecker case CustomRoles.NecroGuesser when ((NecroGuesser)state.Role).GuessedPlayers >= NecroGuesser.NumGuessesToWin.GetInt(): case CustomRoles.RoomRusher when ((RoomRusher)state.Role).Won: case CustomRoles.Auditor: + case CustomRoles.Clerk: case CustomRoles.Magistrate: case CustomRoles.Seamstress: case CustomRoles.Spirit: diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 309397db8..ee3c73f75 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -149,7 +149,7 @@ internal static class OnGameJoinedPatch float timer; if (nextGM != CustomGameMode.All) timer = 0f; - else if (Options.AutoGMPollCommandAfterJoin.GetBool()) timer = Options.AutoGMPollCommandCooldown.GetInt(); + else if (Options.AutoGMPollCommandAfterJoin.GetBool()) timer = Options.AutoGMPollCommandCooldown.GetInt() - 10; else if (Main.AutoStart.Value) timer = (Options.MinWaitAutoStart.GetFloat() * 60) - 65; else timer = 30f; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index a3c8ee741..2da947217 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1884,6 +1884,7 @@ "KickCommandKickMod": "You are not permitted to kick other moderators.", "KickCommandKicked": "was kicked from the game.", "KickCommandKickedRole": "Their role was", + "KickCommandKickedReason": "Reason:", "ModeratorTag": "<#8000ff>Moderator", "VIPTag": "<#ffff00>VIP", "AdminTag": "<#ff0000>Admin", @@ -1958,6 +1959,7 @@ "DeathReason.Mauled": "Mauled", "DeathReason.WipedOut": "Wiped Out", "DeathReason.Retribution": "Retribution", + "DeathReason.Taxes": "Taxes", "ND_DRLaughMessage.Meteor": "Meteor.... I hardly knew her!", "ND_DRLaughMessage.Lava": "Lava is not the best place to swim, huh?", "ND_DRLaughMessage.Tornado": "Out for a spin? I hope this tornado didn't make you dizzy!", @@ -2514,6 +2516,7 @@ "CommandArgs.Check.Id": "The player's ID to check", "CommandArgs.Check.Role": "The role or add-on to check for", "CommandArgs.Ban.Id": "The player's ID to ban or kick", + "CommandArgs.Ban.Reason": "The reason for the punishment", "CommandArgs.Exe.Id": "The player's ID to execute", "CommandArgs.Kill.Id": "The player's ID to kill", "CommandArgs.Colour.Color": "The color to set (ex. red, blue, yellow)", @@ -4470,12 +4473,14 @@ "GuessBackstabber": "You cannot guess a revealed Backstabber.", "HideResults": "Hide Results", "ShowResults": "Show Results", + "EnableAutoMessage": "Enable Auto-Message (sends the template with the 'Notification' tag on a given interval in lobby)", + "AutoMessageSendInterval": "The Auto-Message Is Sent Every", "Wiper": "Wiper", "WiperInfo": "Wipe Out Rooms", "WiperInfoLong": "(Impostors):\\nAs the Wiper, you can wipe out a room by using pet/vanish. This will kill everyone in that room. (Even other impostors, depending on the settings.)\\nYou cannot kill normally.\\nYou cannot use your ability during sabotages.", "Wiper.KillOtherImpostors": "Wiping Out a Room Kills Other <#ff1919>Impostors", "Wiper.CannotUseAbilityDuringSabotage": "You cannot use your ability during sabotages", - "Wiper.MustBeInRoomToUseAbility": "You must be in a generic room to use your ability", + "Wiper.MustBeInRoomToUseAbility": "You must be in a room to use your ability\\nNot in the spawn area", "Wiper.CurrentRoom": "<#00ffa5>Current Room: {0}", "Curser": "Curser", "CurserInfo": "Make Others' Life Worse", @@ -9251,6 +9256,12 @@ "SleepInfo": "You Sleep During Lights Out", "SleepInfoLong": "(Add-ons):\\nYou sleep during lights out. You can't see anything, and you cannot move. You can only wake up when the lights are back on. If someone with the Glow add-on comes near you, you fully wake up and lose this add-on.", "Prefix.Sleep": "Sleeping", + "Clerk": "Clerk", + "ClerkInfo": "Check Players' Taxes", + "ClerkInfoLong": "(Neutrals):\\nAs the Clerk, you may vote for someone in a meeting to check their taxes. If they've completed a task in the last round (or finished them all, or they have none), they get a tax refund (speed & vision boost for the next round). Otherwise, they die.", + "Clerk.CanUseAbilityOnFirstMeeting": "Can Use Ability on First Meeting", + "Clerk.DidTax": "{0} did their taxes. They got a refund!", + "Clerk.Killed": "{0} was cought not doing their taxes (tasks). They were killed!", "Decryptor": "Decryptor", "DecryptorInfo": "Get Letters From Everyone's Roles", "DecryptorInfoLong": "(Crewmates):\\nAs the Decryptor, each X tasks you complete (X is set by the host), you get to know one more letter from everyone's roles. You can see these characters below players' names. The letters are not in order and each letter only appears once for every occurance in the role name. Settings determine whether you can guess.", diff --git a/Roles/Coven/Banshee.cs b/Roles/Coven/Banshee.cs index 0a9843797..1f2c401ab 100644 --- a/Roles/Coven/Banshee.cs +++ b/Roles/Coven/Banshee.cs @@ -111,11 +111,8 @@ public class Banshee : Coven } } - public static void OnReceiveChat() + public override void OnReportDeadBody() { - PlayerControl[] screechedPlayers = Instances.SelectMany(x => x.ScreechedPlayers).Distinct().ToValidPlayers().ToArray(); - if (screechedPlayers.Length == 0) return; - - LateTask.New(() => ChatManager.ClearChat(screechedPlayers), Utils.CalculatePingDelay()); + LateTask.New(() => Instances.SelectMany(x => x.ScreechedPlayers).Distinct().ToValidPlayers().Do(x => x.SetChatVisible(false)), 4f, "Set Chat Hidden For Banshee Victims"); } } \ No newline at end of file diff --git a/Roles/Neutral/Clerk.cs b/Roles/Neutral/Clerk.cs new file mode 100644 index 000000000..4b64c6bfa --- /dev/null +++ b/Roles/Neutral/Clerk.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using AmongUs.GameOptions; +using EHR.Crewmate; +using Hazel; + +namespace EHR.Neutral; + +public class Clerk : RoleBase +{ + public static bool On; + + public override bool IsEnable => On; + + private static OptionItem AbilityUseLimit; + private static OptionItem CanUseAbilityOnFirstMeeting; + private static OptionItem IncreasedSpeed; + private static OptionItem IncreasedVision; + public static OptionItem CancelVote; + + public static HashSet DidTaskThisRound = []; + public static HashSet HasBoost = []; + + public override void SetupCustomOption() + { + StartSetup(654800) + .AutoSetupOption(ref AbilityUseLimit, 5, new IntegerValueRule(1, 30, 1), OptionFormat.Times) + .AutoSetupOption(ref CanUseAbilityOnFirstMeeting, false) + .AutoSetupOption(ref IncreasedSpeed, 1.8f, new FloatValueRule(0.1f, 3f, 0.1f), OptionFormat.Multiplier) + .AutoSetupOption(ref IncreasedVision, 1f, new FloatValueRule(0.1f, 1.5f, 0.1f), OptionFormat.Multiplier) + .CreateVoteCancellingUseSetting(ref CancelVote); + } + + public override void Init() + { + On = false; + DidTaskThisRound = []; + HasBoost = []; + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override bool OnVote(PlayerControl voter, PlayerControl target) + { + if (Starspawn.IsDayBreak) return false; + if (Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; + if (MeetingStates.FirstMeeting && !CanUseAbilityOnFirstMeeting.GetBool()) return false; + if (voter.GetAbilityUseLimit() < 1) return false; + if (target.PlayerId == voter.PlayerId) return false; + + voter.RpcRemoveAbilityUse(); + + TaskState ts = target.GetTaskState(); + + if (DidTaskThisRound.Contains(target.PlayerId) || !ts.HasTasks || ts.IsTaskFinished) + { + HasBoost.Add(target.PlayerId); + Utils.SendMessage(string.Format(Translator.GetString("Clerk.DidTax"), target.PlayerId.ColoredPlayerName()), voter.PlayerId, CustomRoles.Clerk.ToColoredString()); + } + else if (!target.Is(CustomRoles.Pestilence)) + { + target.SetRealKiller(voter); + PlayerState state = Main.PlayerStates[target.PlayerId]; + state.deathReason = PlayerState.DeathReason.Taxes; + state.SetDead(); + Medic.IsDead(target); + target.RpcExileV2(); + Utils.AfterPlayerDeathTasks(target, true); + Utils.SendMessage(string.Format(Translator.GetString("Clerk.Killed"), target.PlayerId.ColoredPlayerName()), title: CustomRoles.Clerk.ToColoredString()); + } + + Main.DontCancelVoteList.Add(voter.PlayerId); + return true; + } + + public override void OnMeetingShapeshift(PlayerControl shapeshifter, PlayerControl target) + { + OnVote(shapeshifter, target); + } + + public override void AfterMeetingTasks() + { + DidTaskThisRound = []; + } + + public override void OnReportDeadBody() + { + HasBoost.Do(x => Main.AllPlayerSpeed[x] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod)); + HasBoost = []; + } + + public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte id) + { + if (HasBoost.Contains(id)) + { + Main.AllPlayerSpeed[id] = IncreasedSpeed.GetFloat(); + float increasedVision = IncreasedVision.GetFloat(); + opt.SetFloat(FloatOptionNames.CrewLightMod, increasedVision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, increasedVision); + } + } +} \ No newline at end of file