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