diff --git a/Main.cs b/Main.cs
index 150dbb8e0..98ad22ac4 100644
--- a/Main.cs
+++ b/Main.cs
@@ -40,8 +40,8 @@ public class Main : BasePlugin
private const string DebugKeyHash = "c0fd562955ba56af3ae20d7ec9e64c664f0facecef4b3e366e109306adeae29d";
private const string DebugKeySalt = "59687b";
private const string PluginGuid = "com.gurge44.endlesshostroles";
- public const string PluginVersion = "7.2.2";
- public const string PluginDisplayVersion = "7.2.2";
+ public const string PluginVersion = "7.3.0";
+ public const string PluginDisplayVersion = "7.3.0";
public const bool TestBuild = false;
public const string NeutralColor = "#ffab1b";
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index 91a2b2aba..206663675 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -953,7 +953,7 @@ internal static class ExtendedPlayerControl
case SchrodingersCat cat when realKiller != null:
cat.OnCheckMurderAsTarget(realKiller, pc);
return;
- case Veteran when Veteran.VeteranInProtect.ContainsKey(pc.PlayerId):
+ case Veteran when Veteran.VeteranInProtect.Contains(pc.PlayerId):
case Pestilence:
return;
}
diff --git a/Modules/Extensions/CountdownTimer.cs b/Modules/Extensions/CountdownTimer.cs
index 2b46b58bb..1a2daeabf 100644
--- a/Modules/Extensions/CountdownTimer.cs
+++ b/Modules/Extensions/CountdownTimer.cs
@@ -17,8 +17,11 @@ public sealed class CountdownTimer : IDisposable
private event Action OnElapsed;
private event Action OnTick;
+ private event Action OnCanceled;
+
+ private readonly bool _hasTickEvent;
- public CountdownTimer(float durationSeconds, Action onElapsed = null, bool autoStart = true, bool cancelOnMeeting = true, bool cancelOnGameEnd = true, Action onTick = null)
+ public CountdownTimer(float durationSeconds, Action onElapsed = null, bool autoStart = true, bool cancelOnMeeting = true, bool cancelOnGameEnd = true, Action onTick = null, Action onCanceled = null)
{
if (durationSeconds <= 0f)
throw new ArgumentOutOfRangeException(nameof(durationSeconds));
@@ -31,10 +34,15 @@ public sealed class CountdownTimer : IDisposable
if (onElapsed != null)
OnElapsed += onElapsed;
+
+ _hasTickEvent = onTick != null;
- if (onTick != null)
+ if (_hasTickEvent)
OnTick += onTick;
+ if (onCanceled != null)
+ OnCanceled += onCanceled;
+
if (autoStart)
Start();
}
@@ -64,12 +72,13 @@ public sealed class CountdownTimer : IDisposable
_completed = false;
_stopwatch.Restart();
- _coroutine = Main.Instance.StartCoroutine(Run());
+ _coroutine = Main.Instance.StartCoroutine(_hasTickEvent ? Run() : RunWithoutTicks());
}
- public void Stop()
+ private IEnumerator RunWithoutTicks()
{
- Dispose();
+ yield return new WaitForSecondsRealtime(_durationSeconds);
+ Complete();
}
private IEnumerator Run()
@@ -78,12 +87,12 @@ public sealed class CountdownTimer : IDisposable
while (_stopwatch.Elapsed.TotalSeconds < _durationSeconds)
{
- if (IsCanceled()) break;
-
int remaining = (int)Math.Ceiling(Remaining.TotalSeconds);
if (lastRemaining != remaining)
{
+ if (IsCanceled()) break;
+
try { OnTick?.Invoke(); }
catch (Exception e) { Utils.ThrowException(e); }
@@ -99,7 +108,7 @@ public sealed class CountdownTimer : IDisposable
Complete();
}
- private bool IsCanceled()
+ public bool IsCanceled()
{
if (_cancelOnMeeting && (ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks)) return true;
return _cancelOnGameEnd && (GameStates.IsEnded || GameStates.IsLobby || !GameStates.InGame);
@@ -113,13 +122,12 @@ public sealed class CountdownTimer : IDisposable
_completed = true;
_stopwatch.Stop();
- try { OnTick?.Invoke(); }
- catch (Exception e) { Utils.ThrowException(e); }
-
try
{
if (!IsCanceled())
OnElapsed?.Invoke();
+ else
+ OnCanceled?.Invoke();
}
catch (Exception e)
{
@@ -142,5 +150,6 @@ public sealed class CountdownTimer : IDisposable
_stopwatch.Stop();
OnElapsed = null;
OnTick = null;
+ OnCanceled = null;
}
}
diff --git a/Modules/GameState.cs b/Modules/GameState.cs
index c5cd4c57d..6d5355ca3 100644
--- a/Modules/GameState.cs
+++ b/Modules/GameState.cs
@@ -435,20 +435,6 @@ public class TaskState
// Ability Use Gain with this task completed
if (alive && !Main.HasJustStarted)
{
- switch (player.GetCustomRole())
- {
- case CustomRoles.Hacker:
- if (!player.IsModdedClient() && Hacker.UseLimit.ContainsKey(player.PlayerId))
- Hacker.UseLimit[player.PlayerId] += Hacker.HackerAbilityUseGainWithEachTaskCompleted.GetFloat();
- else if (Hacker.UseLimitSeconds.ContainsKey(player.PlayerId))
- Hacker.UseLimitSeconds[player.PlayerId] += Hacker.HackerAbilityUseGainWithEachTaskCompleted.GetInt() * Hacker.ModdedClientAbilityUseSecondsMultiplier.GetInt();
-
- if (Hacker.UseLimitSeconds.ContainsKey(player.PlayerId))
- Hacker.SendRPC(player.PlayerId, Hacker.UseLimitSeconds[player.PlayerId]);
-
- break;
- }
-
float add = Utils.GetSettingNameAndValueForRole(player.GetCustomRole(), "AbilityUseGainWithEachTaskCompleted");
if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0)
diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs
index 09e6ca086..f73f332fd 100644
--- a/Modules/NameColorManager.cs
+++ b/Modules/NameColorManager.cs
@@ -162,7 +162,7 @@ public static class NameColorManager
CustomRoles.Glitch when target.IsRoleBlocked() => Main.RoleColors[seerRole],
CustomRoles.Slenderman when Slenderman.IsBlinded(target.PlayerId) => "000000",
CustomRoles.Aid when Aid.ShieldedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Aid],
- CustomRoles.Spy when Spy.SpyRedNameList.ContainsKey(target.PlayerId) => "#BA4A00",
+ CustomRoles.Spy when Spy.SpyRedNameList.Contains(target.PlayerId) => "#BA4A00",
CustomRoles.Mastermind when Mastermind.ManipulateDelays.ContainsKey(target.PlayerId) => "#00ffa5",
CustomRoles.Mastermind when Mastermind.ManipulatedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Arsonist],
CustomRoles.Hitman when (seerRoleClass as Hitman)?.TargetId == target.PlayerId => "000000",
diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs
index 5c3305e63..65ccc2f5a 100644
--- a/Modules/OptionHolder.cs
+++ b/Modules/OptionHolder.cs
@@ -1031,8 +1031,8 @@ public static class Options
{
var sb = new StringBuilder();
- sb.AppendLine("| Command | Description | Arguments | Usage Level | Usage Time | Hidden |");
- sb.AppendLine("|---------|-------------|-----------|-------------|------------|--------|");
+ sb.AppendLine("| Command | Description | Arguments | Usage Level | Usage Time | 'cmd' prefix
required |");
+ sb.AppendLine("|---------|-------------|-----------|-------------|------------|-------------|");
foreach (Command command in Command.AllCommands)
{
diff --git a/Modules/RPC.cs b/Modules/RPC.cs
index a6dc34e8e..263a2eb2c 100644
--- a/Modules/RPC.cs
+++ b/Modules/RPC.cs
@@ -148,7 +148,6 @@ public enum CustomRPC
*/
SetTrackerTarget = 215,
- SetAlchemistTimer,
SyncPostman,
SyncChangeling,
SyncTiger,
@@ -166,7 +165,6 @@ public enum CustomRPC
Exclusionary,
Deadlined,
Blessed,
- CountdownTimer,
// Game Modes
RoomRushDataSync,
@@ -1120,12 +1118,6 @@ internal static class RPCHandlerPatch
{
byte id = reader.ReadByte();
(Main.PlayerStates[id].Role as Swooper)?.ReceiveRPC(reader);
- }
-
- break;
- case CustomRPC.SetAlchemistTimer:
- {
- Alchemist.ReceiveRPC(reader);
break;
}
case CustomRPC.SetBkTimer:
diff --git a/Modules/StateMachineWrapper.cs b/Modules/StateMachineWrapper.cs
index fd3859235..0674cd1bc 100644
--- a/Modules/StateMachineWrapper.cs
+++ b/Modules/StateMachineWrapper.cs
@@ -3,6 +3,7 @@
using System;
using System.Reflection;
using HarmonyLib;
+#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
namespace EHR.Modules;
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index ed49ddcba..fd8d50073 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -3923,7 +3923,7 @@ public static class Utils
switch (target.GetCustomRole())
{
- case CustomRoles.Veteran when target.AmOwner && Veteran.VeteranInProtect.ContainsKey(target.PlayerId):
+ case CustomRoles.Veteran when target.AmOwner && Veteran.VeteranInProtect.Contains(target.PlayerId):
Achievements.Type.BadEncounter.Complete();
break;
case CustomRoles.Catalyst when Catalyst.RemoveGivenAddonsAfterDeath.GetBool():
@@ -4014,6 +4014,7 @@ public static class Utils
Reaper.OnAnyoneDead(target);
Wyrd.OnAnyoneDeath(target);
Thanos.OnDeath(targetRealKiller, target, disconnect);
+ CovenMember.OnAnyoneDead();
if (!onMeeting && !disconnect)
{
diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs
index 917e94c5d..19846cceb 100644
--- a/Patches/HudPatch.cs
+++ b/Patches/HudPatch.cs
@@ -1040,27 +1040,48 @@ internal static class TaskPanelBehaviourPatch
if (subRoles.Count > 0)
{
const int max = 3;
- var roles = subRoles.Take(max);
finalTextBuilder.Append("");
- var innerSb = new StringBuilder();
- foreach (var cr in roles)
+
+ int taken = 0;
+
+ foreach (var subRole in subRoles)
{
+ if (taken++ >= max) break;
+
+ StringBuilder innerSb = new StringBuilder();
innerSb.Append("\r\n\r\n");
- innerSb.Append(cr.ToColoredString());
+ innerSb.Append(subRole.ToColoredString());
innerSb.Append(":\r\n");
- innerSb.Append(GetString($"{cr}Info"));
- finalTextBuilder.Append(Utils.ColorString(Utils.GetRoleColor(cr), innerSb.ToString()));
+ innerSb.Append(GetString($"{subRole}Info"));
+
+ finalTextBuilder.Append(Utils.ColorString(Utils.GetRoleColor(subRole), innerSb.ToString()));
}
+
finalTextBuilder.Append("");
+
int chunk = subRoles.Any(x => GetString(x.ToString()).Contains(' ')) ? 3 : 4;
if (subRoles.Count > max)
{
finalTextBuilder.Append("\r\n....\r\n(");
- foreach (var cr in subRoles.Skip(max).Chunk(chunk))
+
+ bool firstChunk = true;
+
+ foreach (var group in subRoles.Skip(max).Chunk(chunk))
{
- finalTextBuilder.Append(string.Join(", ", cr.Select(r => r.ToColoredString())));
+ if (!firstChunk) finalTextBuilder.Append(",\r\n");
+ firstChunk = false;
+
+ bool first = true;
+
+ foreach (var groupRole in group)
+ {
+ if (!first) finalTextBuilder.Append(", ");
+ first = false;
+ finalTextBuilder.Append(groupRole.ToColoredString());
+ }
}
+
finalTextBuilder.Append(")");
}
}
@@ -1076,15 +1097,15 @@ internal static class TaskPanelBehaviourPatch
finalTextBuilder.Append("\r\n\r\n");
finalTextBuilder.Append(GetString("PVP.ATK"));
finalTextBuilder.Append(": ");
- finalTextBuilder.AppendFormat("{0:N1}", SoloPVP.PlayerATK[lpc.PlayerId]);
+ finalTextBuilder.Append($"{SoloPVP.PlayerATK[lpc.PlayerId]:N1}");
finalTextBuilder.Append("\r\n");
finalTextBuilder.Append(GetString("PVP.DF"));
finalTextBuilder.Append(": ");
- finalTextBuilder.AppendFormat("{0:N1}", SoloPVP.PlayerDF[lpc.PlayerId]);
+ finalTextBuilder.Append($"{SoloPVP.PlayerDF[lpc.PlayerId]:N1}");
finalTextBuilder.Append("\r\n");
finalTextBuilder.Append(GetString("PVP.RCO"));
finalTextBuilder.Append(": ");
- finalTextBuilder.AppendFormat("{0:N1}", SoloPVP.PlayerHPReco[lpc.PlayerId]);
+ finalTextBuilder.Append($"{SoloPVP.PlayerHPReco[lpc.PlayerId]:N1}");
finalTextBuilder.Append("\r\n");
finalTextBuilder.Append("");
diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs
index d101c5ca5..59f4d168c 100644
--- a/Patches/PlayerControlPatch.cs
+++ b/Patches/PlayerControlPatch.cs
@@ -417,6 +417,12 @@ internal static class CheckMurderPatch
Notify("TiredReachedMaxKillsPerRound");
return false;
}
+
+ if (!Summoner.OnAnyoneCheckMurder(killer, target))
+ {
+ Notify("SummonerCantKillCoven");
+ return false;
+ }
if ((Romantic.PartnerId == target.PlayerId && Romantic.IsPartnerProtected) ||
Medic.OnAnyoneCheckMurder(killer, target) ||
@@ -693,7 +699,11 @@ internal static class MurderPlayerPatch
if (target.Is(CustomRoles.Stained)) Stained.OnDeath(target, killer);
- Witness.AllKillers[killer.PlayerId] = TimeStamp;
+ {
+ Witness.AllKillers.Add(killer.PlayerId);
+ PlayerControl killer1 = killer;
+ LateTask.New(() => Witness.AllKillers.Remove(killer1.PlayerId), Options.WitnessTime.GetInt(), log: false);
+ }
killer.AddKillTimerToDict();
diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc
index de2f3a5b0..9ddbf57b1 100644
--- a/Resources/Lang/en_US.jsonc
+++ b/Resources/Lang/en_US.jsonc
@@ -1560,8 +1560,6 @@
"WitnessInfo": "Find out if someone killed recently",
"WitnessInfoLong": "(Crewmates):\\nWhen you use your kill button on someone, it tells you if the player killed recently or not. The yellow exclamation mark means they've killed, and the green chech mark means they've not.",
"WitnessButtonText": "Examine",
- "WitnessFoundInnocent": "✓",
- "WitnessFoundKiller": "⚠",
"WitnessTime": "Max Time after killing where killer appears red",
"Wizard": "Wizard",
"WizardInfo": "Change Players' Stats",
@@ -3380,6 +3378,7 @@
"Summoner.SelfSuffix": "{0} has {1}s to kill",
"Summoner.SummonedPlayerSuffix": "<#ffff00>{0}, the {1} summoned you to kill for them!\\nYou have {2}s",
"Summoner.SummonSuccessMessage": "<#00ff00>✓ <#00bb00>{0} will be summoned after the meeting to kill for you",
+ "SummonerCantKillCoven": "As a summoned player, you kill for the Coven, meaning you can't kill Coven members",
"Timelord": "Timelord",
"TimelordInfo": "Refresh or reset cooldowns; kill",
"TimelordInfoLong": "(Coven):\\nUse your ability on a Coven member to refresh their cooldown (set it to 0). Use your ability on a non-Coven member to reset their cooldown. When enhanced by the Necronomicon, you can kill and also reduce meeting time upon making a kill.",
diff --git a/Roles/HideAndSeek/Dasher.cs b/Roles/HideAndSeek/Dasher.cs
index adb4e7bdd..498b465d4 100644
--- a/Roles/HideAndSeek/Dasher.cs
+++ b/Roles/HideAndSeek/Dasher.cs
@@ -96,19 +96,6 @@ public class Dasher : RoleBase, IHideAndSeekRole
}
}
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!DashStatus.IsDashing) return;
-
- long now = Utils.TimeStamp;
-
- if (DashStatus.DashEndTime <= now)
- {
- DashStatus.IsDashing = false;
- pc.MarkDirtySettings();
- }
- }
-
public override bool OnVanish(PlayerControl pc)
{
Dash(pc);
@@ -127,7 +114,11 @@ public class Dasher : RoleBase, IHideAndSeekRole
if (DashStatus.IsDashing || pc.GetAbilityUseLimit() < 1f) return;
DashStatus.IsDashing = true;
- DashStatus.DashEndTime = Utils.TimeStamp + DashStatus.Duration;
+ DashStatus.Timer = new(DashStatus.Duration, () =>
+ {
+ DashStatus.IsDashing = false;
+ pc.MarkDirtySettings();
+ });
pc.MarkDirtySettings();
pc.RpcRemoveAbilityUse();
}
diff --git a/Roles/HideAndSeek/Detector.cs b/Roles/HideAndSeek/Detector.cs
index f8a1dd27a..3cda9c4ed 100644
--- a/Roles/HideAndSeek/Detector.cs
+++ b/Roles/HideAndSeek/Detector.cs
@@ -1,5 +1,6 @@
using System.Linq;
using EHR.Gamemodes;
+using EHR.Modules.Extensions;
namespace EHR.Roles;
@@ -7,11 +8,9 @@ public class Detector : RoleBase, IHideAndSeekRole
{
public static bool On;
- public static OptionItem InfoFrequency;
- public static OptionItem Vision;
- public static OptionItem Speed;
-
- private long LastInfoTime;
+ private static OptionItem InfoFrequency;
+ private static OptionItem Vision;
+ private static OptionItem Speed;
public override bool IsEnable => On;
public Team Team => Team.Crewmate;
@@ -36,7 +35,7 @@ public class Detector : RoleBase, IHideAndSeekRole
.SetColor(new(66, 221, 245, byte.MaxValue))
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]);
- InfoFrequency = new IntegerOptionItem(69_213_605, "DetectorFrequency", new(0, 60, 1), 20, TabGroup.CrewmateRoles)
+ InfoFrequency = new IntegerOptionItem(69_213_605, "DetectorFrequency", new(0, 60, 1), 15, TabGroup.CrewmateRoles)
.SetGameMode(CustomGameMode.HideAndSeek)
.SetValueFormat(OptionFormat.Seconds)
.SetColor(new(66, 221, 245, byte.MaxValue))
@@ -46,22 +45,15 @@ public class Detector : RoleBase, IHideAndSeekRole
public override void Add(byte playerId)
{
On = true;
- LastInfoTime = Utils.TimeStamp + 8 + InfoFrequency.GetInt();
- }
+ int infoFrequency = InfoFrequency.GetInt();
+ _ = new CountdownTimer(8 + infoFrequency, OnElapsed, cancelOnMeeting: false);
+ return;
- public override void Init()
- {
- On = false;
- }
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!pc.IsAlive()) return;
-
- long now = Utils.TimeStamp;
-
- if (LastInfoTime + InfoFrequency.GetInt() <= now)
+ void OnElapsed()
{
+ PlayerControl pc = Utils.GetPlayerById(playerId);
+ if (pc == null || !pc.IsAlive()) return;
+
PlayerControl[] imps = CustomHnS.PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.GetPlainShipRoom() != null).ToArray();
if (imps.Length > 0)
@@ -69,8 +61,14 @@ public class Detector : RoleBase, IHideAndSeekRole
PlayerControl imp = imps.RandomElement();
string room = Translator.GetString($"{imp.GetPlainShipRoom().RoomId}");
pc.Notify(string.Format(Translator.GetString("DetectorNotify"), room));
- LastInfoTime = now;
}
+
+ _ = new CountdownTimer(infoFrequency, OnElapsed, cancelOnMeeting: false);
}
}
+
+ public override void Init()
+ {
+ On = false;
+ }
}
\ No newline at end of file
diff --git a/Roles/HideAndSeek/IHideAndSeekRole.cs b/Roles/HideAndSeek/IHideAndSeekRole.cs
index 9c8c8329a..de0c0ac7b 100644
--- a/Roles/HideAndSeek/IHideAndSeekRole.cs
+++ b/Roles/HideAndSeek/IHideAndSeekRole.cs
@@ -1,4 +1,6 @@
-namespace EHR.Roles;
+using EHR.Modules.Extensions;
+
+namespace EHR.Roles;
internal interface IHideAndSeekRole
{
@@ -11,8 +13,8 @@ internal interface IHideAndSeekRole
public class DashStatus
{
- public long DashEndTime { get; set; } = Utils.TimeStamp;
- public bool IsDashing { get; set; } = false;
+ public CountdownTimer Timer { get; set; }
+ public bool IsDashing { get; set; }
public int Cooldown { get; init; } = 20;
public int Duration { get; init; } = 5;
}
\ No newline at end of file
diff --git a/Roles/HideAndSeek/Jet.cs b/Roles/HideAndSeek/Jet.cs
index 30b6a8e50..742103358 100644
--- a/Roles/HideAndSeek/Jet.cs
+++ b/Roles/HideAndSeek/Jet.cs
@@ -88,22 +88,13 @@ public class Jet : RoleBase, IHideAndSeekRole
{
if (pc.HasAbilityCD() || DashStatus.IsDashing || pc.GetAbilityUseLimit() < 1f) return;
DashStatus.IsDashing = true;
- DashStatus.DashEndTime = Utils.TimeStamp + DashStatus.Duration;
+ DashStatus.Timer = new(DashStatus.Duration, () =>
+ {
+ DashStatus.IsDashing = false;
+ pc.MarkDirtySettings();
+ });
pc.MarkDirtySettings();
pc.RpcRemoveAbilityUse();
pc.AddAbilityCD(DashStatus.Cooldown + DashStatus.Duration);
}
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!DashStatus.IsDashing) return;
-
- long now = Utils.TimeStamp;
-
- if (DashStatus.DashEndTime <= now)
- {
- DashStatus.IsDashing = false;
- pc.MarkDirtySettings();
- }
- }
}
\ No newline at end of file
diff --git a/Roles/HideAndSeek/Locator.cs b/Roles/HideAndSeek/Locator.cs
index fdd44e868..1840c494e 100644
--- a/Roles/HideAndSeek/Locator.cs
+++ b/Roles/HideAndSeek/Locator.cs
@@ -1,5 +1,6 @@
using System.Linq;
using EHR.Gamemodes;
+using EHR.Modules.Extensions;
namespace EHR.Roles;
@@ -7,14 +8,14 @@ public class Locator : RoleBase, IHideAndSeekRole
{
public static bool On;
- public static OptionItem ArrowFrequency;
- public static OptionItem ArrowDuration;
- public static OptionItem HidersKnowTheyAreLocated;
- public static OptionItem Vision;
- public static OptionItem Speed;
- private byte LocatorId;
+ private static OptionItem ArrowFrequency;
+ private static OptionItem ArrowDuration;
+ private static OptionItem HidersKnowTheyAreLocated;
+ private static OptionItem Vision;
+ private static OptionItem Speed;
- private LocateStatus Status;
+ private byte LocatorId;
+ private byte TargetId;
public override bool IsEnable => On;
public Team Team => Team.Impostor;
@@ -60,8 +61,43 @@ public class Locator : RoleBase, IHideAndSeekRole
public override void Add(byte playerId)
{
On = true;
- Status = new();
+ TargetId = byte.MaxValue;
+ int arrowDuration = ArrowDuration.GetInt();
+ int arrowFrequency = ArrowFrequency.GetInt();
+ var timer = new CountdownTimer(8 + arrowFrequency, OnElapsed, cancelOnMeeting: false);
LocatorId = playerId;
+ return;
+
+ void OnElapsed()
+ {
+ PlayerControl pc = Utils.GetPlayerById(playerId);
+ if (pc == null || !pc.IsAlive()) return;
+
+ PlayerControl target = CustomHnS.PlayerRoles.Where(x => x.Value.Interface.Team != Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.IsAlive()).Shuffle().FirstOrDefault();
+
+ if (target != null)
+ {
+ TargetId = target.PlayerId;
+ TargetArrow.Add(pc.PlayerId, target.PlayerId);
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ if (HidersKnowTheyAreLocated.GetBool()) target.Notify(Translator.GetString("LocatorNotify"));
+
+ timer = new CountdownTimer(arrowFrequency + arrowDuration, OnElapsed, cancelOnMeeting: false);
+
+ LateTask.New(() =>
+ {
+ if (timer.IsCanceled())
+ {
+ timer.Dispose();
+ return;
+ }
+
+ TargetArrow.Remove(pc.PlayerId, TargetId);
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ TargetId = byte.MaxValue;
+ }, arrowDuration);
+ }
+ }
}
public override void Init()
@@ -69,44 +105,9 @@ public class Locator : RoleBase, IHideAndSeekRole
On = false;
}
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!pc.IsAlive()) return;
-
- if (Status.TargetId == byte.MaxValue)
- {
- if (Status.LastArrowEndTime + ArrowFrequency.GetInt() < Utils.TimeStamp)
- {
- PlayerControl target = CustomHnS.PlayerRoles.Where(x => x.Value.Interface.Team != Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.IsAlive()).Shuffle().FirstOrDefault();
-
- if (target != null)
- {
- Status.TargetId = target.PlayerId;
- Status.LastArrowEndTime = Utils.TimeStamp + ArrowDuration.GetInt();
- TargetArrow.Add(pc.PlayerId, target.PlayerId);
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- if (HidersKnowTheyAreLocated.GetBool()) target.Notify(Translator.GetString("LocatorNotify"));
- }
- }
- }
- else if (Status.LastArrowEndTime < Utils.TimeStamp)
- {
- TargetArrow.Remove(pc.PlayerId, Status.TargetId);
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- Status.TargetId = byte.MaxValue;
- }
- }
-
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.PlayerId != target.PlayerId || hud || seer.PlayerId != LocatorId) return string.Empty;
-
- return Status.TargetId == byte.MaxValue ? string.Empty : TargetArrow.GetArrows(seer, Status.TargetId);
- }
-
- private class LocateStatus
- {
- public byte TargetId { get; set; } = byte.MaxValue;
- public long LastArrowEndTime { get; set; } = Utils.TimeStamp;
+ return TargetId == byte.MaxValue ? string.Empty : TargetArrow.GetArrows(seer, TargetId);
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Coven/CovenMember.cs b/Roles/Standard/Coven/CovenMember.cs
index 35e11c936..22bc5b5a1 100644
--- a/Roles/Standard/Coven/CovenMember.cs
+++ b/Roles/Standard/Coven/CovenMember.cs
@@ -1,23 +1,36 @@
-namespace EHR.Roles;
+using System.Collections.Generic;
+
+namespace EHR.Roles;
public class CovenMember : CovenBase
{
public static bool On;
+ private static List Instances = [];
protected override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Never;
public override bool IsEnable => On;
+ private byte CovenMemberId;
+
public override void SetupCustomOption() { }
public override void Init()
{
On = false;
+ Instances = [];
}
public override void Add(byte playerId)
{
On = true;
+ Instances.Add(this);
+ CovenMemberId = playerId;
+ }
+
+ public override void Remove(byte playerId)
+ {
+ Instances.Remove(this);
}
public override bool CanUseKillButton(PlayerControl pc)
@@ -25,14 +38,20 @@ public class CovenMember : CovenBase
return false;
}
- public override void OnFixedUpdate(PlayerControl pc)
+ public static void OnAnyoneDead()
{
- if (!CustomRoles.CovenLeader.RoleExist())
+ if (Instances.Count > 0 && !CustomRoles.CovenLeader.RoleExist())
{
- pc.RpcSetCustomRole(CustomRoles.CovenLeader);
- pc.RpcChangeRoleBasis(CustomRoles.CovenLeader);
- pc.SyncSettings();
- Utils.NotifyRoles(SpecifyTarget: pc);
+ foreach (CovenMember instance in Instances)
+ {
+ PlayerControl pc = Utils.GetPlayerById(instance.CovenMemberId);
+ if (pc == null || !pc.IsAlive()) continue;
+ pc.RpcSetCustomRole(CustomRoles.CovenLeader);
+ pc.RpcChangeRoleBasis(CustomRoles.CovenLeader);
+ pc.MarkDirtySettings();
+ Utils.NotifyRoles(SpecifyTarget: pc);
+ return;
+ }
}
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Coven/Goddess.cs b/Roles/Standard/Coven/Goddess.cs
index 1df4da0fb..0641c2ce2 100644
--- a/Roles/Standard/Coven/Goddess.cs
+++ b/Roles/Standard/Coven/Goddess.cs
@@ -1,6 +1,8 @@
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
+using Il2CppSystem;
namespace EHR.Roles;
@@ -13,10 +15,8 @@ public class Goddess : CovenBase
private static OptionItem CanVentBeforeNecronomicon;
private static OptionItem CanVentAfterNecronomicon;
- private long AbilityEndTS;
-
private byte GoddessId;
- private long LastNotifyTS;
+ private CountdownTimer Timer;
protected override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
@@ -39,9 +39,8 @@ public class Goddess : CovenBase
public override void Add(byte playerId)
{
On = true;
- AbilityEndTS = 0;
- LastNotifyTS = 0;
GoddessId = playerId;
+ Timer = null;
}
public override bool CanUseImpostorVentButton(PlayerControl pc)
@@ -56,8 +55,14 @@ public class Goddess : CovenBase
public override bool OnVanish(PlayerControl pc)
{
- AbilityEndTS = Utils.TimeStamp + AbilityDuration.GetInt();
- Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, AbilityEndTS);
+ Timer = new CountdownTimer(AbilityDuration.GetInt(), () =>
+ {
+ Timer = null;
+ pc.RpcResetAbilityCooldown();
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, false);
+ }, onTick: () => Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc), onCanceled: () => Timer = null);
+ Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, true);
return false;
}
@@ -67,38 +72,9 @@ public class Goddess : CovenBase
AURoleOptions.PhantomDuration = 0.1f;
}
- public override void OnReportDeadBody()
- {
- AbilityEndTS = 0;
- Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, AbilityEndTS);
- }
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (AbilityEndTS > 0)
- {
- long now = Utils.TimeStamp;
- bool notify = now != LastNotifyTS;
-
- if (now >= AbilityEndTS)
- {
- AbilityEndTS = 0;
- Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, AbilityEndTS);
- pc.RpcResetAbilityCooldown();
- notify = true;
- }
-
- if (notify)
- {
- LastNotifyTS = now;
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- }
- }
- }
-
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
- if (AbilityEndTS == 0 || killer.Is(CustomRoles.Pestilence) || !killer.IsAlive()) return true;
+ if (Timer == null || killer.Is(CustomRoles.Pestilence) || !killer.IsAlive()) return true;
killer.SetRealKiller(target);
target.Kill(killer);
@@ -107,12 +83,12 @@ public class Goddess : CovenBase
public void ReceiveRPC(MessageReader reader)
{
- AbilityEndTS = long.Parse(reader.ReadString());
+ Timer = reader.ReadBoolean() ? new CountdownTimer(AbilityDuration.GetInt(), onCanceled: () => Timer = null) : null;
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
- if (seer.PlayerId != GoddessId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || AbilityEndTS == 0) return string.Empty;
- return string.Format(Translator.GetString("Goddess.Suffix"), AbilityEndTS - Utils.TimeStamp, Main.CovenColor);
+ if (seer.PlayerId != GoddessId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || Timer == null) return string.Empty;
+ return string.Format(Translator.GetString("Goddess.Suffix"), (int)Math.Ceiling(Timer.Remaining.TotalSeconds), Main.CovenColor);
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Coven/PotionMaster.cs b/Roles/Standard/Coven/PotionMaster.cs
index 9552377cc..50c9504ed 100644
--- a/Roles/Standard/Coven/PotionMaster.cs
+++ b/Roles/Standard/Coven/PotionMaster.cs
@@ -1,6 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
@@ -19,7 +21,7 @@ public class PotionMaster : CovenBase
private byte PotionMasterId;
private HashSet RevealedPlayers = [];
- private Dictionary ShieldedPlayers = [];
+ private Dictionary ShieldedPlayers = [];
protected override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
@@ -69,9 +71,22 @@ public class PotionMaster : CovenBase
{
if (target.Is(Team.Coven))
{
- long shieldEndTS = Utils.TimeStamp + ShieldDuration.GetInt();
- ShieldedPlayers[target.PlayerId] = shieldEndTS;
- Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 1, target.PlayerId, shieldEndTS);
+ ShieldedPlayers[target.PlayerId] = new CountdownTimer(ShieldDuration.GetInt(), () =>
+ {
+ ShieldedPlayers.Remove(target.PlayerId);
+ Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
+ Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
+ Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 2, target.PlayerId);
+ }, onTick: () =>
+ {
+ Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
+ Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
+ }, onCanceled: () =>
+ {
+ ShieldedPlayers.Remove(target.PlayerId);
+ Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 2, target.PlayerId);
+ });
+ Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 1, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
@@ -97,30 +112,6 @@ public class PotionMaster : CovenBase
Main.AllPlayerKillCooldown[id] = HasNecronomicon ? KillCooldown.GetFloat() : AbilityCooldown.GetFloat();
}
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (GameStates.IsMeeting || ExileController.Instance || !Main.IntroDestroyed || ShieldedPlayers.Count == 0) return;
-
- long now = Utils.TimeStamp;
- List toRemove = ShieldedPlayers.Where(x => now >= x.Value).Select(x => x.Key).ToList();
-
- toRemove.ForEach(x =>
- {
- ShieldedPlayers.Remove(x);
- PlayerControl player = x.GetPlayer();
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player);
- Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
- Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 2, x);
- });
-
- foreach (byte shieldedId in ShieldedPlayers.Keys)
- {
- PlayerControl player = shieldedId.GetPlayer();
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player);
- Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
- }
- }
-
public override bool KnowRole(PlayerControl seer, PlayerControl target)
{
if (base.KnowRole(seer, target)) return true;
@@ -137,7 +128,7 @@ public class PotionMaster : CovenBase
switch (reader.ReadPackedInt32())
{
case 1:
- ShieldedPlayers[reader.ReadByte()] = long.Parse(reader.ReadString());
+ ShieldedPlayers[reader.ReadByte()] = new CountdownTimer(ShieldDuration.GetInt());
break;
case 2:
ShieldedPlayers.Remove(reader.ReadByte());
@@ -152,8 +143,8 @@ public class PotionMaster : CovenBase
{
if (seer.PlayerId == target.PlayerId && seer.IsModdedClient() && !hud) return string.Empty;
if (seer.PlayerId != target.PlayerId && seer.PlayerId != PotionMasterId) return string.Empty;
- if (!ShieldedPlayers.TryGetValue(target.PlayerId, out long shieldExpireTS)) return string.Empty;
+ if (!ShieldedPlayers.TryGetValue(target.PlayerId, out CountdownTimer timer)) return string.Empty;
- return string.Format(Translator.GetString("PotionMaster.Suffix"), shieldExpireTS - Utils.TimeStamp, Main.CovenColor);
+ return string.Format(Translator.GetString("PotionMaster.Suffix"), (int)Math.Ceiling(timer.Remaining.TotalSeconds), Main.CovenColor);
}
}
diff --git a/Roles/Standard/Coven/Summoner.cs b/Roles/Standard/Coven/Summoner.cs
index 9123d0bbc..7123921da 100644
--- a/Roles/Standard/Coven/Summoner.cs
+++ b/Roles/Standard/Coven/Summoner.cs
@@ -142,6 +142,17 @@ public class Summoner : CovenBase
ChatCommands.SummonCommand(pc, command, command.Split(' '));
}
+ public override bool KnowRole(PlayerControl seer, PlayerControl target)
+ {
+ if (base.KnowRole(seer, target)) return true;
+ return seer.PlayerId == SummonedPlayerId && target.Is(Team.Coven);
+ }
+
+ public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target)
+ {
+ return !Instances.Exists(x => x.SummonedPlayerId == killer.PlayerId && target.Is(Team.Coven));
+ }
+
public static void OnAnyoneMurder(PlayerControl killer)
{
foreach (Summoner instance in Instances)
diff --git a/Roles/Standard/Crewmate/Chaos/Car.cs b/Roles/Standard/Crewmate/Chaos/Car.cs
index be98ade94..a74e6060f 100644
--- a/Roles/Standard/Crewmate/Chaos/Car.cs
+++ b/Roles/Standard/Crewmate/Chaos/Car.cs
@@ -8,6 +8,7 @@ namespace EHR.Roles;
public class Car : RoleBase
{
public static bool On;
+ private static bool IsSkeld;
private static OptionItem PropelDistance;
@@ -30,6 +31,7 @@ public class Car : RoleBase
public override void Init()
{
On = false;
+ IsSkeld = Main.CurrentMap is MapNames.Skeld or MapNames.Dleks;
}
public override void Add(byte playerId)
@@ -105,6 +107,7 @@ public class Car : RoleBase
for (Vector2 newPos = target.Pos(); Vector2.Distance(pos, newPos) < distance && GameStates.IsInTask; newPos += addVector)
{
+ if (IsSkeld && (target.IsInRoom(SystemTypes.LowerEngine) || target.IsInRoom(SystemTypes.UpperEngine))) break;
if (PhysicsHelpers.AnythingBetween(collider, collider.bounds.center, newPos + (addVector * 2), Constants.ShipOnlyMask, false)) break;
target.TP(newPos, log: false);
diff --git a/Roles/Standard/Crewmate/Chaos/Dad.cs b/Roles/Standard/Crewmate/Chaos/Dad.cs
index d434cb389..7891c742b 100644
--- a/Roles/Standard/Crewmate/Chaos/Dad.cs
+++ b/Roles/Standard/Crewmate/Chaos/Dad.cs
@@ -322,7 +322,9 @@ public class Dad : RoleBase
Count = 0;
Vector2 pos = pc.Pos();
- if (UsingAbilities.Contains(Ability.Rage) && Main.EnumerateAlivePlayerControls().FindFirst(x => Vector2.Distance(pos, x.Pos()) < 1.3f, out PlayerControl target) && pc.RpcCheckAndMurder(target)) UsingAbilities.Remove(Ability.Rage);
+
+ if (UsingAbilities.Contains(Ability.Rage) && Main.EnumerateAlivePlayerControls().FindFirst(x => Vector2.Distance(pos, x.Pos()) < 1.3f, out PlayerControl target) && pc.RpcCheckAndMurder(target))
+ UsingAbilities.Remove(Ability.Rage);
bool notify = Vector2.Distance(pc.Pos(), Shop.transform.position) < 2f;
diff --git a/Roles/Standard/Crewmate/Chaos/Randomizer.cs b/Roles/Standard/Crewmate/Chaos/Randomizer.cs
index 45bc72c63..524c71892 100644
--- a/Roles/Standard/Crewmate/Chaos/Randomizer.cs
+++ b/Roles/Standard/Crewmate/Chaos/Randomizer.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
+using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
using UnityEngine;
using static EHR.Roles.Randomizer;
@@ -205,7 +208,7 @@ internal static class EffectExtenstions
}
break;
- case Effect.Meeting when TimeSinceLastMeeting > Math.Max(Main.NormalOptions.EmergencyCooldown, 30):
+ case Effect.Meeting when TimeSinceLastMeeting.Elapsed.TotalSeconds > Math.Max(Main.NormalOptions.EmergencyCooldown, 30):
{
PlayerControl pc = PickRandomPlayer();
pc.CmdReportDeadBody(null);
@@ -240,7 +243,7 @@ internal static class EffectExtenstions
case Effect.Tornado:
Tornado.SpawnTornado(PickRandomPlayer());
break;
- case Effect.RevertToBaseRole when TimeSinceLastMeeting > 40f: // To make this less frequent than the others
+ case Effect.RevertToBaseRole when TimeSinceLastMeeting.Elapsed.TotalSeconds > 40f: // To make this less frequent than the others
{
PlayerControl pc = PickRandomPlayer();
if (pc.PlayerId == randomizer.PlayerId) break;
@@ -341,7 +344,7 @@ internal static class EffectExtenstions
}
break;
- case Effect.GhostPlayer when TimeSinceLastMeeting > Options.AdjustedDefaultKillCooldown:
+ case Effect.GhostPlayer when TimeSinceLastMeeting.Elapsed.TotalSeconds > Options.AdjustedDefaultKillCooldown:
{
PlayerControl killer = PickRandomPlayer();
PlayerControl[] allPc = Main.EnumerateAlivePlayerControls().Where(x => x.PlayerId != killer.PlayerId).ToArray();
@@ -353,7 +356,7 @@ internal static class EffectExtenstions
}
break;
- case Effect.Camouflage when TimeSinceLastMeeting > Camouflager.CamouflageCooldown.GetFloat():
+ case Effect.Camouflage when TimeSinceLastMeeting.Elapsed.TotalSeconds > Camouflager.CamouflageCooldown.GetFloat():
var camouflager = new Camouflager();
camouflager.Init();
camouflager.Add(randomizer.PlayerId);
@@ -457,8 +460,7 @@ internal class Randomizer : RoleBase
public static Dictionary Rifts = [];
public static Dictionary Bombs = [];
- public static float TimeSinceLastMeeting;
- private static Dictionary LastEffectPick = [];
+ public static Stopwatch TimeSinceLastMeeting;
private static Dictionary LastTP = [];
private static long LastDeathEffect;
@@ -524,10 +526,8 @@ internal class Randomizer : RoleBase
Rifts = [];
Bombs = [];
- TimeSinceLastMeeting = 0;
- LastEffectPick = [];
- LastTP = [];
- Main.EnumeratePlayerControls().Do(x => LastTP[x.PlayerId] = now);
+ TimeSinceLastMeeting = new();
+ LastTP = Main.PlayerStates.Keys.ToDictionary(x => x, _ => now);
LastDeathEffect = 0;
EffectFrequency = EffectFrequencyOpt.GetInt();
@@ -542,7 +542,24 @@ internal class Randomizer : RoleBase
{
Exists = true;
PlayerIdList.Add(playerId);
- AllPlayerDefaultSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value);
+ AllPlayerDefaultSpeed = Main.PlayerStates.Keys.ToDictionary(x => x, _ => Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod));
+
+ _ = new CountdownTimer(EffectFrequency + 20, OnElapsed, cancelOnMeeting: false);
+ return;
+
+ void OnElapsed()
+ {
+ var pc = Utils.GetPlayerById(playerId);
+ if (pc == null || !pc.IsAlive()) return;
+
+ if (!ReportDeadBodyPatch.MeetingStarted && TimeSinceLastMeeting.IsRunning && TimeSinceLastMeeting.Elapsed.TotalSeconds > 10f)
+ {
+ Effect effect = PickRandomEffect(pc.PlayerId);
+ effect.Apply(pc);
+ }
+
+ _ = new CountdownTimer(EffectFrequency, OnElapsed, cancelOnMeeting: false);
+ }
}
public override void Remove(byte playerId)
@@ -573,8 +590,6 @@ internal class Randomizer : RoleBase
{
long now = Utils.TimeStamp;
- LastEffectPick[id] = now;
-
Effect[] allEffects = Enum.GetValues();
Effect effect = allEffects.RandomElement();
@@ -643,7 +658,7 @@ internal class Randomizer : RoleBase
{
if (!IsEnable) return;
- TimeSinceLastMeeting = 0;
+ TimeSinceLastMeeting.Reset();
LastDeathEffect = Utils.TimeStamp;
Rifts.Clear();
Bombs.Clear();
@@ -660,7 +675,7 @@ internal class Randomizer : RoleBase
{
if (!IsEnable) return;
- TimeSinceLastMeeting = 0;
+ TimeSinceLastMeeting.Restart();
LastDeathEffect = Utils.TimeStamp;
}
@@ -713,29 +728,6 @@ internal class Randomizer : RoleBase
return time < 0 ? string.Empty : $"<#ffff00>⚠ {time}";
}
- public override void OnFixedUpdate(PlayerControl pc)
- {
- try
- {
- if (!IsEnable || !GameStates.IsInTask || Main.HasJustStarted) return;
-
- TimeSinceLastMeeting += Time.fixedDeltaTime;
-
- if (pc == null || !pc.IsAlive() || TimeSinceLastMeeting <= 10f) return;
-
- long now = Utils.TimeStamp;
-
- if (LastEffectPick.TryGetValue(pc.PlayerId, out long ts) && ts + EffectFrequency <= now)
- {
- Effect effect = PickRandomEffect(pc.PlayerId);
- effect.Apply(pc);
- }
- else
- LastEffectPick.TryAdd(pc.PlayerId, now);
- }
- catch (Exception ex) { Logger.Exception(ex, "Randomizer"); }
- }
-
public override void OnCheckPlayerPosition(PlayerControl pc)
{
try
diff --git a/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs b/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs
index a5fbe3f96..1d31332c5 100644
--- a/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs
+++ b/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs
@@ -265,7 +265,6 @@ public class ToiletMaster : RoleBase
long now = Utils.TimeStamp;
if (LastUpdate >= now) return;
-
LastUpdate = now;
int duration = ToiletDuration.GetInt();
diff --git a/Roles/Standard/Crewmate/Investigate/Catcher.cs b/Roles/Standard/Crewmate/Investigate/Catcher.cs
index a4fe9e7d8..a033de622 100644
--- a/Roles/Standard/Crewmate/Investigate/Catcher.cs
+++ b/Roles/Standard/Crewmate/Investigate/Catcher.cs
@@ -1,7 +1,9 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
@@ -17,10 +19,10 @@ public class Catcher : RoleBase
public static OptionItem AbilityUseLimit;
public static OptionItem AbilityUseGainWithEachTaskCompleted;
public static OptionItem AbilityChargesWhenFinishedTasks;
+
private byte CatcherId;
private Dictionary CaughtRoles;
- private long DelayStartTS;
- private long LastUpdate;
+ private CountdownTimer DelayTimer;
private Dictionary Traps;
public override bool IsEnable => On;
@@ -74,8 +76,7 @@ public class Catcher : RoleBase
On = true;
CatcherId = playerId;
Traps = [];
- DelayStartTS = 0;
- LastUpdate = Utils.TimeStamp;
+ DelayTimer = null;
CaughtRoles = [];
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
}
@@ -108,28 +109,17 @@ public class Catcher : RoleBase
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
if (Options.UsePets.GetBool()) return;
- DelayStartTS = Utils.TimeStamp + 1;
- SendRPC();
- }
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance || Options.UsePets.GetBool() || DelayStartTS == 0) return;
-
- long now = Utils.TimeStamp;
- if (now == LastUpdate) return;
-
- LastUpdate = now;
-
- if (DelayStartTS + TrapPlaceDelay.GetInt() <= now)
+ DelayTimer = new CountdownTimer(TrapPlaceDelay.GetInt(), () =>
{
PlaceTrap(pc);
- DelayStartTS = 0;
- SendRPC();
- }
-
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- pc.RpcResetAbilityCooldown();
+ DelayTimer = null;
+ Utils.SendRPC(CustomRPC.SyncRoleData, CatcherId, false);
+ }, onTick: () =>
+ {
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ pc.RpcResetAbilityCooldown();
+ }, onCanceled: () => DelayTimer = null);
+ Utils.SendRPC(CustomRPC.SyncRoleData, CatcherId, true);
}
public override void OnCheckPlayerPosition(PlayerControl pc)
@@ -165,20 +155,15 @@ public class Catcher : RoleBase
}, 10f, "Send Catcher Caught Roles");
}
- private void SendRPC()
- {
- Utils.SendRPC(CustomRPC.SyncRoleData, CatcherId, DelayStartTS);
- }
-
public void ReceiveRPC(MessageReader reader)
{
- DelayStartTS = long.Parse(reader.ReadString());
+ DelayTimer = reader.ReadBoolean() ? new CountdownTimer(TrapPlaceDelay.GetInt(), onCanceled: () => DelayTimer = null) : null;
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
- if (seer.PlayerId != target.PlayerId || seer.PlayerId != CatcherId || meeting || (seer.IsModdedClient() && !hud) || DelayStartTS == 0 || Options.UsePets.GetBool()) return string.Empty;
- return string.Format(Translator.GetString("Catcher.Suffix"), TrapPlaceDelay.GetInt() - (Utils.TimeStamp - DelayStartTS));
+ if (seer.PlayerId != target.PlayerId || seer.PlayerId != CatcherId || meeting || (seer.IsModdedClient() && !hud) || DelayTimer == null || Options.UsePets.GetBool()) return string.Empty;
+ return string.Format(Translator.GetString("Catcher.Suffix"), (int)Math.Ceiling(DelayTimer.Remaining.TotalSeconds));
}
public override bool CanUseVent(PlayerControl pc, int ventId)
diff --git a/Roles/Standard/Crewmate/Investigate/Druid.cs b/Roles/Standard/Crewmate/Investigate/Druid.cs
index 5d1a9a10b..a6940974b 100644
--- a/Roles/Standard/Crewmate/Investigate/Druid.cs
+++ b/Roles/Standard/Crewmate/Investigate/Druid.cs
@@ -1,7 +1,9 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
using static EHR.Options;
using static EHR.Translator;
@@ -21,8 +23,7 @@ public class Druid : RoleBase
public static OptionItem AbilityChargesWhenFinishedTasks;
private PlayerControl DruidPC;
- private long lastUpdate;
- private long TriggerDelay;
+ private CountdownTimer DelayTimer;
private Dictionary TriggerIds = [];
private Dictionary Triggers = [];
@@ -56,9 +57,6 @@ public class Druid : RoleBase
public override void Init()
{
PlayerIdList = [];
- TriggerDelay = 0;
- Triggers = [];
- lastUpdate = TimeStamp;
}
public override void Add(byte playerId)
@@ -66,8 +64,9 @@ public class Druid : RoleBase
PlayerIdList.Add(playerId);
DruidPC = GetPlayerById(playerId);
playerId.SetAbilityUseLimit(UseLimitOpt.GetFloat());
- lastUpdate = TimeStamp;
TriggerIds = [];
+ Triggers = [];
+ DelayTimer = null;
}
public override void Remove(byte playerId)
@@ -111,7 +110,8 @@ public class Druid : RoleBase
{
string roomName = reader.ReadString();
- di.TriggerDelay = 0;
+ di.DelayTimer.Dispose();
+ di.DelayTimer = null;
di.Triggers.TryAdd(position, roomName);
}
else
@@ -137,8 +137,6 @@ public class Druid : RoleBase
if (pc.GetAbilityUseLimit() < 1) return;
- long now = TimeStamp;
-
if (isPet)
{
(Vector2 location, string roomName) = pc.GetPositionInfo();
@@ -148,7 +146,18 @@ public class Druid : RoleBase
TriggerIds.TryAdd(location, id);
}
else
- TriggerDelay = now;
+ {
+ DelayTimer = new CountdownTimer(TriggerPlaceDelay.GetInt(), () =>
+ {
+ byte id = pc.PlayerId;
+ DelayTimer = null;
+ (Vector2 location, string roomName) = pc.GetPositionInfo();
+ Triggers.TryAdd(location, roomName);
+ SendRPCAddTrigger(true, id, location, roomName);
+ _ = new PlayerDetector(location, [pc.PlayerId], out int oid);
+ TriggerIds.TryAdd(location, oid);
+ }, onTick: () => pc.Notify(string.Format(GetString("DruidTimeLeft"), (int)Math.Ceiling(DelayTimer.Remaining.TotalSeconds)), 2f, overrideAll: true), onCanceled: () => DelayTimer = null);
+ }
pc.RpcRemoveAbilityUse();
}
@@ -157,49 +166,23 @@ public class Druid : RoleBase
{
if (!GameStates.IsInTask || Triggers.Count <= 0 || PlayerIdList.Contains(pc.PlayerId)) return;
- foreach (KeyValuePair trigger in Triggers)
+ foreach (KeyValuePair trigger in Triggers.ToArray())
{
if (Vector2.Distance(trigger.Key, pc.Pos()) <= 1.5f)
{
- DruidPC.Notify(string.Format(GetString("DruidTriggerTriggered"), GetFormattedRoomName(trigger.Value), GetFormattedVectorText(trigger.Key)));
Triggers.Remove(trigger.Key);
+ DruidPC.Notify(string.Format(GetString("DruidTriggerTriggered"), GetFormattedRoomName(trigger.Value), GetFormattedVectorText(trigger.Key)));
SendRPCAddTrigger(false, DruidPC.PlayerId, trigger.Key);
CustomNetObject.Get(TriggerIds[trigger.Key])?.Despawn();
}
}
}
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!IsEnable || !GameStates.IsInTask || TriggerDelay == 0 || pc == null) return;
-
- long now = TimeStamp;
-
- if (TriggerDelay + TriggerPlaceDelay.GetInt() < now)
- {
- byte id = pc.PlayerId;
- TriggerDelay = 0;
- (Vector2 location, string roomName) = pc.GetPositionInfo();
- Triggers.TryAdd(location, roomName);
- SendRPCAddTrigger(true, id, location, roomName);
- _ = new PlayerDetector(location, [pc.PlayerId], out int oid);
- TriggerIds.TryAdd(location, oid);
- return;
- }
-
- long timeLeft = TriggerPlaceDelay.GetInt() - (now - TriggerDelay);
- if (lastUpdate < now) pc.Notify(string.Format(GetString("DruidTimeLeft"), timeLeft), 2f, overrideAll: true);
-
- lastUpdate = now;
- }
-
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (hud) return GetHUDText(seer);
- if (seer == null || seer.IsModdedClient() || seer.PlayerId != target.PlayerId || seer.PlayerId != DruidPC.PlayerId) return string.Empty;
-
- if (Triggers.Count == 0) return string.Empty;
+ if (seer.IsModdedClient() || seer.PlayerId != target.PlayerId || seer.PlayerId != DruidPC.PlayerId || Triggers.Count == 0) return string.Empty;
var sb = new StringBuilder();
sb.Append("\n");
diff --git a/Roles/Standard/Crewmate/Investigate/Hacker.cs b/Roles/Standard/Crewmate/Investigate/Hacker.cs
index ec8e957f2..79938f3cb 100644
--- a/Roles/Standard/Crewmate/Investigate/Hacker.cs
+++ b/Roles/Standard/Crewmate/Investigate/Hacker.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
using UnityEngine;
@@ -145,26 +146,37 @@ public class Hacker : RoleBase
pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks"));
}
- public override void OnFixedUpdate(PlayerControl pc)
+ public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount)
{
- if (pc == null) return;
-
- if (GameStates.IsMeeting) return;
-
- if (Main.PlayerStates[pc.PlayerId].TaskState.IsTaskFinished)
+ if (!pc.IsAlive()) return;
+
+ if (completedTaskCount + 1 >= totalTaskCount)
{
- LastUpdate.TryAdd(pc.PlayerId, TimeStamp);
+ _ = new CountdownTimer(5, OnElapsed, cancelOnMeeting: false);
- if (LastUpdate[pc.PlayerId] + 5 < TimeStamp)
+ void OnElapsed()
{
- if (pc.IsModdedClient())
- UseLimitSeconds[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat() * ModdedClientAbilityUseSecondsMultiplier.GetInt();
- else
- UseLimit[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat();
-
- LastUpdate[pc.PlayerId] = TimeStamp;
+ if (!GameStates.IsMeeting && !ExileController.Instance && !AntiBlackout.SkipTasks)
+ {
+ if (pc.IsModdedClient())
+ UseLimitSeconds[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat() * ModdedClientAbilityUseSecondsMultiplier.GetInt();
+ else
+ UseLimit[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat();
+ }
+
+ _ = new CountdownTimer(5, OnElapsed, cancelOnMeeting: false);
}
}
+ else
+ {
+ if (!pc.IsModdedClient() && UseLimit.ContainsKey(pc.PlayerId))
+ UseLimit[pc.PlayerId] += HackerAbilityUseGainWithEachTaskCompleted.GetFloat();
+ else if (UseLimitSeconds.ContainsKey(pc.PlayerId))
+ UseLimitSeconds[pc.PlayerId] += HackerAbilityUseGainWithEachTaskCompleted.GetInt() * ModdedClientAbilityUseSecondsMultiplier.GetInt();
+
+ if (UseLimitSeconds.ContainsKey(pc.PlayerId))
+ SendRPC(pc.PlayerId, UseLimitSeconds[pc.PlayerId]);
+ }
}
public static void MapHandle(PlayerControl pc, MapBehaviour map, MapOptions opts)
diff --git a/Roles/Standard/Crewmate/Investigate/Ignitor.cs b/Roles/Standard/Crewmate/Investigate/Ignitor.cs
index 177b49bff..f1461452c 100644
--- a/Roles/Standard/Crewmate/Investigate/Ignitor.cs
+++ b/Roles/Standard/Crewmate/Investigate/Ignitor.cs
@@ -76,19 +76,19 @@ public class Ignitor : RoleBase // Candle Lighter from TOHY
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
- float Vision;
+ float vision;
if (!Active)
- Vision = OptionTasksFinishedVision.GetFloat();
+ vision = OptionTasksFinishedVision.GetFloat();
else if (ElapsedTime > OptionTaskEndVisionTime.GetInt())
- Vision = OptionTaskStartVision.GetFloat();
+ vision = OptionTaskStartVision.GetFloat();
else
- Vision = OptionTaskStartVision.GetFloat() * (ElapsedTime / OptionTaskEndVisionTime.GetInt());
+ vision = OptionTaskStartVision.GetFloat() * (ElapsedTime / OptionTaskEndVisionTime.GetInt());
- if (Vision <= OptionTaskEndVision.GetFloat()) Vision = OptionTaskEndVision.GetFloat();
+ if (vision <= OptionTaskEndVision.GetFloat()) vision = OptionTaskEndVision.GetFloat();
- opt.SetFloat(FloatOptionNames.CrewLightMod, Vision);
- if (Utils.IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, Vision * 5);
+ opt.SetFloat(FloatOptionNames.CrewLightMod, vision);
+ if (Utils.IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, vision * 5);
}
public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount)
diff --git a/Roles/Standard/Crewmate/Investigate/Investigator.cs b/Roles/Standard/Crewmate/Investigate/Investigator.cs
index 64462b9a6..414058125 100644
--- a/Roles/Standard/Crewmate/Investigate/Investigator.cs
+++ b/Roles/Standard/Crewmate/Investigate/Investigator.cs
@@ -147,7 +147,6 @@ public class Investigator : RoleBase
public static string GetRandomCrewRoleString()
{
CustomRoles randomRole = RandomRolesForTrickster.RandomElement();
-
return $"{ColorString(GetRoleColor(randomRole), GetString(randomRole.ToString()))}";
}
diff --git a/Roles/Standard/Crewmate/Investigate/Lighter.cs b/Roles/Standard/Crewmate/Investigate/Lighter.cs
index ba06abf01..8ec4d3998 100644
--- a/Roles/Standard/Crewmate/Investigate/Lighter.cs
+++ b/Roles/Standard/Crewmate/Investigate/Lighter.cs
@@ -1,4 +1,5 @@
using AmongUs.GameOptions;
+using EHR.Modules.Extensions;
using static EHR.Options;
namespace EHR.Roles;
@@ -6,9 +7,10 @@ namespace EHR.Roles;
internal class Lighter : RoleBase
{
public static bool On;
- private long ActivateTimeStamp;
- private bool IsAbilityActive;
+
public override bool IsEnable => On;
+
+ private CountdownTimer Timer;
public override void SetupCustomOption()
{
@@ -47,15 +49,12 @@ internal class Lighter : RoleBase
{
On = true;
playerId.SetAbilityUseLimit(LighterSkillMaxOfUsage.GetFloat());
- IsAbilityActive = false;
- ActivateTimeStamp = 0;
+ Timer = null;
}
public override void Init()
{
On = false;
- IsAbilityActive = false;
- ActivateTimeStamp = 0;
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
@@ -66,7 +65,7 @@ internal class Lighter : RoleBase
AURoleOptions.EngineerCooldown = LighterSkillCooldown.GetFloat();
}
- if (IsAbilityActive)
+ if (Timer != null)
{
opt.SetVision(false);
@@ -81,7 +80,7 @@ internal class Lighter : RoleBase
{
var progressText = new StringBuilder();
- progressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, IsAbilityActive));
+ progressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, Timer != null));
progressText.Append(Utils.GetTaskCount(playerId, comms));
return progressText.ToString();
@@ -108,14 +107,21 @@ internal class Lighter : RoleBase
private void Light(PlayerControl pc)
{
- if (IsAbilityActive) return;
+ if (Timer != null) return;
if (pc.GetAbilityUseLimit() >= 1)
{
- IsAbilityActive = true;
- ActivateTimeStamp = Utils.TimeStamp;
+ float skillDuration = LighterSkillDuration.GetFloat();
+
+ Timer = new CountdownTimer(skillDuration, () =>
+ {
+ Timer = null;
+ pc.RpcResetAbilityCooldown();
+ pc.Notify(Translator.GetString("LighterSkillStop"));
+ pc.MarkDirtySettings();
+ }, onCanceled: () => Timer = null);
- pc.Notify(Translator.GetString("LighterSkillInUse"), LighterSkillDuration.GetFloat());
+ pc.Notify(Translator.GetString("LighterSkillInUse"), skillDuration);
pc.RpcRemoveAbilityUse();
pc.MarkDirtySettings();
}
@@ -123,25 +129,6 @@ internal class Lighter : RoleBase
pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks"));
}
- public override void OnReportDeadBody()
- {
- IsAbilityActive = false;
- ActivateTimeStamp = 0;
- }
-
- public override void OnFixedUpdate(PlayerControl player)
- {
- if (!GameStates.IsInTask) return;
-
- if (IsAbilityActive && ActivateTimeStamp + LighterSkillDuration.GetInt() < Utils.TimeStamp)
- {
- IsAbilityActive = false;
- player.RpcResetAbilityCooldown();
- player.Notify(Translator.GetString("LighterSkillStop"));
- player.MarkDirtySettings();
- }
- }
-
public override bool CanUseVent(PlayerControl pc, int ventId)
{
return !IsThisRole(pc) || pc.Is(CustomRoles.Nimble) || pc.GetClosestVent()?.Id == ventId;
diff --git a/Roles/Standard/Crewmate/Investigate/Spy.cs b/Roles/Standard/Crewmate/Investigate/Spy.cs
index 5f572db59..f215994ee 100644
--- a/Roles/Standard/Crewmate/Investigate/Spy.cs
+++ b/Roles/Standard/Crewmate/Investigate/Spy.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
@@ -11,7 +12,7 @@ public class Spy : RoleBase
{
private const int Id = 640400;
private static List PlayerIdList = [];
- public static Dictionary SpyRedNameList = [];
+ public static HashSet SpyRedNameList = [];
private static OptionItem SpyRedNameDur;
private static OptionItem UseLimitOpt;
@@ -61,25 +62,13 @@ public class Spy : RoleBase
PlayerIdList.Remove(playerId);
}
- private static void SendRPC(int operate, byte id = byte.MaxValue, bool changeColor = false)
+ private static void SendRPC(int operate, byte id = byte.MaxValue)
{
if (!DoRPC) return;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncSpy, SendOption.Reliable);
writer.Write(operate);
-
- switch (operate)
- {
- case 1: // Red Name Add
- writer.Write(id);
- writer.Write(SpyRedNameList[id].ToString());
- break;
- case 3: // Red Name Remove
- writer.Write(id);
- writer.Write(changeColor);
- break;
- }
-
+ writer.Write(id);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
@@ -91,13 +80,10 @@ public class Spy : RoleBase
{
case 1:
byte susId = reader.ReadByte();
- string stimeStamp = reader.ReadString();
- if (long.TryParse(stimeStamp, out long timeStamp)) SpyRedNameList[susId] = timeStamp;
-
+ SpyRedNameList.Add(susId);
return;
case 3:
SpyRedNameList.Remove(reader.ReadByte());
- reader.ReadBoolean();
return;
}
}
@@ -107,31 +93,21 @@ public class Spy : RoleBase
if (killer == null || target == null || !target.Is(CustomRoles.Spy) || killer.PlayerId == target.PlayerId || target.GetAbilityUseLimit() < 1) return true;
target.RpcRemoveAbilityUse();
- SpyRedNameList.TryAdd(killer.PlayerId, TimeStamp);
+ SpyRedNameList.Add(killer.PlayerId);
SendRPC(1, killer.PlayerId);
NotifyRoles(SpecifySeer: target, SpecifyTarget: killer);
killer.SetKillCooldown();
+ _ = new CountdownTimer(SpyRedNameDur.GetInt(), () =>
+ {
+ SpyRedNameList.Remove(killer.PlayerId);
+ SendRPC(3, killer.PlayerId);
+ NotifyRoles(SpecifySeer: target, SpecifyTarget: killer);
+ }, onCanceled: () =>
+ {
+ SpyRedNameList.Remove(killer.PlayerId);
+ SendRPC(3, killer.PlayerId);
+ });
return false;
}
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (pc == null || !pc.Is(CustomRoles.Spy) || SpyRedNameList.Count == 0) return;
-
- long now = TimeStamp;
- if (now == LastUpdate) return;
-
- LastUpdate = now;
-
- foreach (KeyValuePair x in SpyRedNameList)
- {
- if (x.Value + SpyRedNameDur.GetInt() < now || !GameStates.IsInTask)
- {
- SpyRedNameList.Remove(x.Key);
- SendRPC(3, x.Key, true);
- NotifyRoles(SpecifySeer: pc, SpecifyTarget: x.Key.GetPlayer());
- }
- }
- }
}
diff --git a/Roles/Standard/Crewmate/Investigate/Witness.cs b/Roles/Standard/Crewmate/Investigate/Witness.cs
index db054d943..af5731a39 100644
--- a/Roles/Standard/Crewmate/Investigate/Witness.cs
+++ b/Roles/Standard/Crewmate/Investigate/Witness.cs
@@ -6,7 +6,7 @@ namespace EHR.Roles;
internal class Witness : RoleBase
{
- public static Dictionary AllKillers = [];
+ public static HashSet AllKillers = [];
public static bool On;
public override bool IsEnable => On;
@@ -60,7 +60,7 @@ internal class Witness : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
killer.SetKillCooldown();
- killer.Notify(AllKillers.ContainsKey(target.PlayerId) ? Translator.GetString("WitnessFoundKiller") : Translator.GetString("WitnessFoundInnocent"));
+ killer.Notify($"<#{(AllKillers.Contains(target.PlayerId) ? "ffff00>⚠" : "00ff00>✓")}");
return false;
}
@@ -68,9 +68,4 @@ internal class Witness : RoleBase
{
AllKillers.Clear();
}
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (AllKillers.TryGetValue(pc.PlayerId, out long ktime) && ktime + WitnessTime.GetInt() < Utils.TimeStamp) AllKillers.Remove(pc.PlayerId);
- }
}
\ No newline at end of file
diff --git a/Roles/Standard/Crewmate/Killing/Sentinel.cs b/Roles/Standard/Crewmate/Killing/Sentinel.cs
index c513f2f89..6225560fb 100644
--- a/Roles/Standard/Crewmate/Killing/Sentinel.cs
+++ b/Roles/Standard/Crewmate/Killing/Sentinel.cs
@@ -1,17 +1,16 @@
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
+using EHR.Modules.Extensions;
using static EHR.Options;
using static EHR.Translator;
using static EHR.Utils;
namespace EHR.Roles;
-public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRadius, PlayerControl sentinel = null, bool isPatrolling = false, Vector2? startingPosition = null, long patrolStartTimeStamp = 0)
+public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRadius, PlayerControl sentinel = null, bool isPatrolling = false, Vector2? startingPosition = null)
{
- private readonly Dictionary LastPostitionCheck = [];
private List LastNearbyKillers = [];
- private long LastUpdate;
public byte SentinelId => sentinelId;
@@ -21,14 +20,14 @@ public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRa
private Vector2 StartingPosition { get; set; } = startingPosition ?? Vector2.zero;
- private long PatrolStartTimeStamp { get; set; } = patrolStartTimeStamp;
-
private int PatrolDuration => patrolDuration;
private float PatrolRadius => patrolRadius;
- public PlayerControl[] NearbyKillers => GetPlayersInRadius(PatrolRadius, StartingPosition).Where(x => !x.Is(Team.Crewmate) && (!Sentinel.IsMadmate() || !x.Is(Team.Impostor)) && SentinelId != x.PlayerId).ToArray();
+ private CountdownTimer Timer;
+ public IEnumerable NearbyKillers => GetPlayersInRadius(PatrolRadius, StartingPosition).Where(x => !x.Is(Team.Crewmate) && (!Sentinel.IsMadmate() || !x.Is(Team.Impostor)) && SentinelId != x.PlayerId);
+
public void SetPlayer()
{
Sentinel = GetPlayerById(SentinelId);
@@ -37,57 +36,36 @@ public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRa
public void StartPatrolling()
{
if (IsPatrolling) return;
-
IsPatrolling = true;
StartingPosition = Sentinel.Pos();
- PatrolStartTimeStamp = TimeStamp;
- foreach (PlayerControl pc in NearbyKillers) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), PatrolDuration));
-
+ NearbyKillers.Do(x => x.Notify(string.Format(GetString("KillerNotifyPatrol"), PatrolDuration)));
Sentinel.MarkDirtySettings();
+ Timer = new CountdownTimer(PatrolDuration, FinishPatrolling, onTick: CheckPlayerPositions, onCanceled: () => IsPatrolling = false);
}
- public void Update()
+ private void CheckPlayerPositions()
{
if (!IsPatrolling) return;
- long now = TimeStamp;
- if (LastUpdate >= now) return;
- LastUpdate = now;
+ List killers = NearbyKillers.Select(x => x.PlayerId).ToList();
+ int timeLeft = (int)Timer.Remaining.TotalSeconds;
- if (PatrolStartTimeStamp + PatrolDuration < now)
- FinishPatrolling();
+ foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls())
+ {
+ bool nowInRange = killers.Contains(pc.PlayerId);
+ bool wasInRange = LastNearbyKillers.Contains(pc.PlayerId);
+
+ if (wasInRange && !nowInRange) pc.Notify(GetString("KillerEscapedFromSentinel"));
+ if (nowInRange && timeLeft >= 0) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), timeLeft), 3f, true);
+ }
+
+ LastNearbyKillers = killers;
}
- public void OnCheckPlayerPosition(PlayerControl pc)
- {
- if (!IsPatrolling) return;
-
- long now = TimeStamp;
- LastPostitionCheck.TryAdd(pc.PlayerId, now);
- if (LastPostitionCheck[pc.PlayerId] >= now) return;
- LastPostitionCheck[pc.PlayerId] = now;
-
- PlayerControl[] killers = NearbyKillers;
-
- bool nowInRange = killers.Any(x => x.PlayerId == pc.PlayerId);
- bool wasInRange = LastNearbyKillers.Contains(pc.PlayerId);
-
- long timeLeft = PatrolDuration - (TimeStamp - PatrolStartTimeStamp);
-
- if (wasInRange && !nowInRange) pc.Notify(GetString("KillerEscapedFromSentinel"));
- if (nowInRange && timeLeft >= 0) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), timeLeft), 3f, true);
-
- LastNearbyKillers = killers.Select(x => x.PlayerId).ToList();
- }
-
- public void FinishPatrolling()
+ private void FinishPatrolling()
{
IsPatrolling = false;
- if (!GameStates.IsInTask) return;
-
- foreach (PlayerControl pc in NearbyKillers)
- pc.Suicide(PlayerState.DeathReason.Patrolled, Sentinel);
-
+ NearbyKillers.Do(x => x.Suicide(PlayerState.DeathReason.Patrolled, Sentinel));
Sentinel.MarkDirtySettings();
}
}
@@ -185,36 +163,6 @@ internal class Sentinel : RoleBase
AURoleOptions.EngineerInVentMaxTime = 1f;
}
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!IsEnable) return;
-
- foreach (PatrollingState state in PatrolStates)
- state.Update();
- }
-
- public override void OnCheckPlayerPosition(PlayerControl pc)
- {
- if (!IsEnable) return;
-
- foreach (PatrollingState state in PatrolStates)
- state.OnCheckPlayerPosition(pc);
- }
-
- public override void OnReportDeadBody()
- {
- if (!IsEnable) return;
-
- LateTask.New(() =>
- {
- foreach (PatrollingState state in PatrolStates)
- {
- if (state.IsPatrolling)
- state.FinishPatrolling();
- }
- }, 0.1f, "SentinelFinishPatrol");
- }
-
public override bool CanUseVent(PlayerControl pc, int ventId)
{
return !IsThisRole(pc) || pc.Is(CustomRoles.Nimble) || pc.GetClosestVent()?.Id == ventId;
diff --git a/Roles/Standard/Crewmate/Killing/Veteran.cs b/Roles/Standard/Crewmate/Killing/Veteran.cs
index 34d7e0803..63334aa9a 100644
--- a/Roles/Standard/Crewmate/Killing/Veteran.cs
+++ b/Roles/Standard/Crewmate/Killing/Veteran.cs
@@ -1,13 +1,14 @@
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using static EHR.Options;
namespace EHR.Roles;
internal class Veteran : RoleBase
{
- public static Dictionary VeteranInProtect = [];
+ public static HashSet VeteranInProtect = [];
public static bool On;
public override bool IsEnable => On;
@@ -71,7 +72,7 @@ internal class Veteran : RoleBase
{
var progressText = new StringBuilder();
- progressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, VeteranInProtect.ContainsKey(playerId)));
+ progressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, VeteranInProtect.Contains(playerId)));
progressText.Append(Utils.GetTaskCount(playerId, comms));
return progressText.ToString();
@@ -98,11 +99,17 @@ internal class Veteran : RoleBase
private static void Alert(PlayerControl pc)
{
- if (VeteranInProtect.ContainsKey(pc.PlayerId)) return;
+ if (VeteranInProtect.Contains(pc.PlayerId)) return;
if (pc.GetAbilityUseLimit() >= 1)
{
- VeteranInProtect[pc.PlayerId] = Utils.TimeStamp;
+ VeteranInProtect.Add(pc.PlayerId);
+ _ = new CountdownTimer(VeteranSkillDuration.GetInt(), () =>
+ {
+ VeteranInProtect.Remove(pc.PlayerId);
+ pc.RpcResetAbilityCooldown();
+ pc.Notify(string.Format(Translator.GetString("VeteranOffGuard"), (int)pc.GetAbilityUseLimit()));
+ }, onCanceled: () => VeteranInProtect.Remove(pc.PlayerId));
pc.RpcRemoveAbilityUse();
pc.RPCPlayCustomSound("Gunload");
pc.Notify(Translator.GetString("VeteranOnGuard"), VeteranSkillDuration.GetFloat());
@@ -116,7 +123,7 @@ internal class Veteran : RoleBase
{
if (!killer.IsAlive()) return false;
- if (VeteranInProtect.ContainsKey(target.PlayerId) && killer.PlayerId != target.PlayerId)
+ if (VeteranInProtect.Contains(target.PlayerId) && killer.PlayerId != target.PlayerId)
{
if (!killer.Is(CustomRoles.Pestilence))
{
@@ -139,18 +146,6 @@ internal class Veteran : RoleBase
return true;
}
- public override void OnFixedUpdate(PlayerControl player)
- {
- byte playerId = player.PlayerId;
-
- if (VeteranInProtect.TryGetValue(playerId, out long vtime) && vtime + VeteranSkillDuration.GetInt() < Utils.TimeStamp)
- {
- VeteranInProtect.Remove(playerId);
- player.RpcResetAbilityCooldown();
- player.Notify(string.Format(Translator.GetString("VeteranOffGuard"), (int)player.GetAbilityUseLimit()));
- }
- }
-
public override bool CanUseVent(PlayerControl pc, int ventId)
{
return !IsThisRole(pc) || pc.Is(CustomRoles.Nimble) || pc.GetClosestVent()?.Id == ventId;
diff --git a/Roles/Standard/Crewmate/Miscellaneous/Addict.cs b/Roles/Standard/Crewmate/Miscellaneous/Addict.cs
index 0977e0cb2..725cf8487 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/Addict.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/Addict.cs
@@ -1,8 +1,7 @@
-using System;
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Modules;
-using UnityEngine;
+using EHR.Modules.Extensions;
namespace EHR.Roles;
@@ -13,18 +12,15 @@ public class Addict : RoleBase
private const int Id = 5200;
private static List PlayerIdList = [];
- public static OptionItem VentCooldown;
- public static OptionItem TimeLimit;
+ private static OptionItem VentCooldown;
+ private static OptionItem TimeLimit;
+ private static OptionItem ImmortalTimeAfterVent;
+ private static OptionItem SpeedWhileImmortal;
+ private static OptionItem FreezeTimeAfterImmortal;
- public static OptionItem ImmortalTimeAfterVent;
-
- public static OptionItem SpeedWhileImmortal;
- public static OptionItem FreezeTimeAfterImmortal;
-
- private static float DefaultSpeed;
- private float ImmortalTimer = 420f;
-
- private float SuicideTimer = -10f;
+ private CountdownTimer ImmortalTimer;
+ private CountdownTimer SuicideTimer;
+ private byte AddictId;
public override bool IsEnable => PlayerIdList.Count > 0;
@@ -51,17 +47,37 @@ public class Addict : RoleBase
public override void Init()
{
PlayerIdList = [];
- SuicideTimer = -10f;
- ImmortalTimer = 420f;
- DefaultSpeed = 0;
}
public override void Add(byte playerId)
{
+ AddictId = playerId;
PlayerIdList.Add(playerId);
- SuicideTimer = -10f;
- ImmortalTimer = 420f;
- DefaultSpeed = Main.AllPlayerSpeed[playerId];
+ SuicideTimer = new CountdownTimer(TimeLimit.GetFloat(), () =>
+ {
+ var player = playerId.GetPlayer();
+ if (player == null || !player.IsAlive()) return;
+
+ player.Suicide();
+ SuicideTimer = null;
+
+ if (player.AmOwner)
+ Achievements.Type.OutOfTime.Complete();
+ }, onTick: () =>
+ {
+ var player = playerId.GetPlayer();
+
+ if (player == null || !player.IsAlive())
+ {
+ SuicideTimer.Dispose();
+ SuicideTimer = null;
+ return;
+ }
+
+ if (SuicideTimer.Remaining is { Minutes: 0, Seconds: 8 })
+ player.Notify(Translator.GetString("AddictWarning"), 8f);
+ }, onCanceled: () => SuicideTimer = null);
+ ImmortalTimer = null;
}
public override void Remove(byte playerId)
@@ -69,52 +85,45 @@ public class Addict : RoleBase
PlayerIdList.Remove(playerId);
}
- private bool IsImmortal(PlayerControl player)
- {
- return player.Is(CustomRoles.Addict) && ImmortalTimer <= ImmortalTimeAfterVent.GetFloat();
- }
-
public override void OnReportDeadBody()
{
- foreach (byte player in PlayerIdList.ToArray())
+ PlayerIdList.ForEach(x => Main.AllPlayerSpeed[x] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod));
+ }
+
+ public override void AfterMeetingTasks()
+ {
+ SuicideTimer?.Dispose();
+ SuicideTimer = new CountdownTimer(TimeLimit.GetFloat(), () =>
{
- SuicideTimer = -10f;
- ImmortalTimer = 420f;
- Main.AllPlayerSpeed[player] = DefaultSpeed;
- }
+ var player = AddictId.GetPlayer();
+ if (player == null || !player.IsAlive()) return;
+
+ player.Suicide();
+ SuicideTimer = null;
+
+ if (player.AmOwner)
+ Achievements.Type.OutOfTime.Complete();
+ }, onTick: () =>
+ {
+ var player = AddictId.GetPlayer();
+
+ if (player == null || !player.IsAlive())
+ {
+ SuicideTimer.Dispose();
+ SuicideTimer = null;
+ return;
+ }
+
+ if (SuicideTimer.Remaining is { Minutes: 0, Seconds: 8 })
+ player.Notify(Translator.GetString("AddictWarning"), 8f);
+ }, onCanceled: () => SuicideTimer = null);
+ ImmortalTimer?.Dispose();
+ ImmortalTimer = null;
}
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
- return !IsImmortal(target);
- }
-
- public override void OnFixedUpdate(PlayerControl player)
- {
- if (!GameStates.IsInTask || !IsEnable || Math.Abs(SuicideTimer - -10f) < 0.5f || !player.IsAlive()) return;
-
- if (SuicideTimer >= TimeLimit.GetFloat())
- {
- player.Suicide();
- SuicideTimer = -10f;
-
- if (player.AmOwner)
- Achievements.Type.OutOfTime.Complete();
- }
- else if (Mathf.Approximately(SuicideTimer + 8, TimeLimit.GetFloat()))
- player.Notify(Translator.GetString("AddictWarning"), 8f);
- else
- {
- SuicideTimer += Time.fixedDeltaTime;
-
- if (IsImmortal(player))
- ImmortalTimer += Time.fixedDeltaTime;
- else if (Math.Abs(ImmortalTimer - 420f) > 0.5f && FreezeTimeAfterImmortal.GetFloat() > 0)
- {
- AddictGetDown(player);
- ImmortalTimer = 420f;
- }
- }
+ return ImmortalTimer == null;
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
@@ -125,10 +134,32 @@ public class Addict : RoleBase
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
- if (!pc.Is(CustomRoles.Addict)) return;
+ SuicideTimer?.Dispose();
+ SuicideTimer = new CountdownTimer(TimeLimit.GetFloat(), () =>
+ {
+ pc.Suicide();
+ SuicideTimer = null;
- SuicideTimer = 0f;
- ImmortalTimer = 0f;
+ if (pc.AmOwner)
+ Achievements.Type.OutOfTime.Complete();
+ }, onTick: () =>
+ {
+ if (pc == null || !pc.IsAlive())
+ {
+ SuicideTimer.Dispose();
+ SuicideTimer = null;
+ return;
+ }
+
+ if (SuicideTimer.Remaining is { Minutes: 0, Seconds: 8 })
+ pc.Notify(Translator.GetString("AddictWarning"), 8f);
+ }, onCanceled: () => SuicideTimer = null);
+ ImmortalTimer?.Dispose();
+ ImmortalTimer = new CountdownTimer(ImmortalTimeAfterVent.GetFloat(), () =>
+ {
+ ImmortalTimer = null;
+ if (FreezeTimeAfterImmortal.GetFloat() > 0) AddictGetDown(pc);
+ }, onCanceled: () => ImmortalTimer = null);
Main.AllPlayerSpeed[pc.PlayerId] = SpeedWhileImmortal.GetFloat();
pc.MarkDirtySettings();
@@ -142,7 +173,7 @@ public class Addict : RoleBase
LateTask.New(() =>
{
- Main.AllPlayerSpeed[addict.PlayerId] = DefaultSpeed;
+ Main.AllPlayerSpeed[addict.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
ReportDeadBodyPatch.CanReport[addict.PlayerId] = true;
addict.MarkDirtySettings();
}, FreezeTimeAfterImmortal.GetFloat(), "AddictGetDown");
diff --git a/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs b/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs
index 911ed7ab8..f27fa19d0 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/CameraMan.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Modules;
+using UnityEngine;
namespace EHR.Roles;
@@ -91,13 +92,7 @@ public class CameraMan : RoleBase
if (pc.GetAbilityUseLimit() >= 1)
{
pc.RpcRemoveAbilityUse();
-
- LateTask.New(() =>
- {
- BasePos = pc.Pos();
- pc.RPCPlayCustomSound("teleport");
- if (pc.TP(CameraPosition)) IsTeleported = true;
- }, 2f, "CameraMan Teleport");
+ LateTask.New(() => UseAbility(pc), 2f, "CameraMan Teleport");
}
else
pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks"));
@@ -110,21 +105,39 @@ public class CameraMan : RoleBase
if (pc.GetAbilityUseLimit() >= 1)
{
pc.RpcRemoveAbilityUse();
-
- BasePos = pc.Pos();
- pc.RPCPlayCustomSound("teleport");
- if (pc.TP(CameraPosition)) IsTeleported = true;
+ UseAbility(pc);
}
else
pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks"));
}
- public override void OnFixedUpdate(PlayerControl pc)
+ private void UseAbility(PlayerControl pc)
{
- if (!IsTeleported || !TPBackWhenMoveAway.GetBool() || !pc.IsAlive() || !GameStates.IsInTask || Vector2.Distance(pc.Pos(), CameraPosition) <= DisableDevice.UsableDistance) return;
+ BasePos = pc.Pos();
+ pc.RPCPlayCustomSound("teleport");
+
+ if (pc.TP(CameraPosition))
+ {
+ IsTeleported = true;
+
+ if (TPBackWhenMoveAway.GetBool())
+ {
+ Main.Instance.StartCoroutine(Coroutine());
- IsTeleported = false;
- LateTask.New(() => pc.TP(BasePos), 2f, "CameraMan Teleport Back");
+ System.Collections.IEnumerator Coroutine()
+ {
+ yield return new WaitForSecondsRealtime(2f);
+
+ while (GameStates.IsInTask && pc.IsAlive() && Vector2.Distance(pc.Pos(), CameraPosition) <= DisableDevice.UsableDistance)
+ yield return new WaitForSecondsRealtime(0.5f);
+
+ if (!pc.IsAlive() || !GameStates.IsInTask) yield break;
+
+ IsTeleported = false;
+ pc.TP(BasePos);
+ }
+ }
+ }
}
public override void OnReportDeadBody()
diff --git a/Roles/Standard/Crewmate/Miscellaneous/Express.cs b/Roles/Standard/Crewmate/Miscellaneous/Express.cs
index a210fb740..e8cc00b45 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/Express.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/Express.cs
@@ -1,11 +1,11 @@
-using System.Collections.Generic;
+using AmongUs.GameOptions;
+using EHR.Modules.Extensions;
namespace EHR.Roles;
internal class Express : RoleBase
{
- public static Dictionary SpeedUp = [];
- public static Dictionary SpeedNormal = [];
+ private CountdownTimer Timer;
public static bool On;
public override bool IsEnable => On;
@@ -26,6 +26,7 @@ internal class Express : RoleBase
public override void Add(byte playerId)
{
On = true;
+ Timer = null;
}
public override void Init()
@@ -35,25 +36,23 @@ internal class Express : RoleBase
public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount)
{
- if (!SpeedUp.ContainsKey(player.PlayerId)) SpeedNormal[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId];
-
- Main.AllPlayerSpeed[player.PlayerId] = Options.ExpressSpeed.GetFloat();
- SpeedUp[player.PlayerId] = Utils.TimeStamp;
- player.MarkDirtySettings();
- }
-
- public override void OnFixedUpdate(PlayerControl player)
- {
- if (!GameStates.IsInTask) return;
-
- byte playerId = player.PlayerId;
- long now = Utils.TimeStamp;
-
- if (SpeedUp.TryGetValue(playerId, out long etime) && etime + Options.ExpressSpeedDur.GetInt() < now)
+ if (Timer == null)
{
- SpeedUp.Remove(playerId);
- Main.AllPlayerSpeed[playerId] = SpeedNormal[playerId];
+ Main.AllPlayerSpeed[player.PlayerId] = Options.ExpressSpeed.GetFloat();
player.MarkDirtySettings();
}
+ else
+ Timer.Dispose();
+
+ Timer = new CountdownTimer(Options.ExpressSpeedDur.GetInt(), () =>
+ {
+ Timer = null;
+ Main.AllPlayerSpeed[player.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
+ player.MarkDirtySettings();
+ }, onCanceled: () =>
+ {
+ Timer = null;
+ Main.AllPlayerSpeed[player.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
+ });
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Crewmate/Miscellaneous/Safeguard.cs b/Roles/Standard/Crewmate/Miscellaneous/Safeguard.cs
index 407f5e03d..0569e05c1 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/Safeguard.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/Safeguard.cs
@@ -1,4 +1,5 @@
using System;
+using EHR.Modules.Extensions;
using UnityEngine;
namespace EHR.Roles;
@@ -9,10 +10,9 @@ public class Safeguard : RoleBase
private static OptionItem ShieldDuration;
private static OptionItem MinTasks;
- private byte SafeguardId;
- private float Timer;
- private bool Shielded => Timer > 0;
+ private byte SafeguardId;
+ private CountdownTimer Timer;
public override bool IsEnable => On;
@@ -42,7 +42,7 @@ public class Safeguard : RoleBase
public override void Add(byte playerId)
{
On = true;
- Timer = 0;
+ Timer = null;
SafeguardId = playerId;
}
@@ -52,36 +52,24 @@ public class Safeguard : RoleBase
if (completedTaskCount + 1 >= MinTasks.GetInt())
{
- bool zero = !Shielded;
- Timer += ShieldDuration.GetFloat();
- if (zero) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- }
- }
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (!pc.IsAlive()) return;
-
- if (Shielded)
- {
- Timer -= Time.fixedDeltaTime;
-
- if (Timer <= 0)
+ bool shielded = Timer != null;
+ Timer = new CountdownTimer(ShieldDuration.GetFloat() + (shielded ? (float)Timer.Remaining.TotalSeconds : 0), () =>
{
- Timer = 0;
+ Timer = null;
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- }
+ }, onCanceled: () => Timer = null);
+ if (!shielded) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
}
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
- return !Shielded;
+ return Timer == null;
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
- if (seer.PlayerId != target.PlayerId || seer.PlayerId != SafeguardId || meeting || (seer.IsModdedClient() && !hud) || !Shielded) return string.Empty;
- return seer.IsHost() ? string.Format(Translator.GetString("SafeguardSuffixTimer"), (int)Math.Ceiling(Timer)) : Translator.GetString("SafeguardSuffix");
+ if (seer.PlayerId != target.PlayerId || seer.PlayerId != SafeguardId || meeting || (seer.IsModdedClient() && !hud) || Timer == null) return string.Empty;
+ return seer.IsHost() ? string.Format(Translator.GetString("SafeguardSuffixTimer"), (int)Math.Ceiling(Timer.Remaining.TotalSeconds)) : Translator.GetString("SafeguardSuffix");
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs b/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs
index 6785eb3f2..4dfe32c11 100644
--- a/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs
+++ b/Roles/Standard/Crewmate/Miscellaneous/Vacuum.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
@@ -20,7 +21,7 @@ public class Vacuum : RoleBase
private static OptionItem AbilityChargesWhenFinishedTasks;
private byte VacuumId;
- private long AbilityEndTS;
+ private CountdownTimer Timer;
public override void SetupCustomOption()
{
@@ -41,7 +42,7 @@ public class Vacuum : RoleBase
public override void Add(byte playerId)
{
On = true;
- AbilityEndTS = 0;
+ Timer = null;
VacuumId = playerId;
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
Instances.Add(this);
@@ -50,7 +51,7 @@ public class Vacuum : RoleBase
public override string GetProgressText(byte playerId, bool comms)
{
StringBuilder sb = new();
- sb.Append(Utils.GetAbilityUseLimitDisplay(playerId, AbilityEndTS != 0));
+ sb.Append(Utils.GetAbilityUseLimitDisplay(playerId, Timer != null));
sb.Append(Utils.GetTaskCount(playerId, comms));
return sb.ToString();
}
@@ -76,23 +77,19 @@ public class Vacuum : RoleBase
{
if (pc.GetAbilityUseLimit() < 1) return;
pc.RpcRemoveAbilityUse();
- AbilityEndTS = Utils.TimeStamp + AbilityDuration.GetInt();
- Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityEndTS);
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- }
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (AbilityEndTS == 0) return;
- if (AbilityEndTS > Utils.TimeStamp) return;
- AbilityEndTS = 0;
- Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityEndTS);
+ Timer = new CountdownTimer(AbilityDuration.GetInt(), () =>
+ {
+ Timer = null;
+ Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, false);
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ }, onCanceled: () => Timer = null);
+ Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, true);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
public void ReceiveRPC(MessageReader reader)
{
- AbilityEndTS = long.Parse(reader.ReadString());
+ Timer = reader.ReadBoolean() ? new CountdownTimer(AbilityDuration.GetInt(), onCanceled: () => Timer = null) : null;
}
public static bool BeforeMurderCheck(PlayerControl target)
@@ -103,7 +100,7 @@ public class Vacuum : RoleBase
{
try
{
- if (instance.AbilityEndTS == 0) continue;
+ if (instance.Timer == null) continue;
PlayerControl vacuum = instance.VacuumId.GetPlayer();
if (vacuum == null || !vacuum.IsAlive()) continue;
target.TP(vacuum);
diff --git a/Roles/Standard/Crewmate/Power/Adrenaline.cs b/Roles/Standard/Crewmate/Power/Adrenaline.cs
index 2cf353726..e43f89608 100644
--- a/Roles/Standard/Crewmate/Power/Adrenaline.cs
+++ b/Roles/Standard/Crewmate/Power/Adrenaline.cs
@@ -1,4 +1,6 @@
-using EHR.Modules;
+using AmongUs.GameOptions;
+using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
@@ -16,9 +18,9 @@ public class Adrenaline : RoleBase
public static OptionItem AbilityChargesWhenFinishedTasks;
private byte AdrenalineId;
- private float DefaultSpeed;
private long LastUpdate;
- private int Timer;
+ private CountdownTimer Timer;
+
public override bool IsEnable => On;
public override void SetupCustomOption()
@@ -61,74 +63,49 @@ public class Adrenaline : RoleBase
{
On = true;
AdrenalineId = playerId;
- Timer = 0;
+ Timer = null;
Utils.SendRPC(CustomRPC.SyncRoleData, playerId, Timer);
- DefaultSpeed = Main.AllPlayerSpeed[playerId];
playerId.SetAbilityUseLimit(MaxSurvives.GetFloat());
}
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
- if (!base.OnCheckMurderAsTarget(killer, target) || target.GetAbilityUseLimit() < 1 || target.GetTaskState().CompletedTasksCount < MinTasksRequired.GetInt()) return true;
+ if (!base.OnCheckMurderAsTarget(killer, target) || target.GetAbilityUseLimit() < 1 || target.GetTaskState().CompletedTasksCount < MinTasksRequired.GetInt() || Timer != null) return true;
target.RpcRemoveAbilityUse();
- Timer = Time.GetInt();
- Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, Timer);
+ Timer = new CountdownTimer(Time.GetInt(), () =>
+ {
+ Timer = null;
+ target.Suicide();
+ Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, false);
+
+ if (target.AmOwner)
+ Achievements.Type.OutOfTime.Complete();
+ }, onTick: () => Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target), onCanceled: () =>
+ {
+ Timer = null;
+ Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
+ Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, false);
+ });
+ Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, true);
Main.AllPlayerSpeed[target.PlayerId] += SpeedIncreaseDuringTimer.GetFloat();
target.MarkDirtySettings();
return false;
}
- public override void OnReportDeadBody()
- {
- Timer = 0;
- }
-
- public override void OnFixedUpdate(PlayerControl pc)
- {
- if (Timer == 0 || !pc.IsAlive()) return;
-
- if (GameStates.IsMeeting)
- {
- Timer = 0;
- Main.AllPlayerSpeed[pc.PlayerId] = DefaultSpeed;
- pc.MarkDirtySettings();
- return;
- }
-
- long now = Utils.TimeStamp;
- if (now == LastUpdate) return;
- LastUpdate = now;
-
- Timer--;
-
- if (Timer <= 0)
- {
- Timer = 0;
- pc.Suicide();
-
- if (pc.AmOwner)
- Achievements.Type.OutOfTime.Complete();
- }
-
- Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, Timer);
- Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
- }
-
public static bool CanCallMeeting(PlayerControl pc)
{
- return Main.PlayerStates[pc.PlayerId].Role is Adrenaline an && (an.Timer == 0 || CanCallMeetingDuringTimer.GetBool());
+ return Main.PlayerStates[pc.PlayerId].Role is Adrenaline an && (an.Timer == null || CanCallMeetingDuringTimer.GetBool());
}
public void ReceiveRPC(MessageReader reader)
{
- Timer = reader.ReadPackedInt32();
+ Timer = reader.ReadBoolean() ? new CountdownTimer(Time.GetInt(), onCanceled: () => Timer = null) : null;
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
- if (seer.PlayerId != target.PlayerId || seer.PlayerId != AdrenalineId || meeting || (seer.IsModdedClient() && !hud) || Timer == 0) return string.Empty;
-
- return string.Format(Translator.GetString("Adrenaline.Suffix"), Timer);
+ if (seer.PlayerId != target.PlayerId || seer.PlayerId != AdrenalineId || meeting || (seer.IsModdedClient() && !hud) || Timer == null) return string.Empty;
+ return string.Format(Translator.GetString("Adrenaline.Suffix"), (int)Timer.Remaining.TotalSeconds);
}
}
\ No newline at end of file
diff --git a/Roles/Standard/Crewmate/Power/Alchemist.cs b/Roles/Standard/Crewmate/Power/Alchemist.cs
index 72dd290e9..ea4c7e99d 100644
--- a/Roles/Standard/Crewmate/Power/Alchemist.cs
+++ b/Roles/Standard/Crewmate/Power/Alchemist.cs
@@ -1,7 +1,9 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
+using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
@@ -25,18 +27,14 @@ public class Alchemist : RoleBase
private byte AlchemistId;
public bool FixNextSabo;
- private long InvisTime = -10;
-
- public bool IsProtected;
-
- private long lastFixedTime;
- public string PlayerName = string.Empty;
- public byte PotionID = 10;
- private int ventedId = -10;
+ private bool IsProtected;
+ private string PlayerName = string.Empty;
+ private byte PotionID = 10;
public bool VisionPotionActive;
+ private CountdownTimer InvisTimer;
public override bool IsEnable => PlayerIdList.Count > 0;
- private bool IsInvis => InvisTime != -10;
+ private bool IsInvis => InvisTimer != null;
public override void SetupCustomOption()
{
@@ -72,13 +70,6 @@ public class Alchemist : RoleBase
public override void Init()
{
PlayerIdList = [];
- PotionID = 10;
- PlayerName = string.Empty;
- ventedId = -10;
- InvisTime = -10;
- FixNextSabo = false;
- VisionPotionActive = false;
- AlchemistId = byte.MaxValue;
}
public override void Add(byte playerId)
@@ -87,8 +78,7 @@ public class Alchemist : RoleBase
PlayerName = Utils.GetPlayerById(playerId).GetRealName();
AlchemistId = playerId;
PotionID = 10;
- ventedId = -10;
- InvisTime = -10;
+ InvisTimer = null;
FixNextSabo = false;
VisionPotionActive = false;
}
@@ -169,7 +159,7 @@ public class Alchemist : RoleBase
DrinkPotion(pc);
}
- public static void DrinkPotion(PlayerControl player, bool isPet = false)
+ private static void DrinkPotion(PlayerControl player, bool isPet = false)
{
if (Main.PlayerStates[player.PlayerId].Role is not Alchemist am) return;
@@ -239,24 +229,6 @@ public class Alchemist : RoleBase
am.SendRPCData();
}
- private void SendRPC()
- {
- if (!IsEnable || !Utils.DoRPC) return;
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAlchemistTimer, SendOption.Reliable);
- writer.Write(AlchemistId);
- writer.Write(InvisTime.ToString());
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- }
-
- public static void ReceiveRPC(MessageReader reader)
- {
- byte id = reader.ReadByte();
- if (Main.PlayerStates[id].Role is not Alchemist am) return;
-
- am.InvisTime = long.Parse(reader.ReadString());
- }
-
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
AURoleOptions.EngineerCooldown = VentCooldown.GetFloat();
@@ -268,49 +240,30 @@ public class Alchemist : RoleBase
if (PotionID != 6) return false;
PotionID = 10;
- PlayerControl pc = instance.myPlayer;
if (!AmongUsClient.Instance.AmHost) return false;
-
- ventedId = ventId;
+ PlayerControl pc = instance.myPlayer;
LateTask.New(() => instance.RpcExitVentDesync(ventId, pc), 0.5f);
- InvisTime = Utils.TimeStamp;
- SendRPC();
- pc.Notify(GetString("ChameleonInvisState"), InvisDuration.GetFloat());
-
+ float invisDuration = InvisDuration.GetFloat();
+
+ InvisTimer = new CountdownTimer(invisDuration, () =>
+ {
+ InvisTimer = null;
+ if (pc == null || !pc.IsAlive()) return;
+ Main.EnumeratePlayerControls().Without(pc).Do(x => instance.RpcExitVentDesync(ventId, x));
+ pc.Notify(GetString("SwooperInvisStateOut"));
+ Utils.SendRPC(CustomRPC.SyncRoleData, AlchemistId, false);
+ }, onTick: pc.IsModdedClient() ? null : () => pc.Notify(string.Format(GetString("SwooperInvisStateCountdown"), (int)Math.Ceiling(InvisTimer.Remaining.TotalSeconds)), overrideAll: true), onCanceled: () => InvisTimer = null);
+
+ pc.Notify(GetString("ChameleonInvisState"), invisDuration);
+ Utils.SendRPC(CustomRPC.SyncRoleData, AlchemistId, true);
return true;
}
- public override void OnFixedUpdate(PlayerControl player)
+ public void ReceiveRPC(MessageReader reader)
{
- if (!GameStates.IsInTask || !IsEnable) return;
-
- long now = Utils.TimeStamp;
-
- if (lastFixedTime != now && InvisTime != -10)
- {
- lastFixedTime = now;
- var refresh = false;
- long remainTime = InvisTime + (long)InvisDuration.GetFloat() - now;
-
- switch (remainTime)
- {
- case < 0:
- int ventId = ventedId == -10 ? Main.LastEnteredVent[player.PlayerId].Id : ventedId;
- Main.EnumeratePlayerControls().Without(player).Do(x => player.MyPhysics.RpcExitVentDesync(ventId, x));
- player.Notify(GetString("SwooperInvisStateOut"));
- SendRPC();
- refresh = true;
- InvisTime = -10;
- break;
- case <= 10 when !player.IsModdedClient():
- player.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1), overrideAll: true);
- break;
- }
-
- if (refresh) SendRPC();
- }
+ InvisTimer = reader.ReadBoolean() ? new CountdownTimer(InvisDuration.GetFloat(), onCanceled: () => InvisTimer = null) : null;
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
@@ -321,8 +274,8 @@ public class Alchemist : RoleBase
if (IsInvis)
{
- long remainTime = InvisTime + (long)InvisDuration.GetFloat() - Utils.TimeStamp;
- sb.Append(string.Format(GetString("ChameleonInvisStateCountdown"), remainTime + 1));
+ int remainTime = (int)Math.Ceiling(InvisTimer.Remaining.TotalSeconds);
+ sb.Append(string.Format(GetString("ChameleonInvisStateCountdown"), remainTime));
}
else
{
diff --git a/Roles/Standard/Impostor/Concealing/Hangman.cs b/Roles/Standard/Impostor/Concealing/Hangman.cs
index 2e674cfcd..c99713562 100644
--- a/Roles/Standard/Impostor/Concealing/Hangman.cs
+++ b/Roles/Standard/Impostor/Concealing/Hangman.cs
@@ -77,7 +77,7 @@ public class Hangman : RoleBase
{
if (target.Is(CustomRoles.Pestilence)) return false;
- if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return false;
+ if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.Contains(target.PlayerId)) return false;
RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound);
killer.RpcRemoveAbilityUse();
diff --git a/Roles/Standard/Impostor/Concealing/Mastermind.cs b/Roles/Standard/Impostor/Concealing/Mastermind.cs
index 55e63a1fe..3d75a96fa 100644
--- a/Roles/Standard/Impostor/Concealing/Mastermind.cs
+++ b/Roles/Standard/Impostor/Concealing/Mastermind.cs
@@ -170,7 +170,7 @@ public class Mastermind : RoleBase
foreach (byte id in PlayerIdList) (Main.PlayerStates[id].Role as Mastermind)?.NotifyMastermindTargetSurvived();
- if (target.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.ContainsKey(target.PlayerId) || target.Is(CustomRoles.Mastermind))
+ if (target.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.Contains(target.PlayerId) || target.Is(CustomRoles.Mastermind))
{
Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Suicide;
Main.PlayerStates[killer.PlayerId].SetDead();
diff --git a/Roles/Standard/Impostor/Concealing/Vampire.cs b/Roles/Standard/Impostor/Concealing/Vampire.cs
index 86bf06dcf..a677bb8c7 100644
--- a/Roles/Standard/Impostor/Concealing/Vampire.cs
+++ b/Roles/Standard/Impostor/Concealing/Vampire.cs
@@ -96,7 +96,7 @@ public class Vampire : RoleBase
if (target.Is(CustomRoles.Pestilence)) return true;
if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) return true;
if (target.Is(CustomRoles.Opportunist) && target.AllTasksCompleted() && Opportunist.OppoImmuneToAttacksWhenTasksDone.GetBool()) return true;
- if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return true;
+ if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.Contains(target.PlayerId)) return true;
if (Medic.ProtectList.Contains(target.PlayerId)) return true;
if (CanKillNormally) return killer.CheckDoubleTrigger(target, Bite);
diff --git a/Roles/Standard/Impostor/Miscellaneous/Gambler.cs b/Roles/Standard/Impostor/Miscellaneous/Gambler.cs
index ed000b208..c9ad5cf09 100644
--- a/Roles/Standard/Impostor/Miscellaneous/Gambler.cs
+++ b/Roles/Standard/Impostor/Miscellaneous/Gambler.cs
@@ -176,7 +176,7 @@ public class Gambler : RoleBase
killer.Notify(GetString("GamblerGet.IgnoreDefense"));
if ((target.Is(CustomRoles.Pestilence) && IgnorePestilence.GetBool())
- || (Veteran.VeteranInProtect.ContainsKey(target.PlayerId) && IgnoreVeteranAlert.GetBool())
+ || (Veteran.VeteranInProtect.Contains(target.PlayerId) && IgnoreVeteranAlert.GetBool())
|| (Medic.InProtect(target.PlayerId) && IgnoreMedicShield.GetBool())
|| ((target.Is(CustomRoles.Jinx) || target.Is(CustomRoles.CursedWolf)) && IgnoreCursedWolfAndJinx.GetBool()))
{
@@ -185,7 +185,7 @@ public class Gambler : RoleBase
}
if ((target.Is(CustomRoles.Pestilence) && !IgnorePestilence.GetBool())
- || (Veteran.VeteranInProtect.ContainsKey(target.PlayerId) && !IgnoreVeteranAlert.GetBool())
+ || (Veteran.VeteranInProtect.Contains(target.PlayerId) && !IgnoreVeteranAlert.GetBool())
|| (Medic.InProtect(target.PlayerId) && !IgnoreMedicShield.GetBool())
|| ((target.Is(CustomRoles.Jinx) || target.Is(CustomRoles.CursedWolf)) && !IgnoreCursedWolfAndJinx.GetBool()))
break;
diff --git a/Roles/Standard/Neutral/Killing/Agitator.cs b/Roles/Standard/Neutral/Killing/Agitator.cs
index 4d7054c29..63c580787 100644
--- a/Roles/Standard/Neutral/Killing/Agitator.cs
+++ b/Roles/Standard/Neutral/Killing/Agitator.cs
@@ -105,7 +105,7 @@ public class Agitator : RoleBase
if (AgitatorAutoReportBait.GetBool() && target.Is(CustomRoles.Bait)) return true;
- if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)))
+ if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.Contains(target.PlayerId)))
{
target.Kill(killer);
ResetBomb();
@@ -216,7 +216,7 @@ public class Agitator : RoleBase
if (target.PlayerId == LastBombedPlayer) return;
if (!AgitatorCanGetBombed.GetBool() && target.Is(CustomRoles.Agitator)) return;
- if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)))
+ if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.Contains(target.PlayerId)))
{
target.Kill(player);
ResetBomb();
diff --git a/Roles/Standard/Neutral/Killing/WeaponMaster.cs b/Roles/Standard/Neutral/Killing/WeaponMaster.cs
index ca31d7658..d819889ba 100644
--- a/Roles/Standard/Neutral/Killing/WeaponMaster.cs
+++ b/Roles/Standard/Neutral/Killing/WeaponMaster.cs
@@ -192,7 +192,7 @@ public class WeaponMaster : RoleBase
{
foreach (PlayerControl player in Main.EnumerateAlivePlayerControls())
{
- if (Pelican.IsEaten(player.PlayerId) || player == killer || target == player || player.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) continue;
+ if (Pelican.IsEaten(player.PlayerId) || player == killer || target == player || player.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.Contains(target.PlayerId)) continue;
if (Vector2.Distance(killer.Pos(), player.Pos()) <= Radius.GetFloat())
player.Suicide(PlayerState.DeathReason.Kill, killer);