diff --git a/CustomRoles.cs b/CustomRoles.cs
index f5b44014d..27da0e092 100644
--- a/CustomRoles.cs
+++ b/CustomRoles.cs
@@ -170,6 +170,7 @@ public enum CustomRoles
Benefactor,
Bestower,
Bodyguard,
+ Bouncer,
CameraMan,
Captain,
Car,
diff --git a/Main.cs b/Main.cs
index e7d6b37d4..575ec4be9 100644
--- a/Main.cs
+++ b/Main.cs
@@ -513,6 +513,7 @@ public class Main : BasePlugin
{ CustomRoles.Vacuum, "#E44CD6" },
{ CustomRoles.Carrier, "#5DE2E7" },
{ CustomRoles.Transmitter, "#c9a11e" },
+ { CustomRoles.Bouncer, "#95C8E8" },
{ CustomRoles.Operative, "#47f5d2" },
{ CustomRoles.Tar, "#8C796B" },
{ CustomRoles.Sensor, "#a3f7ff" },
diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index 79509f674..875845050 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -41,6 +41,7 @@ internal static class CustomRolesHelper
CustomRoles.Carrier,
CustomRoles.Empress,
CustomRoles.Tar,
+ CustomRoles.Bouncer,
// HnS
CustomRoles.Jet,
@@ -252,6 +253,7 @@ internal static class CustomRolesHelper
CustomRoles.Farmer => CustomRoles.Crewmate,
CustomRoles.Captain => CustomRoles.Crewmate,
CustomRoles.Transmitter => CustomRoles.Crewmate,
+ CustomRoles.Bouncer => CustomRoles.Crewmate,
CustomRoles.Tar => CustomRoles.Crewmate,
CustomRoles.Sensor => CustomRoles.Crewmate,
CustomRoles.Doorjammer => CustomRoles.Crewmate,
@@ -1433,6 +1435,7 @@ internal static class CustomRolesHelper
CustomRoles.Benefactor => RoleOptionType.Crewmate_Support,
CustomRoles.Bestower => RoleOptionType.Crewmate_Support,
CustomRoles.Bodyguard => RoleOptionType.Crewmate_Support,
+ CustomRoles.Bouncer => RoleOptionType.Crewmate_Support,
CustomRoles.Chef => RoleOptionType.Crewmate_Support,
CustomRoles.Cleanser => RoleOptionType.Crewmate_Support,
CustomRoles.Convener => RoleOptionType.Crewmate_Support,
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index 4f82dd969..89f7d7d1f 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -869,25 +869,25 @@ internal static class ExtendedPlayerControl
return Main.AbilityUseLimit.GetValueOrDefault(player.PlayerId, float.NaN);
}
- public void RpcRemoveAbilityUse(bool log = true)
+ public void RpcRemoveAbilityUse(bool log = true, bool notify = true)
{
float current = player.GetAbilityUseLimit();
if (float.IsNaN(current) || current <= 0f) return;
- player.SetAbilityUseLimit(current - 1, log: log);
+ player.SetAbilityUseLimit(current - 1, log: log, notify: notify);
}
- public void RpcIncreaseAbilityUseLimitBy(float get, bool log = true)
+ public void RpcIncreaseAbilityUseLimitBy(float get, bool log = true, bool notify = true)
{
float current = player.GetAbilityUseLimit();
if (float.IsNaN(current)) return;
- player.SetAbilityUseLimit(current + get, log: log);
+ player.SetAbilityUseLimit(current + get, log: log, notify: notify);
}
- public void SetAbilityUseLimit(float limit, bool rpc = true, bool log = true)
+ public void SetAbilityUseLimit(float limit, bool rpc = true, bool log = true, bool notify = true)
{
- player.PlayerId.SetAbilityUseLimit(limit, rpc, log);
+ player.PlayerId.SetAbilityUseLimit(limit, rpc, log, notify);
}
public void Suicide(PlayerState.DeathReason deathReason = PlayerState.DeathReason.Suicide, PlayerControl realKiller = null)
@@ -2510,7 +2510,7 @@ internal static class ExtendedPlayerControl
return Main.AbilityUseLimit.GetValueOrDefault(playerId, float.NaN);
}
- public void SetAbilityUseLimit(float limit, bool rpc = true, bool log = true)
+ public void SetAbilityUseLimit(float limit, bool rpc = true, bool log = true, bool notify = true)
{
limit = (float)Math.Round(limit, 2);
@@ -2527,7 +2527,7 @@ internal static class ExtendedPlayerControl
}
PlayerControl pc = GetPlayerById(playerId);
- if (Main.IntroDestroyed) NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ if (notify && Main.IntroDestroyed) NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
if (log) Logger.Info($" {pc.GetNameWithRole()} => {Math.Round(limit, 2)}", "SetAbilityUseLimit");
}
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index 614bccc59..f12ebacdc 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -4039,6 +4039,7 @@ public static class Utils
int cd = role switch
{
CustomRoles.Mole => Mole.CD.GetInt(),
+ CustomRoles.Bouncer => Bouncer.AbilityCooldown.GetInt(),
CustomRoles.Operative => Operative.AbilityCooldown.GetInt(),
CustomRoles.PortalMaker => PortalMaker.AbilityCooldown.GetInt(),
CustomRoles.Telecommunication => Telecommunication.VentCooldown.GetInt(),
diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs
index 81b1d3ab2..3bcf93c52 100644
--- a/Patches/ChatCommandPatch.cs
+++ b/Patches/ChatCommandPatch.cs
@@ -1153,10 +1153,10 @@ internal static class ChatCommands
private static void JailTalkCommand(PlayerControl player, string text, string[] args)
{
- if (args.Length < 2) return;
+ if (args.Length < 2 || !player.IsAlive()) return;
Jailor jailor = Main.PlayerStates[player.PlayerId].Role as Jailor ?? Main.PlayerStates.Select(x => x.Value.Role as Jailor).FirstOrDefault(x => x != null);
- if (jailor == null) return;
+ if (jailor == null || jailor.JailorTarget == byte.MaxValue) return;
bool amJailor = Jailor.PlayerIdList.Contains(player.PlayerId);
bool amJailed = player.PlayerId == jailor.JailorTarget;
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index b93253354..fa4eddc75 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -404,6 +404,16 @@
"BodyguardKillsKiller": "Bodyguard kills murderer",
"BodyguardProtectRadius": "Protect Radius",
"BodyguardProtected": "The Bodyguard protected your target and died for them",
+ "Bouncer": "Bouncer",
+ "BouncerInfo": "Guard Rooms",
+ "BouncerInfoLong": "(Crewmates):\\nAs the Bouncer, you may pet to mark the room you're currently in and bounce off players who try to enter it while your ability is active.",
+ "Bouncer.AbortBouncingIfLeftRoom": "Abort Bouncing When Leaving The Room",
+ "Bouncer.WhoGetsBounced": "Who Gets Bounced Off",
+ "Bouncer.WhoGetsBouncedOptions.Everyone": "Everyone",
+ "Bouncer.WhoGetsBouncedOptions.Impostors": "<#ff1919>Impostors",
+ "Bouncer.WhoGetsBouncedOptions.ImpNKNPNE": "<#ff1919>Imp/<#ff5500>NK/<#a10e49>NP/<#ffff00>NE",
+ "Bouncer.Suffix": "<#00ffa5>Guarding {0}",
+ "Bouncer.Suffix.RemainingSeconds": " <#00ffff>({0}s left)",
"CameraMan": "Camera Man",
"CameraManInfo": "View Cameras anytime",
"CameraManInfoLong": "(Crewmates):\\nAs the Camera Man, you can vent/pet to instantly teleport to the security room.",
@@ -1947,7 +1957,7 @@
"GangsterSuccessfullyRecruited": "You successfully recruited a player",
"Gatekeeper": "Gatekeeper",
"GatekeeperInfo": "Keep Track Of People In Rooms",
- "GatekeeperInfoLong": "(Impostors):\\nAs the Gatekeeper, you may vanish/pet/shapeshift to mark the room you're in. From now, you'll now see how many living players are in that room, updated in real time. Dead bodies aren't counted. You can have multiple marked rooms at the same time depending on settings.",
+ "GatekeeperInfoLong": "(Impostors):\\nAs the Gatekeeper, you may vanish/pet/shapeshift to mark the room you're in. From now, you'll see how many living players are in that room, updated in real time. Dead bodies aren't counted. You can have multiple marked rooms at the same time depending on settings.",
"Gatekeeper.MaxRoomsMarkedAtOnce": "Max Rooms Marked At Once",
"Generator": "Generator",
"GeneratorInfo": "Power up the ship",
diff --git a/Roles/Standard/Coven/Dreamweaver.cs b/Roles/Standard/Coven/Dreamweaver.cs
index 31cd31d42..523f115ad 100644
--- a/Roles/Standard/Coven/Dreamweaver.cs
+++ b/Roles/Standard/Coven/Dreamweaver.cs
@@ -63,10 +63,10 @@ public class Dreamweaver : CovenBase
{
if (Thanos.IsImmune(target)) return false;
InsanePlayers.Add(target.PlayerId);
+ killer.RpcRemoveAbilityUse(notify: false);
Utils.SendRPC(CustomRPC.SyncRoleData, DreamweaverId, 1, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
- killer.RpcRemoveAbilityUse();
return false;
}
diff --git a/Roles/Standard/Coven/Enchanter.cs b/Roles/Standard/Coven/Enchanter.cs
index 70b0b8831..d8265bbcf 100644
--- a/Roles/Standard/Coven/Enchanter.cs
+++ b/Roles/Standard/Coven/Enchanter.cs
@@ -61,10 +61,10 @@ public class Enchanter : CovenBase
if (killer.GetAbilityUseLimit() < 1) return;
EnchantedPlayers ??= [];
EnchantedPlayers.Add(target.PlayerId);
+ killer.RpcRemoveAbilityUse(notify: false);
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
- killer.RpcRemoveAbilityUse();
}
}
}
diff --git a/Roles/Standard/Crewmate/Chaos/Tree.cs b/Roles/Standard/Crewmate/Chaos/Tree.cs
index ad0de3d72..6a5292dbf 100644
--- a/Roles/Standard/Crewmate/Chaos/Tree.cs
+++ b/Roles/Standard/Crewmate/Chaos/Tree.cs
@@ -51,13 +51,14 @@ public class Tree : RoleBase
public override void OnPet(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed;
pc.MarkDirtySettings();
bool treeSpriteVisible = TreeSpriteVisible.GetBool();
+ pc.RpcRemoveAbilityUse(notify: !treeSpriteVisible);
+
if (treeSpriteVisible)
{
TreeSpriteActive = true;
diff --git a/Roles/Standard/Crewmate/Investigate/Analyst.cs b/Roles/Standard/Crewmate/Investigate/Analyst.cs
index 4bcfd2382..835d3a085 100644
--- a/Roles/Standard/Crewmate/Investigate/Analyst.cs
+++ b/Roles/Standard/Crewmate/Investigate/Analyst.cs
@@ -164,7 +164,7 @@ internal class Analyst : RoleBase
if (CurrentTarget.TIME + Duration.GetInt() <= Utils.TimeStamp)
{
CurrentTarget.ID = byte.MaxValue;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(GetAnalyzeResult(target), 10f);
pc.SetKillCooldown();
}
diff --git a/Roles/Standard/Crewmate/Investigate/Catcher.cs b/Roles/Standard/Crewmate/Investigate/Catcher.cs
index b7b6b2201..85f7bd98c 100644
--- a/Roles/Standard/Crewmate/Investigate/Catcher.cs
+++ b/Roles/Standard/Crewmate/Investigate/Catcher.cs
@@ -93,7 +93,7 @@ public class Catcher : RoleBase
{
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Vector2 pos = pc.Pos();
Traps[pos] = new(pos, pc);
diff --git a/Roles/Standard/Crewmate/Investigate/Coroner.cs b/Roles/Standard/Crewmate/Investigate/Coroner.cs
index a10132d4e..bda93e171 100644
--- a/Roles/Standard/Crewmate/Investigate/Coroner.cs
+++ b/Roles/Standard/Crewmate/Investigate/Coroner.cs
@@ -108,8 +108,8 @@ public class Coroner : RoleBase
CoronerTargets.Add(killer.PlayerId);
TargetArrow.Add(pc.PlayerId, killer.PlayerId);
+ pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(GetString("CoronerTrackRecorded"));
- pc.RpcRemoveAbilityUse();
if (LeaveDeadBodyUnreportable.GetBool())
{
diff --git a/Roles/Standard/Crewmate/Investigate/Druid.cs b/Roles/Standard/Crewmate/Investigate/Druid.cs
index ba151ceee..d1b95a1cf 100644
--- a/Roles/Standard/Crewmate/Investigate/Druid.cs
+++ b/Roles/Standard/Crewmate/Investigate/Druid.cs
@@ -156,7 +156,7 @@ public class Druid : RoleBase
}, onTick: () => pc.Notify(string.Format(GetString("DruidTimeLeft"), (int)Math.Ceiling(DelayTimer.Remaining.TotalSeconds)), 2f, overrideAll: true), onCanceled: () => DelayTimer = null);
}
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: isPet);
}
public override void OnCheckPlayerPosition(PlayerControl pc)
diff --git a/Roles/Standard/Crewmate/Investigate/Lighter.cs b/Roles/Standard/Crewmate/Investigate/Lighter.cs
index d81fe500b..478349422 100644
--- a/Roles/Standard/Crewmate/Investigate/Lighter.cs
+++ b/Roles/Standard/Crewmate/Investigate/Lighter.cs
@@ -116,8 +116,8 @@ internal class Lighter : RoleBase
pc.MarkDirtySettings();
}, onCanceled: () => Timer = null);
+ pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(Translator.GetString("LighterSkillInUse"), skillDuration);
- pc.RpcRemoveAbilityUse();
pc.MarkDirtySettings();
}
else
diff --git a/Roles/Standard/Crewmate/Investigate/Medium.cs b/Roles/Standard/Crewmate/Investigate/Medium.cs
index 8dbb15c04..2583c7dd7 100644
--- a/Roles/Standard/Crewmate/Investigate/Medium.cs
+++ b/Roles/Standard/Crewmate/Investigate/Medium.cs
@@ -59,7 +59,7 @@ public class Medium : RoleBase
{
if (!pc.Is(CustomRoles.Medium) || pc.PlayerId == target.PlayerId || pc.GetAbilityUseLimit() < 1) continue;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
ContactPlayer.TryAdd(target.PlayerId, pc.PlayerId);
Logger.Info($"Medium Connection: {pc.GetNameWithRole()} => {target.PlayerName}", "Medium");
}
diff --git a/Roles/Standard/Crewmate/Investigate/Perceiver.cs b/Roles/Standard/Crewmate/Investigate/Perceiver.cs
index cf2685a0b..b6a5fd2a0 100644
--- a/Roles/Standard/Crewmate/Investigate/Perceiver.cs
+++ b/Roles/Standard/Crewmate/Investigate/Perceiver.cs
@@ -74,12 +74,11 @@ internal class Perceiver : RoleBase
private static void UseAbility(PlayerControl pc)
{
if (!pc || pc.GetAbilityUseLimit() < 1f) return;
+ pc.RpcRemoveAbilityUse(notify: false);
PlayerControl[] killers = FastVector2.GetPlayersInRange(pc.Pos(), Radius.GetFloat(), x => !x.Is(Team.Crewmate) && x.HasKillButton()).ToArray();
pc.Notify(string.Format(Translator.GetString("PerceiverNotify"), killers.Length), 7f);
- pc.RpcRemoveAbilityUse();
-
if (pc.AmOwner)
{
HashSet allKillers = Main.CachedAlivePlayerControls().Where(x => !x.Is(Team.Crewmate) && x.HasKillButton()).Select(x => x.PlayerId).ToHashSet();
diff --git a/Roles/Standard/Crewmate/Investigate/Scout.cs b/Roles/Standard/Crewmate/Investigate/Scout.cs
index 99f4c1157..c847a7a8a 100644
--- a/Roles/Standard/Crewmate/Investigate/Scout.cs
+++ b/Roles/Standard/Crewmate/Investigate/Scout.cs
@@ -87,7 +87,7 @@ public class Scout : RoleBase
byte trackerId = reader.ReadByte();
byte targetId = reader.ReadByte();
- Utils.GetPlayerById(trackerId).RpcRemoveAbilityUse();
+ Utils.GetPlayerById(trackerId)?.RpcRemoveAbilityUse();
TrackerTarget[trackerId].Add(targetId);
}
diff --git a/Roles/Standard/Crewmate/Investigate/Sentry.cs b/Roles/Standard/Crewmate/Investigate/Sentry.cs
index 8d918a007..df052c660 100644
--- a/Roles/Standard/Crewmate/Investigate/Sentry.cs
+++ b/Roles/Standard/Crewmate/Investigate/Sentry.cs
@@ -149,7 +149,7 @@ internal class Sentry : RoleBase
if (!fromDevice)
{
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
}
string roomName = Translator.GetString(MonitoredRoom.RoomId);
@@ -168,7 +168,7 @@ internal class Sentry : RoleBase
if (target.shapeshiftTargetPlayerId != byte.MaxValue)
realTarget = Utils.GetPlayerById(target.shapeshiftTargetPlayerId);
- if (realTarget == null) realTarget = target;
+ if (!realTarget) realTarget = target;
string coloredName = Utils.ColorString(Main.PlayerColors[realTarget.PlayerId], realTarget.GetRealName());
@@ -182,7 +182,7 @@ internal class Sentry : RoleBase
foreach (byte id in DeadBodiesInRoom)
{
PlayerControl bodyPc = Utils.GetPlayerById(id);
- if (bodyPc == null) continue;
+ if (!bodyPc) continue;
string coloredName = Utils.ColorString(Main.PlayerColors[id], bodyPc.GetRealName());
diff --git a/Roles/Standard/Crewmate/Investigate/Spy.cs b/Roles/Standard/Crewmate/Investigate/Spy.cs
index 2e522bc80..505fb1dd7 100644
--- a/Roles/Standard/Crewmate/Investigate/Spy.cs
+++ b/Roles/Standard/Crewmate/Investigate/Spy.cs
@@ -85,7 +85,7 @@ public class Spy : RoleBase
{
if (!target.Is(CustomRoles.Spy) || killer.PlayerId == target.PlayerId || target.GetAbilityUseLimit() < 1) return true;
- target.RpcRemoveAbilityUse();
+ target.RpcRemoveAbilityUse(notify: false);
SpyRedNameList ??= [];
SpyRedNameList.Add(killer.PlayerId);
SendRPC(1, killer.PlayerId);
diff --git a/Roles/Standard/Crewmate/Investigate/Unshifter.cs b/Roles/Standard/Crewmate/Investigate/Unshifter.cs
index e2742b301..f83677967 100644
--- a/Roles/Standard/Crewmate/Investigate/Unshifter.cs
+++ b/Roles/Standard/Crewmate/Investigate/Unshifter.cs
@@ -61,7 +61,7 @@ public class Unshifter : RoleBase
target.RpcShapeshift(target, true);
target.RpcResetAbilityCooldown();
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.SetKillCooldown();
killer.Notify(GetString("UnshifterSuccess"));
diff --git a/Roles/Standard/Crewmate/Investigate/Whisperer.cs b/Roles/Standard/Crewmate/Investigate/Whisperer.cs
index aff5aad24..6bdfd7827 100644
--- a/Roles/Standard/Crewmate/Investigate/Whisperer.cs
+++ b/Roles/Standard/Crewmate/Investigate/Whisperer.cs
@@ -165,7 +165,7 @@ public class Whisperer : RoleBase
Info.Add(info);
if (Info.Count > 5) Info.RemoveAt(0);
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 4, info);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
diff --git a/Roles/Standard/Crewmate/Killing/Veteran.cs b/Roles/Standard/Crewmate/Killing/Veteran.cs
index 535be3591..b875626e9 100644
--- a/Roles/Standard/Crewmate/Killing/Veteran.cs
+++ b/Roles/Standard/Crewmate/Killing/Veteran.cs
@@ -106,7 +106,7 @@ internal class Veteran : RoleBase
pc.RpcResetAbilityCooldown();
pc.Notify(string.Format(Translator.GetString("VeteranOffGuard"), (int)pc.GetAbilityUseLimit()));
}, onCanceled: () => VeteranInProtect.Remove(pc.PlayerId));
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
pc.RPCPlayCustomSound("Gunload");
pc.Notify(Translator.GetString("VeteranOnGuard"), VeteranSkillDuration.GetFloat());
pc.MarkDirtySettings();
diff --git a/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs b/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs
index 844c53f1e..956163a10 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs
@@ -86,7 +86,7 @@ public class CameraMan : RoleBase
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
if (UsePets.GetBool()) return;
- if (pc == null) return;
+ if (!pc) return;
if (pc.GetAbilityUseLimit() >= 1)
{
@@ -99,7 +99,7 @@ public class CameraMan : RoleBase
public override void OnPet(PlayerControl pc)
{
- if (pc == null) return;
+ if (!pc) return;
if (pc.GetAbilityUseLimit() >= 1)
{
diff --git a/Roles/Standard/Crewmate/Miscellaneous/Paranoid.cs b/Roles/Standard/Crewmate/Miscellaneous/Paranoid.cs
index 4958008a1..6ef3cc989 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/Paranoid.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/Paranoid.cs
@@ -89,7 +89,7 @@ internal class Paranoid : RoleBase
{
if (pc.GetAbilityUseLimit() >= 1)
{
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
if (AmongUsClient.Instance.AmHost) LateTask.New(() => Utils.SendMessage(Translator.GetString("SkillUsedLeft") + (ParanoidNumOfUseButton.GetInt() - ParaUsedButtonCount[pc.PlayerId]), pc.PlayerId), 4f, "Paranoid Skill Remain Message");
pc.NoCheckStartMeeting(pc.Data);
}
diff --git a/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs b/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs
index 3e3d2c548..6747fa98a 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs
@@ -71,7 +71,7 @@ public class Vacuum : RoleBase
public override void OnPet(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Timer = new CountdownTimer(AbilityDuration.GetInt(), () =>
{
Timer = null;
diff --git a/Roles/Standard/Crewmate/Power/Adrenaline.cs b/Roles/Standard/Crewmate/Power/Adrenaline.cs
index 363d2c49d..9fb63ccf9 100644
--- a/Roles/Standard/Crewmate/Power/Adrenaline.cs
+++ b/Roles/Standard/Crewmate/Power/Adrenaline.cs
@@ -70,7 +70,7 @@ public class Adrenaline : RoleBase
{
if (!base.OnCheckMurderAsTarget(killer, target) || target.GetAbilityUseLimit() < 1 || target.GetTaskState().CompletedTasksCount < MinTasksRequired.GetInt() || Timer != null) return true;
- target.RpcRemoveAbilityUse();
+ target.RpcRemoveAbilityUse(notify: false);
Timer = new CountdownTimer(Time.GetInt(), () =>
{
Timer = null;
diff --git a/Roles/Standard/Crewmate/Power/Astral.cs b/Roles/Standard/Crewmate/Power/Astral.cs
index 0f8860416..ed75929d1 100644
--- a/Roles/Standard/Crewmate/Power/Astral.cs
+++ b/Roles/Standard/Crewmate/Power/Astral.cs
@@ -68,7 +68,7 @@ public class Astral : RoleBase
void BecomeGhostTemporarily(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1f || ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Timer = new CountdownTimer(AbilityDuration.GetInt(), () => BecomeAliveAgain(pc), onTick: () => Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc), onCanceled: () => Timer = null);
Utils.SendRPC(CustomRPC.SyncRoleData, AstralId);
diff --git a/Roles/Standard/Crewmate/Power/Swapper.cs b/Roles/Standard/Crewmate/Power/Swapper.cs
index 13c3bc5c1..1f392730f 100644
--- a/Roles/Standard/Crewmate/Power/Swapper.cs
+++ b/Roles/Standard/Crewmate/Power/Swapper.cs
@@ -184,7 +184,7 @@ public class Swapper : RoleBase
{
if (SwapTargets != (byte.MaxValue, byte.MaxValue))
{
- Utils.GetPlayerById(SwapperId).RpcRemoveAbilityUse();
+ Utils.GetPlayerById(SwapperId)?.RpcRemoveAbilityUse(notify: false);
SwapTargets = (byte.MaxValue, byte.MaxValue);
}
}
diff --git a/Roles/Standard/Crewmate/Support/Aid.cs b/Roles/Standard/Crewmate/Support/Aid.cs
index 6078765c0..ee027aaad 100644
--- a/Roles/Standard/Crewmate/Support/Aid.cs
+++ b/Roles/Standard/Crewmate/Support/Aid.cs
@@ -91,7 +91,7 @@ public class Aid : RoleBase
{
if (pc.GetAbilityUseLimit() >= 1 && TargetId != byte.MaxValue)
{
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
PlayerControl target = Utils.GetPlayerById(TargetId);
ShieldedPlayers[TargetId] = new CountdownTimer(AidDur.GetInt(), () =>
{
diff --git a/Roles/Standard/Crewmate/Support/Benefactor.cs b/Roles/Standard/Crewmate/Support/Benefactor.cs
index 2bf76a622..b60f109a8 100644
--- a/Roles/Standard/Crewmate/Support/Benefactor.cs
+++ b/Roles/Standard/Crewmate/Support/Benefactor.cs
@@ -68,7 +68,7 @@ internal class Benefactor : RoleBase
if (player.Is(CustomRoles.Benefactor))
{
if (playerId.GetAbilityUseLimit() < 1) return;
- player.RpcRemoveAbilityUse();
+ player.RpcRemoveAbilityUse(notify: false);
if (TaskIndex.TryGetValue(playerId, out var list)) list.Add(task.Index);
else TaskIndex[playerId] = [task.Index];
diff --git a/Roles/Standard/Crewmate/Support/Bouncer.cs b/Roles/Standard/Crewmate/Support/Bouncer.cs
new file mode 100644
index 000000000..447cfb971
--- /dev/null
+++ b/Roles/Standard/Crewmate/Support/Bouncer.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using EHR.Modules.Extensions;
+
+namespace EHR.Roles;
+
+public class Bouncer : RoleBase
+{
+ public static bool On;
+
+ public static OptionItem AbilityCooldown;
+ private static OptionItem AbilityDuration;
+ private static OptionItem AbortBouncingIfLeftRoom;
+ private static OptionItem WhoGetsBounced;
+ private static OptionItem AbilityUseLimit;
+ private static OptionItem AbilityUseGainWithEachTaskCompleted;
+ private static OptionItem AbilityChargesWhenFinishedTasks;
+
+ private static readonly string[] WhoGetsBouncedOptions =
+ [
+ "Bouncer.WhoGetsBouncedOptions.Everyone",
+ "Bouncer.WhoGetsBouncedOptions.Impostors",
+ "Bouncer.WhoGetsBouncedOptions.ImpNKNPNE"
+ ];
+
+ public override bool IsEnable => On;
+
+ private Dictionary LastPosition = [];
+ private CountdownTimer Timer;
+ private PlainShipRoom MarkedRoom;
+ private byte BouncerId;
+
+ public override void SetupCustomOption()
+ {
+ StartSetup(659300)
+ .AutoSetupOption(ref AbilityCooldown, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds)
+ .AutoSetupOption(ref AbilityDuration, 15, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds)
+ .AutoSetupOption(ref AbortBouncingIfLeftRoom, true)
+ .AutoSetupOption(ref WhoGetsBounced, 0, WhoGetsBouncedOptions)
+ .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times)
+ .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.5f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times)
+ .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times);
+ }
+
+ public override void Init()
+ {
+ On = false;
+ }
+
+ public override void Add(byte playerId)
+ {
+ On = true;
+ BouncerId = playerId;
+ LastPosition = [];
+ Timer = null;
+ MarkedRoom = null;
+ playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
+ }
+
+ public override void OnPet(PlayerControl pc)
+ {
+ var room = pc.GetPlainShipRoom();
+ if (!room || pc.GetAbilityUseLimit() < 1f) return;
+
+ MarkedRoom = room;
+ Timer = new CountdownTimer(AbilityDuration.GetFloat(), () =>
+ {
+ Timer = null;
+ if (!pc || !pc.IsAlive()) return;
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ }, onTick: () =>
+ {
+ if (Timer.Remaining.TotalSeconds >= 6 || !pc || !pc.IsAlive()) return;
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ }, onCanceled: () => Timer = null);
+ pc.RpcRemoveAbilityUse(notify: false);
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ }
+
+ public override void OnCheckPlayerPosition(PlayerControl pc)
+ {
+ if (Timer == null) return;
+
+ if (pc.PlayerId == BouncerId)
+ {
+ if (AbortBouncingIfLeftRoom.GetBool() && !pc.IsInRoom(MarkedRoom))
+ {
+ Timer.Dispose();
+ Timer = null;
+ }
+
+ return;
+ }
+
+ if (!LastPosition.TryGetValue(pc.PlayerId, out Vector2 lastPosition))
+ LastPosition[pc.PlayerId] = pc.transform.position;
+ else if (pc.IsInRoom(MarkedRoom) && WhoGetsBounced.GetValue() switch
+ {
+ 0 => true,
+ 1 => pc.Is(Team.Impostor),
+ 2 => pc.Is(Team.Impostor) || pc.IsNeutralKiller() || pc.IsNeutralPariah() || pc.IsNeutralEvil(),
+ _ => false
+ })
+ pc.TP(lastPosition);
+ }
+
+ public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
+ {
+ if (seer.PlayerId != BouncerId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || Timer == null) return string.Empty;
+ int remainingSeconds = (int)Math.Ceiling(Timer.Remaining.TotalSeconds);
+ string timerText = seer.IsModdedClient() || remainingSeconds <= 5 ? string.Format(Translator.GetString("Bouncer.Suffix.RemainingSeconds"), remainingSeconds) : string.Empty;
+ return string.Format(Translator.GetString("Bouncer.Suffix"), Translator.GetString(MarkedRoom.RoomId)) + timerText;
+ }
+}
\ No newline at end of file
diff --git a/Roles/Standard/Crewmate/Support/Convener.cs b/Roles/Standard/Crewmate/Support/Convener.cs
index 094c2ea0d..e4fa9f6d7 100644
--- a/Roles/Standard/Crewmate/Support/Convener.cs
+++ b/Roles/Standard/Crewmate/Support/Convener.cs
@@ -64,7 +64,7 @@ internal class Convener : RoleBase
private static void PullEveryone(PlayerControl pc, bool isPet = false)
{
- if (pc == null || pc.GetAbilityUseLimit() < 1f) return;
+ if (!pc || pc.GetAbilityUseLimit() < 1f) return;
pc.RPCPlayCustomSound("Teleport");
LateTask.New(() => Main.EnumerateAlivePlayerControls().MassTP(pc.Pos(), log: true), isPet ? 0.5f : 2f, "Convener TP");
diff --git a/Roles/Standard/Crewmate/Support/Deputy.cs b/Roles/Standard/Crewmate/Support/Deputy.cs
index dc18e06ab..9924136f3 100644
--- a/Roles/Standard/Crewmate/Support/Deputy.cs
+++ b/Roles/Standard/Crewmate/Support/Deputy.cs
@@ -80,7 +80,7 @@ public class Deputy : RoleBase
if (!target.Is(CustomRoles.Deputy))
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.Notify(CustomRoles.Deputy.ColoredTextByRole(GetString("DeputyHandcuffedPlayer")));
diff --git a/Roles/Standard/Crewmate/Support/DonutDelivery.cs b/Roles/Standard/Crewmate/Support/DonutDelivery.cs
index e8ca67216..8384231d0 100644
--- a/Roles/Standard/Crewmate/Support/DonutDelivery.cs
+++ b/Roles/Standard/Crewmate/Support/DonutDelivery.cs
@@ -98,7 +98,7 @@ public class DonutDelivery : RoleBase
{
if (!IsEnable) return false;
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
int num1 = IRandom.Instance.Next(0, 19);
killer.Notify(GetString($"DonutDelivered-{num1}"));
diff --git a/Roles/Standard/Crewmate/Support/Doorjammer.cs b/Roles/Standard/Crewmate/Support/Doorjammer.cs
index e9cced17e..0faf6c400 100644
--- a/Roles/Standard/Crewmate/Support/Doorjammer.cs
+++ b/Roles/Standard/Crewmate/Support/Doorjammer.cs
@@ -65,7 +65,7 @@ public class Doorjammer : RoleBase
Utils.SendRPC(CustomRPC.SyncRoleData, DoorjammerId, 2);
}
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
JammedRooms.Add(room);
Utils.SendRPC(CustomRPC.SyncRoleData, DoorjammerId, 1, (byte)room);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
diff --git a/Roles/Standard/Crewmate/Support/Escort.cs b/Roles/Standard/Crewmate/Support/Escort.cs
index d3fc7144a..48436e36b 100644
--- a/Roles/Standard/Crewmate/Support/Escort.cs
+++ b/Roles/Standard/Crewmate/Support/Escort.cs
@@ -70,7 +70,7 @@ public class Escort : RoleBase
{
if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false;
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.SetKillCooldown();
target.BlockRole(Duration.GetFloat());
killer.Notify(GetString("EscortTargetHacked"));
diff --git a/Roles/Standard/Crewmate/Support/Gardener.cs b/Roles/Standard/Crewmate/Support/Gardener.cs
index 63c7efea8..881991c36 100644
--- a/Roles/Standard/Crewmate/Support/Gardener.cs
+++ b/Roles/Standard/Crewmate/Support/Gardener.cs
@@ -52,8 +52,8 @@ public class Gardener : RoleBase
}
Plants.Add(new Plant(pos));
+ pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(Translator.GetString("Gardener.PlantCreated"));
- pc.RpcRemoveAbilityUse();
}
public override void AfterMeetingTasks()
diff --git a/Roles/Standard/Crewmate/Support/Grappler.cs b/Roles/Standard/Crewmate/Support/Grappler.cs
index c7f0661c2..e6950ad04 100644
--- a/Roles/Standard/Crewmate/Support/Grappler.cs
+++ b/Roles/Standard/Crewmate/Support/Grappler.cs
@@ -53,7 +53,7 @@ public class Grappler : RoleBase
if (GrapplerId.GetAbilityUseLimit() >= 1f)
{
InUse = true;
- GrapplerId.GetPlayer().RpcRemoveAbilityUse();
+ GrapplerId.GetPlayer()?.RpcRemoveAbilityUse(notify: false);
}
else
InUse = false;
diff --git a/Roles/Standard/Crewmate/Support/Grenadier.cs b/Roles/Standard/Crewmate/Support/Grenadier.cs
index 9357cc76f..ae619569a 100644
--- a/Roles/Standard/Crewmate/Support/Grenadier.cs
+++ b/Roles/Standard/Crewmate/Support/Grenadier.cs
@@ -123,8 +123,8 @@ internal class Grenadier : RoleBase
}
pc.RPCPlayCustomSound("FlashBang");
+ pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(Translator.GetString("GrenadierSkillInUse"), GrenadierSkillDuration.GetFloat());
- pc.RpcRemoveAbilityUse();
Utils.MarkEveryoneDirtySettingsV3();
}
else
diff --git a/Roles/Standard/Crewmate/Support/Medic.cs b/Roles/Standard/Crewmate/Support/Medic.cs
index cf02724ab..e70c80e25 100644
--- a/Roles/Standard/Crewmate/Support/Medic.cs
+++ b/Roles/Standard/Crewmate/Support/Medic.cs
@@ -170,7 +170,7 @@ public class Medic : RoleBase
{
if (ProtectList.Contains(target.PlayerId)) return false;
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
ProtectList.Add(target.PlayerId);
TempMarkProtectedList.Add(target.PlayerId);
diff --git a/Roles/Standard/Crewmate/Support/Monarch.cs b/Roles/Standard/Crewmate/Support/Monarch.cs
index 3340f7552..441754adf 100644
--- a/Roles/Standard/Crewmate/Support/Monarch.cs
+++ b/Roles/Standard/Crewmate/Support/Monarch.cs
@@ -74,7 +74,7 @@ public class Monarch : RoleBase
if (target != null && !target.GetCustomRole().IsNotKnightable() && !target.Is(CustomRoles.Knighted) && !target.Is(CustomRoles.Stealer))
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
target.RpcSetCustomRole(CustomRoles.Knighted);
var sender = CustomRpcSender.Create("Monarch.OnCheckMurder", SendOption.Reliable);
diff --git a/Roles/Standard/Crewmate/Support/Pacifist.cs b/Roles/Standard/Crewmate/Support/Pacifist.cs
index 3f57efbcb..d5d2cbf3f 100644
--- a/Roles/Standard/Crewmate/Support/Pacifist.cs
+++ b/Roles/Standard/Crewmate/Support/Pacifist.cs
@@ -78,7 +78,7 @@ internal class Pacifist : RoleBase
return;
}
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
bool isMadMate = pc.Is(CustomRoles.Madmate);
Main.EnumerateAlivePlayerControls()
diff --git a/Roles/Standard/Crewmate/Support/SecurityGuard.cs b/Roles/Standard/Crewmate/Support/SecurityGuard.cs
index e822916dd..ae8ceec57 100644
--- a/Roles/Standard/Crewmate/Support/SecurityGuard.cs
+++ b/Roles/Standard/Crewmate/Support/SecurityGuard.cs
@@ -95,8 +95,8 @@ internal class SecurityGuard : RoleBase
pc.RpcResetAbilityCooldown();
pc.Notify(Translator.GetString("SecurityGuardSkillStop"));
}, onCanceled: () => BlockSabo.Remove(pc.PlayerId));
+ pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(Translator.GetString("SecurityGuardSkillInUse"), SecurityGuardSkillDuration.GetFloat());
- pc.RpcRemoveAbilityUse();
}
else
pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks"));
diff --git a/Roles/Standard/Crewmate/Support/TimeMaster.cs b/Roles/Standard/Crewmate/Support/TimeMaster.cs
index e6751f96a..c80336a2b 100644
--- a/Roles/Standard/Crewmate/Support/TimeMaster.cs
+++ b/Roles/Standard/Crewmate/Support/TimeMaster.cs
@@ -109,7 +109,7 @@ internal class TimeMaster : RoleBase
public override void OnPet(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Main.Instance.StartCoroutine(Rewind());
@@ -121,7 +121,7 @@ internal class TimeMaster : RoleBase
{
if (UsePets.GetBool()) return;
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Main.Instance.StartCoroutine(Rewind());
diff --git a/Roles/Standard/Crewmate/Support/Ventguard.cs b/Roles/Standard/Crewmate/Support/Ventguard.cs
index d590841da..d510cffd7 100644
--- a/Roles/Standard/Crewmate/Support/Ventguard.cs
+++ b/Roles/Standard/Crewmate/Support/Ventguard.cs
@@ -70,7 +70,7 @@ internal class Ventguard : RoleBase
{
if (pc.GetAbilityUseLimit() >= 1)
{
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
if (!BlockedVents.Contains(vent.Id)) BlockedVents.Add(vent.Id);
pc.Notify(Translator.GetString("VentBlockSuccess"));
diff --git a/Roles/Standard/Crewmate/Support/Wizard.cs b/Roles/Standard/Crewmate/Support/Wizard.cs
index 643750da4..f7c1fd117 100644
--- a/Roles/Standard/Crewmate/Support/Wizard.cs
+++ b/Roles/Standard/Crewmate/Support/Wizard.cs
@@ -217,7 +217,7 @@ public class Wizard : RoleBase
hasValue |= sender.SyncSettings(target);
hasValue |= sender.SetKillCooldown(killer);
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
hasValue |= CustomRpcSenderExtensions.Notify(ref sender, killer, string.Format(Translator.GetString("Wizard.BuffGivenNotify"), target.PlayerId.ColoredPlayerName(), Translator.GetString($"Wizard.Buff.{SelectedBuff}"), Math.Round(value, 1)));
Utils.SendRPC(CustomRPC.SyncRoleData, 3, target.PlayerId);
diff --git a/Roles/Standard/Impostor/Concealing/Anonymous.cs b/Roles/Standard/Impostor/Concealing/Anonymous.cs
index 72dda8b9d..7cde900af 100644
--- a/Roles/Standard/Impostor/Concealing/Anonymous.cs
+++ b/Roles/Standard/Impostor/Concealing/Anonymous.cs
@@ -83,7 +83,7 @@ public class Anonymous : RoleBase
{
if (!shapeshifting || pc.GetAbilityUseLimit() < 1 || ssTarget == null || ssTarget.Is(CustomRoles.LazyGuy) || ssTarget.Is(CustomRoles.Lazy) || Thanos.IsImmune(ssTarget)) return false;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
var targetId = byte.MaxValue;
diff --git a/Roles/Standard/Impostor/Concealing/Fabricator.cs b/Roles/Standard/Impostor/Concealing/Fabricator.cs
index 32a6350e0..bbe484870 100644
--- a/Roles/Standard/Impostor/Concealing/Fabricator.cs
+++ b/Roles/Standard/Impostor/Concealing/Fabricator.cs
@@ -37,6 +37,6 @@ public class Fabricator : RoleBase
if (NextDeathReason == PlayerState.DeathReason.Kill || killer.GetAbilityUseLimit() < 1 || !Main.PlayerStates.TryGetValue(target.PlayerId, out var state)) return;
state.deathReason = NextDeathReason;
RPC.SendDeathReason(target.PlayerId, state.deathReason, state.IsDead);
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Impostor/Concealing/Swooper.cs b/Roles/Standard/Impostor/Concealing/Swooper.cs
index 45f94b760..e152f0df9 100644
--- a/Roles/Standard/Impostor/Concealing/Swooper.cs
+++ b/Roles/Standard/Impostor/Concealing/Swooper.cs
@@ -210,7 +210,7 @@ public class Swooper : RoleBase
StartInvisTimer(pc, ventId);
- if (!wraith) pc.RpcRemoveAbilityUse();
+ if (!wraith) pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(GetString("SwooperInvisState"), Duration);
return true;
@@ -253,7 +253,7 @@ public class Swooper : RoleBase
StartInvisTimer(pc);
- if (!wraith) pc.RpcRemoveAbilityUse();
+ if (!wraith) pc.RpcRemoveAbilityUse(notify: false);
pc.Notify(GetString("SwooperInvisState"), Duration);
}
diff --git a/Roles/Standard/Impostor/Killing/Cantankerous.cs b/Roles/Standard/Impostor/Killing/Cantankerous.cs
index 10e6838eb..127ba57a0 100644
--- a/Roles/Standard/Impostor/Killing/Cantankerous.cs
+++ b/Roles/Standard/Impostor/Killing/Cantankerous.cs
@@ -75,6 +75,6 @@ public class Cantankerous : RoleBase
public override void OnMurder(PlayerControl killer, PlayerControl target)
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Impostor/Killing/Kamikaze.cs b/Roles/Standard/Impostor/Killing/Kamikaze.cs
index 2d6d3cd60..193963520 100644
--- a/Roles/Standard/Impostor/Killing/Kamikaze.cs
+++ b/Roles/Standard/Impostor/Killing/Kamikaze.cs
@@ -61,8 +61,6 @@ internal class Kamikaze : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
- if (killer == null || target == null) return false;
-
if (killer.GetAbilityUseLimit() < 1) return true;
return killer.CheckDoubleTrigger(target, () =>
diff --git a/Roles/Standard/Impostor/Killing/Witch.cs b/Roles/Standard/Impostor/Killing/Witch.cs
index 4d0751130..69e858433 100644
--- a/Roles/Standard/Impostor/Killing/Witch.cs
+++ b/Roles/Standard/Impostor/Killing/Witch.cs
@@ -181,7 +181,7 @@ public class Witch : RoleBase
SendRPC(true, killer.PlayerId, target.PlayerId);
killer.SetKillCooldown(SpellCooldown.GetFloat());
killer.RPCPlayCustomSound("Curse");
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true);
}
}
diff --git a/Roles/Standard/Impostor/Miscellaneous/Consigliere.cs b/Roles/Standard/Impostor/Miscellaneous/Consigliere.cs
index 94b578d58..27cd6cd11 100644
--- a/Roles/Standard/Impostor/Miscellaneous/Consigliere.cs
+++ b/Roles/Standard/Impostor/Miscellaneous/Consigliere.cs
@@ -118,7 +118,7 @@ public class Consigliere : RoleBase
{
if (!IsReveal(target.PlayerId))
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
RevealTarget.Add(target.PlayerId);
Logger.Info($"{killer.GetNameWithRole()}: Reveal target → {target.GetNameWithRole()} || Remaining: {killer.GetAbilityUseLimit()} uses", "Consigliere");
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
diff --git a/Roles/Standard/Impostor/Miscellaneous/Obstructer.cs b/Roles/Standard/Impostor/Miscellaneous/Obstructer.cs
index 25859cc36..f2eb013aa 100644
--- a/Roles/Standard/Impostor/Miscellaneous/Obstructer.cs
+++ b/Roles/Standard/Impostor/Miscellaneous/Obstructer.cs
@@ -59,8 +59,10 @@ public class Obstructer : RoleBase
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls())
{
- if (!Main.AllPlayerSpeed.ContainsKey(pc.PlayerId)) continue;
- Main.AllPlayerSpeed[pc.PlayerId] -= SpeedReductionPerSecond.GetFloat();
+ if (!Main.AllPlayerSpeed.TryGetValue(pc.PlayerId, out float speed)) continue;
+ float reduction = SpeedReductionPerSecond.GetFloat();
+ if (speed - reduction <= Main.MinSpeed) continue;
+ Main.AllPlayerSpeed[pc.PlayerId] -= reduction;
pc.MarkDirtySettings();
}
} while (Utils.IsAnySabotageActive());
diff --git a/Roles/Standard/Impostor/Miscellaneous/Visionary.cs b/Roles/Standard/Impostor/Miscellaneous/Visionary.cs
index dde3f11b0..8affc556d 100644
--- a/Roles/Standard/Impostor/Miscellaneous/Visionary.cs
+++ b/Roles/Standard/Impostor/Miscellaneous/Visionary.cs
@@ -72,7 +72,7 @@ internal class Visionary : RoleBase
RevealedPlayerIds.Add(target.PlayerId);
Utils.SendRPC(CustomRPC.SyncRoleData, shapeshifter.PlayerId, target.PlayerId);
- shapeshifter.RpcRemoveAbilityUse();
+ shapeshifter.RpcRemoveAbilityUse(notify: false);
Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: target);
return false;
diff --git a/Roles/Standard/Impostor/Support/Blackmailer.cs b/Roles/Standard/Impostor/Support/Blackmailer.cs
index 9d8cd798f..90fb302f1 100644
--- a/Roles/Standard/Impostor/Support/Blackmailer.cs
+++ b/Roles/Standard/Impostor/Support/Blackmailer.cs
@@ -54,9 +54,9 @@ internal class Blackmailer : RoleBase
return killer.CheckDoubleTrigger(target, () =>
{
BlackmailedPlayerIds.Add(target.PlayerId);
+ killer.RpcRemoveAbilityUse(notify: false);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
killer.SetKillCooldown(3f);
- killer.RpcRemoveAbilityUse();
NumBlackmailedThisRound++;
});
}
diff --git a/Roles/Standard/Impostor/Support/Camouflager.cs b/Roles/Standard/Impostor/Support/Camouflager.cs
index f7cba6f65..40e217a02 100644
--- a/Roles/Standard/Impostor/Support/Camouflager.cs
+++ b/Roles/Standard/Impostor/Support/Camouflager.cs
@@ -84,7 +84,7 @@ public class Camouflager : RoleBase
return true;
}
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
IsActive = true;
Camouflage.CheckCamouflage();
@@ -94,7 +94,7 @@ public class Camouflager : RoleBase
public override bool OnVanish(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1) return false;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
IsActive = true;
Camouflage.CheckCamouflage();
diff --git a/Roles/Standard/Impostor/Support/Consort.cs b/Roles/Standard/Impostor/Support/Consort.cs
index 02430d20f..2ba97a1a6 100644
--- a/Roles/Standard/Impostor/Support/Consort.cs
+++ b/Roles/Standard/Impostor/Support/Consort.cs
@@ -49,13 +49,11 @@ public class Consort : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
- if (!IsEnable || killer == null || target == null) return false;
-
if (killer.GetAbilityUseLimit() <= 0 || !killer.Is(CustomRoles.Consort)) return true;
return killer.CheckDoubleTrigger(target, () =>
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
target.BlockRole(Duration.GetFloat());
killer.Notify(GetString("EscortTargetHacked"));
killer.SetKillCooldown(CD.GetFloat());
diff --git a/Roles/Standard/Impostor/Support/EvilEraser.cs b/Roles/Standard/Impostor/Support/EvilEraser.cs
index 46f95582e..eacd698da 100644
--- a/Roles/Standard/Impostor/Support/EvilEraser.cs
+++ b/Roles/Standard/Impostor/Support/EvilEraser.cs
@@ -88,7 +88,7 @@ internal class EvilEraser : RoleBase
{
return killer.CheckDoubleTrigger(target, () =>
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.SetKillCooldown();
killer.Notify(GetString("TargetErasedInRound"));
if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId);
diff --git a/Roles/Standard/Impostor/Support/Framer.cs b/Roles/Standard/Impostor/Support/Framer.cs
index 3798bb494..efedd6c61 100644
--- a/Roles/Standard/Impostor/Support/Framer.cs
+++ b/Roles/Standard/Impostor/Support/Framer.cs
@@ -55,8 +55,8 @@ public class Framer : RoleBase
{
FramedPlayers.Add(target.PlayerId);
killer.SetKillCooldown();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.Notify(string.Format(Translator.GetString("Framer.TargetFramedNotify"), target.PlayerId.ColoredPlayerName()));
- killer.RpcRemoveAbilityUse();
});
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Impostor/Support/Gangster.cs b/Roles/Standard/Impostor/Support/Gangster.cs
index 53f21ccce..82a3b2e45 100644
--- a/Roles/Standard/Impostor/Support/Gangster.cs
+++ b/Roles/Standard/Impostor/Support/Gangster.cs
@@ -81,7 +81,7 @@ public class Gangster : RoleBase
{
if (!killer.GetCustomSubRoles().FindFirst(x => x.IsConverted(), out CustomRoles convertedAddon)) convertedAddon = CustomRoles.Madmate;
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
target.RpcSetCustomRole(convertedAddon);
var sender = CustomRpcSender.Create("Gangster.OnCheckMurder", SendOption.Reliable);
diff --git a/Roles/Standard/Impostor/Support/Gatekeeper.cs b/Roles/Standard/Impostor/Support/Gatekeeper.cs
index fe7dd314c..749e1aae1 100644
--- a/Roles/Standard/Impostor/Support/Gatekeeper.cs
+++ b/Roles/Standard/Impostor/Support/Gatekeeper.cs
@@ -82,7 +82,7 @@ public class Gatekeeper : RoleBase
if (room && MarkedRooms.TryAdd(room.RoomId, -1))
{
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
if (MarkedRooms.Count > MaxRoomsMarkedAtOnce.GetInt())
MarkedRooms.Remove(MarkedRooms.Keys.First());
diff --git a/Roles/Standard/Impostor/Support/Hypnotist.cs b/Roles/Standard/Impostor/Support/Hypnotist.cs
index 29912fc38..d20aa1252 100644
--- a/Roles/Standard/Impostor/Support/Hypnotist.cs
+++ b/Roles/Standard/Impostor/Support/Hypnotist.cs
@@ -80,7 +80,7 @@ public class Hypnotist : RoleBase
public override void OnPet(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Timer = new CountdownTimer(AbilityDuration.GetInt(), () =>
{
diff --git a/Roles/Standard/Impostor/Support/Nullifier.cs b/Roles/Standard/Impostor/Support/Nullifier.cs
index e6a3eed55..91ba4aed7 100644
--- a/Roles/Standard/Impostor/Support/Nullifier.cs
+++ b/Roles/Standard/Impostor/Support/Nullifier.cs
@@ -88,7 +88,7 @@ internal class Nullifier : RoleBase
Vigilante.SendRPC(target.PlayerId);
break;
default:
- target.RpcRemoveAbilityUse();
+ target.RpcRemoveAbilityUse(notify: false);
break;
}
diff --git a/Roles/Standard/Impostor/Support/Occultist.cs b/Roles/Standard/Impostor/Support/Occultist.cs
index 14a7f2770..351d3ea07 100644
--- a/Roles/Standard/Impostor/Support/Occultist.cs
+++ b/Roles/Standard/Impostor/Support/Occultist.cs
@@ -121,7 +121,7 @@ public class Occultist : RoleBase
OccultistPC.Notify(string.Format(Translator.GetString("OccultistRevived"), target.PlayerId.ColoredPlayerName()));
}, onCanceled: () => Revives.Remove(target.PlayerId));
- reporter.RpcRemoveAbilityUse();
+ reporter.RpcRemoveAbilityUse(notify: false);
reporter.Notify(string.Format(Translator.GetString("OccultistReviving"), target.PlayerId.ColoredPlayerName()), ReviveTime.GetInt());
return false;
diff --git a/Roles/Standard/Neutral/Benign/Backstabber.cs b/Roles/Standard/Neutral/Benign/Backstabber.cs
index 27624cf6c..26cfa904c 100644
--- a/Roles/Standard/Neutral/Benign/Backstabber.cs
+++ b/Roles/Standard/Neutral/Benign/Backstabber.cs
@@ -82,7 +82,7 @@ public class Backstabber : RoleBase
if (targetTeam == EHR.Team.Crewmate && killer.AmOwner)
Achievements.Type.StabbingTheBack.Complete();
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.Notify(string.Format(Translator.GetString("Backstabber.MurderNotify"), Utils.ColorString(targetTeam.GetColor(), Translator.GetString(targetTeam.ToString())), Translator.GetString($"BackstabberTeam.{Team}")), 10f);
}
diff --git a/Roles/Standard/Neutral/Benign/Investor.cs b/Roles/Standard/Neutral/Benign/Investor.cs
index 6f85c09c1..845040f94 100644
--- a/Roles/Standard/Neutral/Benign/Investor.cs
+++ b/Roles/Standard/Neutral/Benign/Investor.cs
@@ -63,7 +63,7 @@ public class Investor : RoleBase
{
if (MarkedPlayers.Add(target.PlayerId))
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, target.PlayerId);
killer.SetKillCooldown(AbilityCooldown.GetInt());
diff --git a/Roles/Standard/Neutral/Benign/Pursuer.cs b/Roles/Standard/Neutral/Benign/Pursuer.cs
index 58ae44b83..1327fb2dd 100644
--- a/Roles/Standard/Neutral/Benign/Pursuer.cs
+++ b/Roles/Standard/Neutral/Benign/Pursuer.cs
@@ -79,7 +79,7 @@ public class Pursuer : RoleBase
{
if (!pc || !target || !pc.Is(CustomRoles.Pursuer)) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
clientList.Add(target.PlayerId);
NotActiveList.Add(pc.PlayerId);
pc.SetKillCooldown();
diff --git a/Roles/Standard/Neutral/Evil/Gaslighter.cs b/Roles/Standard/Neutral/Evil/Gaslighter.cs
index 901d0345f..0087778cb 100644
--- a/Roles/Standard/Neutral/Evil/Gaslighter.cs
+++ b/Roles/Standard/Neutral/Evil/Gaslighter.cs
@@ -206,9 +206,9 @@ public class Gaslighter : RoleBase
target.RpcGuardAndKill(killer);
target.RpcGuardAndKill(target);
target.Notify(CustomRoles.Monarch.ColoredTextByRole(Translator.GetString("KnightedByMonarch")));
- Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.SetKillCooldown();
+ Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
break;
case Round.Curse:
killer.RPCPlayCustomSound("Curse");
@@ -221,10 +221,10 @@ public class Gaslighter : RoleBase
killer.RPCPlayCustomSound("Shield");
target.RPCPlayCustomSound("Shield");
ShieldedPlayers.Add(target.PlayerId);
+ killer.RpcRemoveAbilityUse(notify: false);
+ killer.SetKillCooldown();
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
- killer.RpcRemoveAbilityUse();
- killer.SetKillCooldown();
break;
}
diff --git a/Roles/Standard/Neutral/Evil/Vulture.cs b/Roles/Standard/Neutral/Evil/Vulture.cs
index a92c777f4..f30adfd79 100644
--- a/Roles/Standard/Neutral/Evil/Vulture.cs
+++ b/Roles/Standard/Neutral/Evil/Vulture.cs
@@ -159,7 +159,7 @@ public class Vulture : RoleBase
return false;
}
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
CooldownTimer = new CountdownTimer(VultureReportCD.GetInt(), () =>
{
CooldownTimer = null;
diff --git a/Roles/Standard/Neutral/Killing/Blockade.cs b/Roles/Standard/Neutral/Killing/Blockade.cs
index b90c932cb..d44ec93d3 100644
--- a/Roles/Standard/Neutral/Killing/Blockade.cs
+++ b/Roles/Standard/Neutral/Killing/Blockade.cs
@@ -89,7 +89,7 @@ public class Blockade : RoleBase
void PlaceBlockade(PlayerControl pc)
{
if (pc.GetAbilityUseLimit() < 1f) return;
- pc.RpcRemoveAbilityUse();
+ pc.RpcRemoveAbilityUse(notify: false);
Blockades.Add(pc.Pos());
pc.Notify(Translator.GetString("MarkDone"));
diff --git a/Roles/Standard/Neutral/Killing/Cherokious.cs b/Roles/Standard/Neutral/Killing/Cherokious.cs
index b14579900..906c66e82 100644
--- a/Roles/Standard/Neutral/Killing/Cherokious.cs
+++ b/Roles/Standard/Neutral/Killing/Cherokious.cs
@@ -52,8 +52,9 @@ internal class Cherokious : RoleBase
if (pc.GetAbilityUseLimit() < 1) return;
PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc);
+ if (!target) return;
if (target.Is(CustomRoles.Spy) && !Spy.OnKillAttempt(pc, target)) return;
- if (target != null && pc.RpcCheckAndMurder(target)) pc.RpcRemoveAbilityUse();
+ if (pc.RpcCheckAndMurder(target)) pc.RpcRemoveAbilityUse();
}
public override bool CanUseKillButton(PlayerControl pc)
diff --git a/Roles/Standard/Neutral/Killing/Cultist.cs b/Roles/Standard/Neutral/Killing/Cultist.cs
index 644581272..1bdba2aa4 100644
--- a/Roles/Standard/Neutral/Killing/Cultist.cs
+++ b/Roles/Standard/Neutral/Killing/Cultist.cs
@@ -104,7 +104,7 @@ public class Cultist : RoleBase
if (CanBeCharmed(target))
{
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
target.RpcSetCustomRole(CustomRoles.Charmed);
diff --git a/Roles/Standard/Neutral/Killing/Jackal.cs b/Roles/Standard/Neutral/Killing/Jackal.cs
index 56f552253..f5876f167 100644
--- a/Roles/Standard/Neutral/Killing/Jackal.cs
+++ b/Roles/Standard/Neutral/Killing/Jackal.cs
@@ -164,7 +164,7 @@ public class Jackal : RoleBase
var sender = CustomRpcSender.Create("Jackal.OnCheckMurder", SendOption.Reliable);
var hasValue = false;
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
target.RpcSetCustomRole(CustomRoles.Sidekick);
target.RpcChangeRoleBasis(CustomRoles.Sidekick);
SidekickId = target.PlayerId;
diff --git a/Roles/Standard/Neutral/Killing/Quarry.cs b/Roles/Standard/Neutral/Killing/Quarry.cs
index e10739be6..4d9824907 100644
--- a/Roles/Standard/Neutral/Killing/Quarry.cs
+++ b/Roles/Standard/Neutral/Killing/Quarry.cs
@@ -120,7 +120,7 @@ public class Quarry : RoleBase
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
}, onTick: () =>
{
- if (target == null || !target.IsAlive())
+ if (!target || !target.IsAlive())
{
TargetId = byte.MaxValue;
SeekTimer = null;
diff --git a/Roles/Standard/Neutral/Killing/Sprayer.cs b/Roles/Standard/Neutral/Killing/Sprayer.cs
index 4eb0b5549..1d9ac9cf0 100644
--- a/Roles/Standard/Neutral/Killing/Sprayer.cs
+++ b/Roles/Standard/Neutral/Killing/Sprayer.cs
@@ -141,7 +141,7 @@ internal class Sprayer : RoleBase
Vector2 pos = SprayerPC.Pos();
Traps[pos] = new(pos, SprayerPC);
- SprayerPC.RpcRemoveAbilityUse();
+ SprayerPC.RpcRemoveAbilityUse(notify: false);
if (SprayerId.GetAbilityUseLimit() > 0) SprayerPC.AddAbilityCD(CD.GetInt());
diff --git a/Roles/Standard/Neutral/Killing/Virus.cs b/Roles/Standard/Neutral/Killing/Virus.cs
index 3df7f9e08..bc132fdb8 100644
--- a/Roles/Standard/Neutral/Killing/Virus.cs
+++ b/Roles/Standard/Neutral/Killing/Virus.cs
@@ -121,7 +121,7 @@ public class Virus : RoleBase
if (!CanBeInfected(target)) return;
byte id = PlayerIdList[0];
- Utils.GetPlayerById(id).RpcRemoveAbilityUse();
+ Utils.GetPlayerById(id)?.RpcRemoveAbilityUse();
if (KillInfectedPlayerAfterMeeting.GetBool())
{
diff --git a/Roles/Standard/Neutral/Pariah/Spirit.cs b/Roles/Standard/Neutral/Pariah/Spirit.cs
index 34648c20a..085d5a131 100644
--- a/Roles/Standard/Neutral/Pariah/Spirit.cs
+++ b/Roles/Standard/Neutral/Pariah/Spirit.cs
@@ -75,7 +75,7 @@ public class Spirit : RoleBase
break;
}
- shapeshifter.RpcRemoveAbilityUse();
+ shapeshifter.RpcRemoveAbilityUse(notify: false);
shapeshifter.SyncSettings();
target.Notify(string.Format(Translator.GetString("SpiritTarget"), CustomRoles.Spirit.ToColoredString()));
Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: target);
diff --git a/Roles/Standard/Neutral/Pariah/Starspawn.cs b/Roles/Standard/Neutral/Pariah/Starspawn.cs
index 560035248..38430fa4d 100644
--- a/Roles/Standard/Neutral/Pariah/Starspawn.cs
+++ b/Roles/Standard/Neutral/Pariah/Starspawn.cs
@@ -70,7 +70,7 @@ public class Starspawn : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (!IsolatedPlayers.Add(target.PlayerId)) return false;
- killer.RpcRemoveAbilityUse();
+ killer.RpcRemoveAbilityUse(notify: false);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, target.PlayerId);