diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index 38bab90c2..4f7400eb7 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -702,6 +702,7 @@ internal static class CustomRolesHelper
CustomRoles.Mimic or
CustomRoles.Damocles or
CustomRoles.DeadlyQuota or
+ CustomRoles.Circumvent or
CustomRoles.TicketsStealer or
CustomRoles.Taskcounter or
CustomRoles.Swift;
@@ -741,7 +742,7 @@ internal static class CustomRolesHelper
CustomRoles.Avanger when mainRole.IsImpostor() && !Options.ImpCanBeAvanger.GetBool() => false,
CustomRoles.Lazy when mainRole.IsNeutral() || mainRole.IsImpostor() || (mainRole.IsTasklessCrewmate() && !Options.TasklessCrewCanBeLazy.GetBool()) || (mainRole.IsTaskBasedCrewmate() && !Options.TaskBasedCrewCanBeLazy.GetBool()) => false,
CustomRoles.TicketsStealer or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when mainRole is CustomRoles.Bomber or CustomRoles.Nuker or CustomRoles.BoobyTrap or CustomRoles.Capitalism => false,
- CustomRoles.TicketsStealer or CustomRoles.Mimic or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when !mainRole.IsImpostor() => false,
+ CustomRoles.TicketsStealer or CustomRoles.Mimic or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Circumvent or CustomRoles.Damocles or CustomRoles.Mare when !mainRole.IsImpostor() => false,
CustomRoles.Torch when !mainRole.IsCrewmate() || mainRole is CustomRoles.Bewilder or CustomRoles.Sunglasses or CustomRoles.GuardianAngelTOHE => false,
CustomRoles.Bewilder when mainRole is CustomRoles.Torch or CustomRoles.GuardianAngelTOHE or CustomRoles.Sunglasses => false,
CustomRoles.Sunglasses when mainRole is CustomRoles.Torch or CustomRoles.GuardianAngelTOHE or CustomRoles.Bewilder => false,
@@ -870,7 +871,7 @@ internal static class CustomRolesHelper
CustomRoles.Avanger when pc.GetCustomRole().IsImpostor() && !Options.ImpCanBeAvanger.GetBool() => false,
CustomRoles.Lazy when pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsImpostor() || (pc.GetCustomRole().IsTasklessCrewmate() && !Options.TasklessCrewCanBeLazy.GetBool()) || (pc.GetCustomRole().IsTaskBasedCrewmate() && !Options.TaskBasedCrewCanBeLazy.GetBool()) => false,
CustomRoles.TicketsStealer or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when pc.Is(CustomRoles.Bomber) || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.BoobyTrap) || pc.Is(CustomRoles.Capitalism) => false,
- CustomRoles.TicketsStealer or CustomRoles.Mimic or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when !pc.GetCustomRole().IsImpostor() => false,
+ CustomRoles.TicketsStealer or CustomRoles.Mimic or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Circumvent or CustomRoles.Damocles or CustomRoles.Mare when !pc.GetCustomRole().IsImpostor() => false,
CustomRoles.Torch when !pc.IsCrewmate() || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Sunglasses) || pc.Is(CustomRoles.GuardianAngelTOHE) => false,
CustomRoles.Bewilder when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelTOHE) || pc.Is(CustomRoles.Sunglasses) => false,
CustomRoles.Sunglasses when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelTOHE) || pc.Is(CustomRoles.Bewilder) => false,
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index 52c1e35b5..32369c7d7 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -815,6 +815,7 @@ public static class Utils
if (pc.Is(CustomRoles.Damocles)) ProgressText.Append($" {Damocles.GetProgressText(playerId)}");
if (pc.Is(CustomRoles.Stressed)) ProgressText.Append($" {Stressed.GetProgressText(playerId)}");
+ if (pc.Is(CustomRoles.Circumvent)) ProgressText.Append($" {Circumvent.GetProgressText(playerId)}");
if (pc.Is(CustomRoles.Taskcounter))
{
string totalCompleted = comms ? "?" : $"{GameData.Instance.CompletedTasks}";
@@ -2455,6 +2456,7 @@ public static class Utils
Damocles.AfterMeetingTasks();
Stressed.AfterMeetingTasks();
+ Circumvent.AfterMeetingTasks();
if (Options.AirshipVariableElectrical.GetBool())
AirshipElectricalDoors.Initialize();
@@ -2532,9 +2534,6 @@ public static class Utils
Randomizer.OnAnyoneDeath(target);
- if (Romantic.PartnerId == target.PlayerId)
- _ = new LateTask(Romantic.ChangeRole, 0.5f, "Romantic ChangeRole");
-
if (Executioner.Target.ContainsValue(target.PlayerId))
Executioner.ChangeRoleByTarget(target);
if (Lawyer.Target.ContainsValue(target.PlayerId))
diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs
index 0eacdd721..1864d1be2 100644
--- a/Patches/HudPatch.cs
+++ b/Patches/HudPatch.cs
@@ -308,7 +308,8 @@ class HudManagerPatch
__instance.KillButton?.OverrideText(GetString("SuccubusKillButtonText"));
break;
case CustomRoles.Amnesiac:
- __instance.KillButton?.OverrideText(GetString("RememberButtonText"));
+ ActionButton amneButton = Amnesiac.RememberMode.GetValue() == 0 ? __instance.KillButton : __instance.ReportButton;
+ amneButton?.OverrideText(GetString("RememberButtonText"));
break;
case CustomRoles.Monarch:
usedButton?.OverrideText(GetString("MonarchKillButtonText"));
diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs
index 588ba0e8d..90a9c498c 100644
--- a/Patches/PlayerControlPatch.cs
+++ b/Patches/PlayerControlPatch.cs
@@ -1859,6 +1859,11 @@ class CoEnterVentPatch
}
}
+ if (__instance.myPlayer.Is(CustomRoles.Circumvent))
+ {
+ Circumvent.OnCoEnterVent(__instance, id);
+ }
+
if (__instance.myPlayer.GetCustomRole().GetDYRole() == RoleTypes.Impostor && !Main.PlayerStates[__instance.myPlayer.PlayerId].Role.CanUseImpostorVentButton(__instance.myPlayer))
{
_ = new LateTask(() => { __instance.RpcBootFromVent(id); }, 0.5f);
diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs
index 0ee54fe38..97966eae9 100644
--- a/Patches/onGameStartedPatch.cs
+++ b/Patches/onGameStartedPatch.cs
@@ -188,6 +188,7 @@ internal class ChangeRoleSettings
Stressed.Init();
Asthmatic.Init();
DoubleShot.Init();
+ Circumvent.Init();
}
catch (Exception ex)
{
@@ -530,6 +531,7 @@ internal class SelectRolesPatch
Stressed.Add();
Asthmatic.Add();
+ Circumvent.Add();
EndOfSelectRolePatch:
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index 88e129fd0..652639ba6 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -1747,6 +1747,14 @@
"DeadlyQuotaInfoLong": "(Add-ons):\\nDeadly Quota is an add-on that only impostors can get. The holder of this add-on will need to get a specified amount of kills (or more) to be able to win. If their win condition triggers before getting a required amount of kills, the player loses even if they were supposed to win.",
"DQNumOfKillsNeeded": "Minimum number of Kills required",
"Prefix.DeadlyQuota": "Deadly Quota",
+ "Circumvent": "Circumvent",
+ "CircumventInfo": "You can't vent",
+ "CircumventInfoLong": "(Add-ons):\\nCircumvent is an add-on that only impostors can get. The holder of this add-on will not be able to use vents (or only limited times per game or round).",
+ "Prefix.Circumvent": "Circumvent",
+ "NoVenting": "Block Venting",
+ "VentLimit": "Vent Entering Limit",
+ "LimitPerGame": "Limit Per Game",
+ "LimitPerRounds": "Limit Per Rounds",
"Damocles": "Damocles",
"DamoclesInfo": "Everything changes your lifespan",
"DamoclesInfoLong": "(Add-ons):\\nDamocles is an add-on that only impostors can get. The holder of this add-on will constantly have a timer displayed next to their role name. If the timer expires, the holder dies. The timer stops during meetings. Below is a list of what increases your time and what lowers your suicide timer:\\n\\n[+] You, or another impostor or madmate kills\\n[+] Entering a vent you didn't enter this round yet\\n[+] Surviving a meeting\\n[+] A player not on your team gets ejected\\n[-] Fixing a sabotage\\n[-] An impostor or a madmate dies\\n[-] Reporting a dead body or calling a meeting\\n[-] A player on your team gets ejected",
@@ -3058,6 +3066,7 @@
"LibrarianSilenceMode": "Silence",
"LibrarianNormalMode": "Normal",
"CanKillWhileShifted": "Can Kill While Shapeshifting",
+ "Shapeshift": "Shapeshift",
"Postman": "Postman",
"PostmanInfo": "Deliver Mail to Everyone!",
"PostmanInfoLong": "(Neutral):\\nAs the Postman, your job is to deliver mail to everyone. You'll always have a target, and you need to click your kill button on that target to deliver the mail to them. When you've delivered mail to everyone, your win condition is satisfied. You'll win with the winning team, no matter what happens.",
diff --git a/Roles/AddOns/Impostor/Circumvent.cs b/Roles/AddOns/Impostor/Circumvent.cs
new file mode 100644
index 000000000..dcb27e259
--- /dev/null
+++ b/Roles/AddOns/Impostor/Circumvent.cs
@@ -0,0 +1,105 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TOHE.Roles.AddOns.Impostor
+{
+ internal class Circumvent : IAddon
+ {
+ public AddonTypes Type => AddonTypes.ImpOnly;
+
+ private static Dictionary Limits = [];
+
+ private static OptionItem VentPreventionMode;
+ private static OptionItem Limit;
+
+ private static readonly string[] VentPreventionModes =
+ [
+ "NoVenting",
+ "LimitPerGame",
+ "LimitPerRounds"
+ ];
+
+ public void SetupCustomOption()
+ {
+ Options.SetupAdtRoleOptions(14690, CustomRoles.Circumvent, canSetNum: true);
+ VentPreventionMode = StringOptionItem.Create(14693, "VentPreventionMode", VentPreventionModes, 1, TabGroup.Addons, false)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Circumvent]);
+ Limit = IntegerOptionItem.Create(14694, "VentLimit", new(1, 90, 1), 8, TabGroup.Addons, false)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Circumvent]);
+ }
+
+ public static void Init()
+ {
+ Limits = [];
+ }
+
+ public static void Add()
+ {
+ if (VentPreventionMode.GetValue() == 0) return;
+
+ _ = new LateTask(() =>
+ {
+ foreach (var state in Main.PlayerStates)
+ {
+ if (state.Value.SubRoles.Contains(CustomRoles.Circumvent))
+ {
+ Limits[state.Key] = Limit.GetInt();
+ }
+ }
+ }, 3f, "Add Circumvents");
+ }
+
+ public static void OnCoEnterVent(PlayerPhysics physics, int ventId)
+ {
+ if (VentPreventionMode.GetValue() == 0)
+ {
+ _ = new LateTask(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Circumvent Boot From Vent");
+ return;
+ }
+
+ if (Limits.ContainsKey(physics.myPlayer.PlayerId))
+ {
+ Limits[physics.myPlayer.PlayerId]--;
+ Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer);
+ }
+ }
+
+ public static void AfterMeetingTasks()
+ {
+ if (VentPreventionMode.GetValue() != 2) return;
+
+ foreach (var playerId in Limits.Keys.ToArray())
+ {
+ Limits[playerId] = Limit.GetInt();
+ }
+ }
+
+ public static string GetProgressText(byte playerId)
+ {
+ if (!Limits.TryGetValue(playerId, out var limit)) return string.Empty;
+
+ var mode = VentPreventionMode.GetValue();
+ var color = limit switch
+ {
+ > 11 => "#00ff00",
+ > 7 when mode == 2 => "#00ff00",
+ > 7 => "#ffff00",
+ > 3 when mode == 2 => "#ffff00",
+ > 3 => "#ffa500",
+ > 0 when mode == 2 => "#ffa500",
+ > 0 => "#ff0000",
+ 0 when mode == 2 => "#ff0000",
+ 0 => "#8b0000",
+
+ _ => "#000000"
+ };
+
+ return $" ({limit})";
+ }
+
+ public static bool CanUseImpostorVentButton(PlayerControl pc)
+ {
+ return pc.inVent || (!Limits.TryGetValue(pc.PlayerId, out var limit) && VentPreventionMode.GetValue() != 0) || limit > 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs
index dc8f95d00..1606fd416 100644
--- a/Roles/Impostor/Blackmailer.cs
+++ b/Roles/Impostor/Blackmailer.cs
@@ -9,15 +9,24 @@ public class Blackmailer : RoleBase
private const int Id = 643050;
private static List playerIdList = [];
public static OptionItem SkillCooldown;
+ public static OptionItem BlackmailMode;
public static Dictionary BlackmailerMaxUp = [];
public static List ForBlackmailer = [];
+ private static readonly string[] BlackmailModes =
+ [
+ "EKill",
+ "Shapeshift"
+ ];
+
public static void SetupCustomOption()
{
SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Blackmailer);
SkillCooldown = FloatOptionItem.Create(Id + 5, "BlackmailerSkillCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.Blackmailer])
.SetValueFormat(OptionFormat.Seconds);
+ BlackmailMode = StringOptionItem.Create(Id + 4, "BlackmailMode", BlackmailModes, 1, TabGroup.ImpostorRoles, false)
+ .SetParent(CustomRoleSpawnChances[CustomRoles.Blackmailer]);
}
public override void Init()
@@ -40,9 +49,20 @@ public class Blackmailer : RoleBase
public override bool IsEnable => playerIdList.Count > 0;
+ public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
+ {
+ if (BlackmailMode.GetValue() == 1 || ForBlackmailer.Contains(target.PlayerId)) return true;
+
+ return killer.CheckDoubleTrigger(target, () =>
+ {
+ ForBlackmailer.Add(target.PlayerId);
+ killer.SetKillCooldown(3f);
+ });
+ }
+
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
{
- ForBlackmailer.Add(target.PlayerId);
+ if (BlackmailMode.GetValue() == 1 && !ForBlackmailer.Contains(target.PlayerId)) ForBlackmailer.Add(target.PlayerId);
return false;
}
}
\ No newline at end of file
diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs
index 2d500c008..78a0ae9ca 100644
--- a/Roles/Neutral/Amnesiac.cs
+++ b/Roles/Neutral/Amnesiac.cs
@@ -61,7 +61,7 @@ public class Amnesiac : RoleBase
public override bool IsEnable => playerIdList.Count > 0;
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = id.GetAbilityUseLimit() >= 1 ? RememberCooldown.GetFloat() : 300f;
- public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && player.GetAbilityUseLimit() >= 1;
+ public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && player.GetAbilityUseLimit() >= 1 && RememberMode.GetValue() == 0;
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false);
diff --git a/Roles/Neutral/Romantic.cs b/Roles/Neutral/Romantic.cs
index dc8b8bf02..4173a3ca8 100644
--- a/Roles/Neutral/Romantic.cs
+++ b/Roles/Neutral/Romantic.cs
@@ -149,6 +149,14 @@ public class Romantic : RoleBase
return false;
}
+ public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad)
+ {
+ if (!lowLoad && (Partner.Data.Disconnected || !Partner.IsAlive()) && Romantic_.IsAlive() && Romantic_.Is(CustomRoles.Romantic))
+ {
+ ChangeRole();
+ }
+ }
+
public static string TargetMark(PlayerControl seer, PlayerControl target)
{
if (!seer.Is(CustomRoles.Romantic))
@@ -203,7 +211,6 @@ public class Romantic : RoleBase
try
{
Romantic_.RpcSetCustomRole(partnerRole);
- HudManager.Instance.SetHudActive(Romantic_, Romantic_.Data.Role, !GameStates.IsMeeting);
Main.PlayerStates[RomanticId].RemoveSubRole(CustomRoles.NotAssigned);
Logger.Info($"Romantic Partner With Kill Button Died => Changing {Romantic_.GetNameWithRole()} to {Partner.GetAllRoleName().RemoveHtmlTags()}", "Romantic");
}
@@ -222,13 +229,7 @@ public class Romantic : RoleBase
VengefulRomantic.SendRPC();
}
- Utils.NotifyRoles(SpecifySeer: Romantic_);
- Utils.NotifyRoles(SpecifyTarget: Romantic_);
-
- Romantic_.ResetKillCooldown();
Romantic_.SetKillCooldown();
-
- Romantic_.SyncSettings();
}
}
diff --git a/Roles/RoleBase.cs b/Roles/RoleBase.cs
index 1287b023d..99199bdf8 100644
--- a/Roles/RoleBase.cs
+++ b/Roles/RoleBase.cs
@@ -2,6 +2,7 @@
using System;
using System.Text;
using TOHE.Roles.AddOns.Crewmate;
+using TOHE.Roles.AddOns.Impostor;
namespace TOHE
{
@@ -45,7 +46,7 @@ namespace TOHE
public virtual bool CanUseImpostorVentButton(PlayerControl pc)
{
- return pc.IsAlive() && (pc.Is(CustomRoleTypes.Impostor) || (pc.Is(CustomRoles.Bloodlust) && Bloodlust.CanVent.GetBool())) && pc.Data.Role.Role is not RoleTypes.Crewmate and not RoleTypes.Engineer;
+ return pc.IsAlive() && (pc.Is(CustomRoleTypes.Impostor) || (pc.Is(CustomRoles.Bloodlust) && Bloodlust.CanVent.GetBool())) && Circumvent.CanUseImpostorVentButton(pc) && pc.Data.Role.Role is not RoleTypes.Crewmate and not RoleTypes.Engineer;
}
public virtual bool CanUseSabotage(PlayerControl pc)
diff --git a/main.cs b/main.cs
index ad894b330..7192f1467 100644
--- a/main.cs
+++ b/main.cs
@@ -509,6 +509,7 @@ public class Main : BasePlugin
{ CustomRoles.Reach, "#74ba43" },
{ CustomRoles.Magnet, "#eb3477" },
{ CustomRoles.DeadlyQuota, "#ff1919" },
+ { CustomRoles.Circumvent, "#ff1919" },
{ CustomRoles.Damocles, "#ff1919" },
{ CustomRoles.Stressed, "#9403fc" },
{ CustomRoles.Charmed, "#cf6acd" },
@@ -945,6 +946,7 @@ public enum CustomRoles
Bewilder,
Bloodlust,
Charmed,
+ Circumvent,
Cleansed,
Clumsy,
Contagious,