diff --git a/CustomRoles.cs b/CustomRoles.cs
index 8c072d294..a46c3bb6d 100644
--- a/CustomRoles.cs
+++ b/CustomRoles.cs
@@ -306,6 +306,7 @@ public enum CustomRoles
Bargainer,
Beehive,
Berserker,
+ Blockade,
BloodKnight,
Bubble,
Chemist,
diff --git a/Main.cs b/Main.cs
index 634d11895..b4f3608d2 100644
--- a/Main.cs
+++ b/Main.cs
@@ -571,6 +571,7 @@ public class Main : BasePlugin
{ CustomRoles.Thanos, "#F9D401" },
{ CustomRoles.Berserker, "#50538F" },
{ CustomRoles.SerialKiller, "#233fcc" },
+ { CustomRoles.Blockade, "#d6fa34" },
{ CustomRoles.Quarry, "#c1fb2b" },
{ CustomRoles.Accumulator, "#2bfbae" },
{ CustomRoles.Spider, "#C9E44C" },
@@ -1090,6 +1091,7 @@ public enum CustomWinner
Necromancer = CustomRoles.Necromancer,
Wraith = CustomRoles.Wraith,
SerialKiller = CustomRoles.SerialKiller,
+ Blockade = CustomRoles.Blockade,
Quarry = CustomRoles.Quarry,
Accumulator = CustomRoles.Accumulator,
Spider = CustomRoles.Spider,
diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index add69c826..9ac5f322e 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -529,6 +529,7 @@ internal static class CustomRolesHelper
CustomRoles.Duality => RoleTypes.Impostor,
CustomRoles.Berserker => RoleTypes.Impostor,
CustomRoles.SerialKiller => RoleTypes.Impostor,
+ CustomRoles.Blockade => RoleTypes.Shapeshifter,
CustomRoles.Quarry => RoleTypes.Shapeshifter,
CustomRoles.SoulCollector => RoleTypes.Impostor,
CustomRoles.Spider => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter,
@@ -668,6 +669,7 @@ internal static class CustomRolesHelper
CustomRoles.Parasite or
CustomRoles.Berserker or
CustomRoles.SerialKiller or
+ CustomRoles.Blockade or
CustomRoles.Quarry or
CustomRoles.Accumulator or
CustomRoles.Spider or
@@ -1994,6 +1996,7 @@ public enum CountTypes
HexMaster,
Wraith,
SerialKiller,
+ Blockade,
Quarry,
Accumulator,
Spider,
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index b8f027856..39e324038 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -817,6 +817,7 @@ public static class Utils
case CustomRoles.Eclipse:
case CustomRoles.Pyromaniac:
case CustomRoles.SerialKiller:
+ case CustomRoles.Blockade:
case CustomRoles.Quarry:
case CustomRoles.Spider:
case CustomRoles.SoulCollector:
@@ -3676,6 +3677,7 @@ public static class Utils
{
CustomRoles.Farmer => 2,
CustomRoles.Thanos => 5,
+ CustomRoles.Blockade => 5,
CustomRoles.Mole => Mole.CD.GetInt(),
CustomRoles.PortalMaker => PortalMaker.AbilityCooldown.GetInt(),
CustomRoles.Telecommunication => Telecommunication.VentCooldown.GetInt(),
diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc
index d440320ac..52da4563d 100644
--- a/Resources/Lang/en_US.jsonc
+++ b/Resources/Lang/en_US.jsonc
@@ -2474,6 +2474,10 @@
"BerserkerInfo": "Go Berserk If Others Kill More Than You",
"BerserkerInfoLong": "(Neutrals):\\nAs the Berserker, you get stronger each time someone other than you kills:\\n1: Long kill range\\n2: Lower kill cooldown\\n3: Impostor vision\\n4: Immunity\\n5: Suicide\\nIf you kill, your strength goes down a level. Kill everyone to win!",
"Berserker.LowerKillCooldown": "Lower Kill Cooldown",
+ "Blockade": "Blockade",
+ "BlockadeInfo": "Place Blockades",
+ "BlockadeInfoLong": "(Neutrals):\\nAs the Blockade, you can vanish/pet/shapeshift to place blockades. No one other than you can go through them. All blockades disappear when a meeting starts.",
+ "Blockade.BlockadeRadius": "Blockade Radius",
"BloodKnight": "Blood Knight",
"BloodKnightInfo": "Killing gives you a temporary shield",
"BloodKnightInfoLong": "(Neutrals):\\nBlood Knights win if they're the last killing role alive and crewmates are equal or less than them. They gain temporary shields after every kill, making them immortal for a few seconds.",
diff --git a/Roles/Standard/Neutral/Killing/Blockade.cs b/Roles/Standard/Neutral/Killing/Blockade.cs
new file mode 100644
index 000000000..b90c932cb
--- /dev/null
+++ b/Roles/Standard/Neutral/Killing/Blockade.cs
@@ -0,0 +1,116 @@
+using System.Collections.Generic;
+using AmongUs.GameOptions;
+
+namespace EHR.Roles;
+
+public class Blockade : RoleBase
+{
+ public static bool On;
+
+ private static OptionItem BlockadeRadius;
+ private static OptionItem KillCooldown;
+ private static OptionItem CanVent;
+ private static OptionItem ImpostorVision;
+ private static OptionItem AbilityUseLimit;
+ private static OptionItem AbilityUseGainWithEachKill;
+
+ public override bool IsEnable => On;
+
+ private List Blockades;
+ private Dictionary LastPosition;
+
+ public override void SetupCustomOption()
+ {
+ StartSetup(658300)
+ .AutoSetupOption(ref BlockadeRadius, 1.5f, new FloatValueRule(0.1f, 10f, 0.1f), OptionFormat.Multiplier)
+ .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
+ .AutoSetupOption(ref CanVent, true)
+ .AutoSetupOption(ref ImpostorVision, true)
+ .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times)
+ .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times);
+ }
+
+ public override void Init()
+ {
+ On = false;
+ }
+
+ public override void Add(byte playerId)
+ {
+ On = true;
+ Blockades = [];
+ LastPosition = [];
+ playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
+ }
+
+ public override void SetKillCooldown(byte id)
+ {
+ Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
+ }
+
+ public override void ApplyGameOptions(IGameOptions opt, byte id)
+ {
+ opt.SetVision(ImpostorVision.GetBool());
+
+ if (Options.UsePhantomBasis.GetBool())
+ {
+ AURoleOptions.PhantomCooldown = 5f;
+ AURoleOptions.PhantomDuration = 1f;
+ }
+ else if (!Options.UsePets.GetBool())
+ {
+ AURoleOptions.ShapeshifterCooldown = 5f;
+ AURoleOptions.ShapeshifterDuration = 1f;
+ }
+ }
+
+ public override bool CanUseImpostorVentButton(PlayerControl pc)
+ {
+ return CanVent.GetBool();
+ }
+
+ public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
+ {
+ if (shapeshifting) PlaceBlockade(shapeshifter);
+ return false;
+ }
+
+ public override bool OnVanish(PlayerControl pc)
+ {
+ PlaceBlockade(pc);
+ return false;
+ }
+
+ public override void OnPet(PlayerControl pc)
+ {
+ PlaceBlockade(pc);
+ }
+
+ void PlaceBlockade(PlayerControl pc)
+ {
+ if (pc.GetAbilityUseLimit() < 1f) return;
+ pc.RpcRemoveAbilityUse();
+
+ Blockades.Add(pc.Pos());
+ pc.Notify(Translator.GetString("MarkDone"));
+ }
+
+ public override void OnCheckPlayerPosition(PlayerControl pc)
+ {
+ if (pc.Is(CustomRoles.Blockade)) return;
+
+ Vector2 pos = pc.Pos();
+ float radius = BlockadeRadius.GetFloat();
+
+ if (Blockades.Exists(x => FastVector2.DistanceWithinRange(pos, x, radius)))
+ pc.TP(LastPosition.GetValueOrDefault(pc.PlayerId, pc.transform.position));
+ else
+ LastPosition[pc.PlayerId] = pc.transform.position;
+ }
+
+ public override void OnReportDeadBody()
+ {
+ Blockades = [];
+ LastPosition = [];
+ }
+}
\ No newline at end of file