diff --git a/CustomRoles.cs b/CustomRoles.cs index ff9289d64..be2c15ab0 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -57,6 +57,7 @@ public enum CustomRoles EvilGuesser, EvilTracker, Exclusionary, + Exorcist, Fabricator, Fakeshifter, Fireworker, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 847fd7871..d64515fdd 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -165,6 +165,7 @@ internal static class CustomRolesHelper CustomRoles.Venerer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Kidnapper => CustomRoles.Shapeshifter, CustomRoles.Frightener => CustomRoles.Shapeshifter, + CustomRoles.Exorcist => CustomRoles.Impostor, CustomRoles.Obstructer => CustomRoles.Impostor, CustomRoles.Ambusher => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Stasis => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, @@ -765,6 +766,7 @@ internal static class CustomRolesHelper CustomRoles.Psychopath or CustomRoles.Ambusher or CustomRoles.Kidnapper or + CustomRoles.Exorcist or CustomRoles.Frightener or CustomRoles.Obstructer or CustomRoles.Exclusionary or @@ -1298,6 +1300,7 @@ internal static class CustomRolesHelper CustomRoles.Consigliere => RoleOptionType.Impostor_Miscellaneous, CustomRoles.CursedWolf => RoleOptionType.Impostor_Miscellaneous, CustomRoles.EvilTracker => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.Exorcist => RoleOptionType.Impostor_Miscellaneous, CustomRoles.Frightener => RoleOptionType.Impostor_Miscellaneous, CustomRoles.Gambler => RoleOptionType.Impostor_Miscellaneous, CustomRoles.Generator => RoleOptionType.Impostor_Miscellaneous, diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index c7b18d5e8..ed2d60a26 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -236,6 +236,7 @@ internal static class ChatCommands new("NeutralInfo", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, NeutralInfoCommand, true, false), new("PlayerInfo", "[id]", Command.UsageLevels.Everyone, Command.UsageTimes.Always, PlayerInfoCommand, true, false, [GetString("CommandArgs.PlayerInfo.Id")]), new("TimeLimit", "", Command.UsageLevels.Everyone, Command.UsageTimes.InGame, TimeLimitCommand, true, false), + new("Exo", "", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ExoCommand, true, true), new("ConfirmAuth", "{uuid}", Command.UsageLevels.Everyone, Command.UsageTimes.Always, ConfirmAuthCommand, true, false, [GetString("CommandArgs.ConfirmAuth.UUID")]), @@ -385,6 +386,9 @@ internal static class ChatCommands if (GameStates.InGame && (Silencer.ForSilencer.Contains(PlayerControl.LocalPlayer.PlayerId) || (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].Role is Dad { IsEnable: true } dad && dad.UsingAbilities.Contains(Dad.Ability.GoForMilk))) && PlayerControl.LocalPlayer.IsAlive()) goto Canceled; + if (GameStates.IsMeeting && Exorcist.AbilityEndTS > Utils.TimeStamp) + LateTask.New(() => PlayerControl.LocalPlayer.Suicide(), 0.1f); + CheckAnagramGuess(PlayerControl.LocalPlayer.PlayerId, text); if (ChatHistory.Count == 0 || ChatHistory[^1] != text) @@ -521,6 +525,27 @@ internal static class ChatCommands // --------------------------------------------------------------------------------------------------------------------------------------------- + private static void ExoCommand(PlayerControl player, string text, string[] args) + { + if (Starspawn.IsDayBreak) return; + + if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.MainRole != CustomRoles.Exorcist || player.GetAbilityUseLimit() < 1) return; + + int duration = Exorcist.AbilityDuration.GetInt(); + Exorcist.AbilityEndTS = Utils.TimeStamp + duration; + player.RpcRemoveAbilityUse(); + + Utils.SendMessage(string.Format(GetString("Exorcist.AbilityUsedMsg"), duration), title: CustomRoles.Exorcist.ToColoredString()); + + LateTask.New(() => + { + if (!GameStates.IsMeeting) return; + Utils.SendMessage(GetString("Exorcist.AbilityEnded"), title: CustomRoles.Exorcist.ToColoredString()); + }, duration); + + MeetingManager.SendCommandUsedMessage(args[0]); + } + private static void TimeLimitCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage("\n", player.PlayerId, Options.EnableGameTimeLimit.GetBool() ? $"{Options.GameTimeLimit.GetInt() - Main.GameTimer.Elapsed.TotalSeconds:N0}s {GetString("RemainingText.Suffix")}" : "∞"); @@ -3287,6 +3312,9 @@ internal static class ChatCommands return; } + if (GameStates.IsMeeting && Exorcist.AbilityEndTS > now) + player.Suicide(realKiller: Main.EnumeratePlayerControls().FirstOrDefault(x => x.Is(CustomRoles.Exorcist))); + if (text.StartsWith("\n")) text = text[1..]; switch (Options.CurrentGameMode) diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc index 8ef27be60..a5373f952 100644 --- a/Resources/Lang/en_US.jsonc +++ b/Resources/Lang/en_US.jsonc @@ -1862,6 +1862,11 @@ "Exclusionary.CooldownAfterExclusion": "Cooldown After Exclusion", "Exclusionary.ExclusionDelay": "Exclusion Delay", "Exclusionary.ExclusionDuration": "Exclusion Duration", + "Exorcist": "Exorcist", + "ExorcistInfo": "Kill Mindless Chatters", + "ExorcistInfoLong": "(Impostors):\\nAs the Exorcist, you can use /exo in a meeting to activate your ability (everyone is notified about it). Anyone who sends a chat message during your ability dies instantly.", + "Exorcist.AbilityUsedMsg": "<#ff5555>Anyone who sends a message in the next <#000000>{0} seconds will die!", + "Exorcist.AbilityEnded": "<#00bb00>Messages can be sent again!", "Fabricator": "Fabricator", "FabricatorInfo": "Change Death Reasons", "FabricatorInfoLong": "(Impostors):\\nAs the Fabricator, you can use /cmd fabricate {deathreason} to set the death reason for your next kill. Replace '{deathreason}' with one of the available death reasons in the mod.", @@ -9130,6 +9135,7 @@ "CommandDescription.EnableAllRoles": "Enables all roles and add-ons", "CommandDescription.End": "Ends the game", "CommandDescription.Exe": "Executes a player", + "CommandDescription.Exo": "Activate your ability as the Exorcist", "CommandDescription.Fabricate": "Set a fake death reason for your next kill as the Fabricator", "CommandDescription.Fix": "Fixes the black screen bug for a specific player.", "CommandDescription.Forge": "As the Forger, forge a fake role for a player.", @@ -9240,6 +9246,7 @@ "CommandForms.EnableAllRoles": "enableallroles", "CommandForms.End": "end", "CommandForms.Exe": "exe", + "CommandForms.Exo": "exo", "CommandForms.Fabricate": "fabricate", "CommandForms.Fix": "fix,blackscreenfix,fixblackscreen", "CommandForms.Forge": "forge", diff --git a/Roles/Standard/Impostor/Miscellaneous/Exorcist.cs b/Roles/Standard/Impostor/Miscellaneous/Exorcist.cs new file mode 100644 index 000000000..a5bded82c --- /dev/null +++ b/Roles/Standard/Impostor/Miscellaneous/Exorcist.cs @@ -0,0 +1,34 @@ +namespace EHR.Roles; + +public class Exorcist : RoleBase +{ + public static bool On; + + public static OptionItem AbilityDuration; + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachKill; + + public static long AbilityEndTS; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + StartSetup(658500) + .AutoSetupOption(ref AbilityDuration, 15, new IntegerValueRule(1, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 1f, new FloatValueRule(0f, 5f, 0.25f), OptionFormat.Times); + } + + public override void Init() + { + On = false; + AbilityEndTS = 0; + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat()); + } +} \ No newline at end of file