From 5809b5ef7c28b04fddc974d2e90d930621865741 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Tue, 10 Feb 2026 11:05:51 +0100 Subject: [PATCH] Optimize & Cache GetPlainShipRoom + FastVector2.GetPlayersInRange --- .editorconfig | 2 +- Gamemodes/BedWars.cs | 2 +- Gamemodes/Deathrace.cs | 2 +- Gamemodes/NaturalDisasters.cs | 13 ++-- Modules/ExtendedPlayerControl.cs | 73 +++++++++++++++++-- Modules/Extensions/FastVector2.cs | 59 +++++++++++++++ Modules/Utils.cs | 10 +-- Roles/Standard/Coven/Banshee.cs | 3 +- Roles/Standard/Coven/Dreamweaver.cs | 7 +- Roles/Standard/Coven/Empress.cs | 3 +- Roles/Standard/Coven/Poache.cs | 23 +++--- Roles/Standard/Coven/Siren.cs | 38 ++++++---- Roles/Standard/Crewmate/Chaos/Randomizer.cs | 58 +++++---------- Roles/Standard/Crewmate/Chaos/ToiletMaster.cs | 2 +- Roles/Standard/Crewmate/Chaos/Tree.cs | 3 +- Roles/Standard/Crewmate/Investigate/Leery.cs | 3 +- Roles/Standard/Crewmate/Killing/Sentinel.cs | 2 +- Roles/Standard/Ghost/Bloodmoon.cs | 3 +- Roles/Standard/Impostor/Concealing/Stealth.cs | 5 +- Roles/Standard/Impostor/Concealing/Venerer.cs | 3 +- Roles/Standard/Impostor/Killing/Sapper.cs | 3 +- Roles/Standard/Neutral/Killing/Bubble.cs | 3 +- Roles/Standard/Neutral/Killing/Explosivist.cs | 3 +- Roles/Standard/Neutral/Killing/Magician.cs | 6 +- Roles/Standard/Neutral/Killing/Mycologist.cs | 3 +- 25 files changed, 221 insertions(+), 111 deletions(-) diff --git a/.editorconfig b/.editorconfig index d4c127e61..fd9f24939 100644 --- a/.editorconfig +++ b/.editorconfig @@ -267,7 +267,7 @@ resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlig resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = none resharper_inconsistent_naming_highlighting = suggestion resharper_invert_if_highlighting = none -resharper_loop_can_be_converted_to_query_highlighting = warning +resharper_loop_can_be_converted_to_query_highlighting = hint resharper_mvc_action_not_resolved_highlighting = warning resharper_mvc_area_not_resolved_highlighting = warning resharper_mvc_controller_not_resolved_highlighting = warning diff --git a/Gamemodes/BedWars.cs b/Gamemodes/BedWars.cs index 45e44e81c..69cbf3a54 100644 --- a/Gamemodes/BedWars.cs +++ b/Gamemodes/BedWars.cs @@ -1363,7 +1363,7 @@ public static class BedWars } } - PlayerControl[] playersInRadius = Utils.GetPlayersInRadius(ShopAndItemGeneratorRange, NetObject.Position).ToArray(); + PlayerControl[] playersInRadius = FastVector2.GetPlayersInRange(ShopAndItemGeneratorRange, NetObject.Position).ToArray(); foreach (PlayerControl pc in playersInRadius) { diff --git a/Gamemodes/Deathrace.cs b/Gamemodes/Deathrace.cs index c26743e00..0995d976f 100644 --- a/Gamemodes/Deathrace.cs +++ b/Gamemodes/Deathrace.cs @@ -397,7 +397,7 @@ public static class Deathrace PowerUp powerUp = data.PowerUps[0]; pc.RPCPlayCustomSound("Line"); data.PowerUps.RemoveAt(0); - PlayerControl[] playersInRange = Utils.GetPlayersInRadius(PowerUpEffectRange, pc.Pos()).Without(pc).ToArray(); + PlayerControl[] playersInRange = FastVector2.GetPlayersInRange(PowerUpEffectRange, pc.Pos()).Without(pc).ToArray(); switch (powerUp) { diff --git a/Gamemodes/NaturalDisasters.cs b/Gamemodes/NaturalDisasters.cs index e3490ad07..619fa60e3 100644 --- a/Gamemodes/NaturalDisasters.cs +++ b/Gamemodes/NaturalDisasters.cs @@ -695,12 +695,11 @@ public static class NaturalDisasters { LastHit = now; var hit = new Vector2(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Bottom, MapBounds.Y.Top)); - var cno = new Lightning(hit); - if (cno.playerControl.GetPlainShipRoom() != null) - cno.Despawn(); - else + if (hit.GetPlainShipRoom() == null) { + _ = new Lightning(hit); + foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls()) { if (FastVector2.DistanceWithinRange(pc.Pos(), hit, Range / 2f)) @@ -984,11 +983,9 @@ public static class NaturalDisasters { if (pc.onLadder || pc.inMovingPlat || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) continue; - PlainShipRoom room = pc.GetPlainShipRoom(); - - if (room != null && CollapsedRooms.Exists(x => x == room)) + if (CollapsedRooms.FindFirst(x => pc.IsInRoom(x), out PlainShipRoom collapsedRoom)) { - if (LastPosition.TryGetValue(pc.PlayerId, out Vector2 lastPos)) + if (LastPosition.TryGetValue(pc.PlayerId, out Vector2 lastPos) && !lastPos.IsInRoom(collapsedRoom)) { RPC.PlaySoundRPC(pc.PlayerId, Sounds.ImpDiscovered); pc.TP(lastPos); diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 206663675..292526906 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AmongUs.GameOptions; @@ -2209,25 +2210,85 @@ internal static class ExtendedPlayerControl return killerId == byte.MaxValue ? null : GetPlayerById(killerId); } + private static readonly Dictionary PlayerRoomCache = []; + + [SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")] public static PlainShipRoom GetPlainShipRoom(this PlayerControl pc) { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return null; + if (!pc.IsAlive()) return null; - Il2CppReferenceArray rooms = ShipStatus.Instance.AllRooms; - return rooms.Where(room => room.roomArea).FirstOrDefault(room => pc.Collider.IsTouching(room.roomArea)); + byte id = pc.PlayerId; + Vector3 pos = pc.Pos(); + + if (PlayerRoomCache.TryGetValue(id, out var cached)) + { + var area = cached.roomArea; + if (area && area.bounds.Contains(pos)) return cached; + } + + var rooms = ShipStatus.Instance.AllRooms; + for (int i = 0; i < rooms.Count; i++) + { + var room = rooms[i]; + var area = room.roomArea; + + if (area && area.bounds.Contains(pos)) + { + PlayerRoomCache[id] = room; + return room; + } + } + + PlayerRoomCache.Remove(id); + return null; + } + + [SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")] + public static PlainShipRoom GetPlainShipRoom(this Vector2 pos) + { + var rooms = ShipStatus.Instance.AllRooms; + for (int i = 0; i < rooms.Count; i++) + { + var room = rooms[i]; + var area = room.roomArea; + + if (area && area.bounds.Contains(pos)) + return room; + } + + return null; + } + + public static bool IsInRoom(this Vector2 pos, PlainShipRoom room) + { + if (room == null) return false; + var roomArea = room.roomArea; + return roomArea && roomArea.bounds.Contains(pos); + } + + public static bool IsInRoom(this Vector2 pos, SystemTypes roomId) + { + PlainShipRoom room = roomId.GetRoomClass(); + if (room == null) return false; + var roomArea = room.roomArea; + return roomArea && roomArea.bounds.Contains(pos); } public static bool IsInRoom(this PlayerControl pc, PlainShipRoom room) { - if (room == null || !room.roomArea) return false; - return pc.IsAlive() && pc.Collider.IsTouching(room.roomArea); + if (room == null) return false; + var roomArea = room.roomArea; + if (!roomArea) return false; + return pc.IsAlive() && roomArea.bounds.Contains(pc.Pos()); } public static bool IsInRoom(this PlayerControl pc, SystemTypes roomId) { if (!pc.IsAlive()) return false; PlainShipRoom room = roomId.GetRoomClass(); - return room != null && room.roomArea && pc.Collider.IsTouching(room.roomArea); + if (room == null) return false; + var roomArea = room.roomArea; + return roomArea && roomArea.bounds.Contains(pc.Pos()); } public static PlainShipRoom GetRoomClass(this SystemTypes systemTypes) diff --git a/Modules/Extensions/FastVector2.cs b/Modules/Extensions/FastVector2.cs index 983e150c4..7a9c93c51 100644 --- a/Modules/Extensions/FastVector2.cs +++ b/Modules/Extensions/FastVector2.cs @@ -13,6 +13,28 @@ public static class FastVector2 return dx * dx + dy * dy <= range * range; } + /// + /// Finds all living players from origin in a given range + /// + public static IEnumerable GetPlayersInRange(float range, Vector2 origin) + { + float rangeSq = range * range; + + foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls()) + { + if (pc.inVent) continue; + + Vector2 p = pc.Pos(); + float dx = p.x - origin.x; + float dy = p.y - origin.y; + float sq = dx * dx + dy * dy; + + if (sq > rangeSq) continue; + + yield return pc; + } + } + /// /// Finds the closest position to origin within the given range. /// Returns true if one was found. @@ -48,6 +70,43 @@ public static class FastVector2 return found; } + /// + /// Finds the closest living player to origin within the given range. + /// Returns true if one was found. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetClosestPlayerInRange( + in Vector2 origin, + float range, + out PlayerControl closest) + { + float rangeSq = range * range; + float minSq = float.MaxValue; + bool found = false; + closest = null; + + foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls()) + { + if (pc.inVent) continue; + + Vector2 p = pc.Pos(); + float dx = p.x - origin.x; + float dy = p.y - origin.y; + float sq = dx * dx + dy * dy; + + if (sq > rangeSq) continue; + + if (sq < minSq) + { + minSq = sq; + closest = pc; + found = true; + } + } + + return found; + } + /// /// Finds the closest position to origin within the given range from the values of the given dictionary, /// and returns the key assiciated with it in the dictionary. diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f0c338e4b..f0c377b75 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2457,16 +2457,16 @@ public static class Utils foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls()) { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return null; + if (!pc.IsAlive()) return null; + Vector3 position = pc.Pos(); Il2CppReferenceArray rooms = ShipStatus.Instance.AllRooms; - if (rooms == null) return null; foreach (PlainShipRoom room in rooms) { - if (!room.roomArea) continue; - - if (!pc.Collider.IsTouching(room.roomArea)) continue; + var roomArea = room.roomArea; + if (!roomArea) continue; + if (!roomArea.bounds.Contains(position)) continue; string roomName = GetString($"{room.RoomId}"); if (!playerRooms.TryAdd(roomName, 1)) playerRooms[roomName]++; diff --git a/Roles/Standard/Coven/Banshee.cs b/Roles/Standard/Coven/Banshee.cs index 39d49c3c2..0b4567c31 100644 --- a/Roles/Standard/Coven/Banshee.cs +++ b/Roles/Standard/Coven/Banshee.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; namespace EHR.Roles; @@ -61,7 +62,7 @@ public class Banshee : CovenBase { float radius = Radius.GetFloat(); Vector2 pos = pc.Pos(); - IEnumerable nearbyPlayers = Utils.GetPlayersInRadius(radius, pos).Without(pc); + IEnumerable nearbyPlayers = FastVector2.GetPlayersInRange(radius, pos).Without(pc); if (!HasNecronomicon) ScreechedPlayers = nearbyPlayers.Select(x => x.PlayerId).ToHashSet(); else nearbyPlayers.Do(x => x.Suicide(PlayerState.DeathReason.Deafened, pc)); diff --git a/Roles/Standard/Coven/Dreamweaver.cs b/Roles/Standard/Coven/Dreamweaver.cs index 73ea547db..381eb2bbc 100644 --- a/Roles/Standard/Coven/Dreamweaver.cs +++ b/Roles/Standard/Coven/Dreamweaver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; namespace EHR.Roles; @@ -82,11 +83,7 @@ public class Dreamweaver : CovenBase { if (lowLoad || GameStates.IsMeeting || ExileController.Instance || !Main.IntroDestroyed || !pc.IsAlive() || !pc.Is(CustomRoles.Insane) || Main.KillTimers[pc.PlayerId] > 0f) return; - Vector2 pos = pc.Pos(); - PlayerControl[] nearbyPlayers = Utils.GetPlayersInRadius(1.5f, pos).Without(pc).ToArray(); - if (nearbyPlayers.Length == 0) return; - - PlayerControl nearestPlayer = nearbyPlayers.MinBy(x => Vector2.Distance(x.Pos(), pos)); + if (!FastVector2.TryGetClosestPlayerInRange(pc.Pos(), 1.5f, out PlayerControl nearestPlayer)) return; RoleBase roleBase = Main.PlayerStates[pc.PlayerId].Role; Type type = roleBase.GetType(); diff --git a/Roles/Standard/Coven/Empress.cs b/Roles/Standard/Coven/Empress.cs index 5fe744b6c..85490f30e 100644 --- a/Roles/Standard/Coven/Empress.cs +++ b/Roles/Standard/Coven/Empress.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.Modules.Extensions; namespace EHR.Roles; @@ -204,7 +205,7 @@ public class Empress : CovenBase public override void OnPet(PlayerControl pc) { - PlayerControl[] playersInRadius = Utils.GetPlayersInRadius(CoverageRange.GetFloat(), pc.Pos()).Where(x => x.Is(Team.Coven)).ToArray(); + PlayerControl[] playersInRadius = FastVector2.GetPlayersInRange(CoverageRange.GetFloat(), pc.Pos()).Where(x => x.Is(Team.Coven)).ToArray(); float duration = CoverageDuration.GetFloat(); float delay = CoverageDelay.GetFloat(); LateTask.New(() => diff --git a/Roles/Standard/Coven/Poache.cs b/Roles/Standard/Coven/Poache.cs index 7c702785b..481268094 100644 --- a/Roles/Standard/Coven/Poache.cs +++ b/Roles/Standard/Coven/Poache.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; namespace EHR.Roles; @@ -14,7 +15,7 @@ public class Poache : CovenBase private static OptionItem CanVentAfterNecronomicon; public static HashSet PoachedPlayers = []; - private static List<(byte ID, long KillTimeStamp)> KillDelays = []; + private static List KillDelays = []; private byte PoacheId; @@ -61,20 +62,20 @@ public class Poache : CovenBase PoachedPlayers.Add(target.PlayerId); Utils.SendRPC(CustomRPC.SyncRoleData, PoacheId, 1, target.PlayerId); } - else KillDelays.Add((target.PlayerId, Utils.TimeStamp + KillDelay.GetInt())); + else + { + KillDelays.Add(target.PlayerId); + _ = new CountdownTimer(KillDelay.GetInt(), () => + { + if (!KillDelays.Remove(target.PlayerId)) return; + target.Suicide(PlayerState.DeathReason.Poison, realKiller: killer); + }, onCanceled: () => KillDelays.Remove(target.PlayerId)); + } killer.SetKillCooldown(AbilityCooldown.GetFloat()); return false; } - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || GameStates.IsMeeting || ExileController.Instance || !Main.IntroDestroyed || !pc.IsAlive() || !KillDelays.FindFirst(x => x.ID == pc.PlayerId, out (byte ID, long KillTimeStamp) killData) || Utils.TimeStamp < killData.KillTimeStamp) return; - - pc.Suicide(PlayerState.DeathReason.Poison, realKiller: PoacheId.GetPlayer()); - KillDelays.Remove(killData); - } - public override void AfterMeetingTasks() { PoachedPlayers.Clear(); @@ -83,7 +84,7 @@ public class Poache : CovenBase public override void OnReportDeadBody() { - KillDelays.ForEach(x => x.ID.GetPlayer()?.Suicide(PlayerState.DeathReason.Poison, realKiller: PoacheId.GetPlayer())); + KillDelays.ForEach(x => x.GetPlayer()?.Suicide(PlayerState.DeathReason.Poison, realKiller: PoacheId.GetPlayer())); KillDelays.Clear(); } diff --git a/Roles/Standard/Coven/Siren.cs b/Roles/Standard/Coven/Siren.cs index 136ff6526..b878a2b43 100644 --- a/Roles/Standard/Coven/Siren.cs +++ b/Roles/Standard/Coven/Siren.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.Modules.Extensions; namespace EHR.Roles; @@ -37,7 +38,7 @@ public class Siren : CovenBase "ECMO.OriginalTeam" ]; - private Dictionary EffectEndTS; + private HashSet AffectedPlayers; private byte SirenId; private Dictionary Stages; @@ -75,7 +76,7 @@ public class Siren : CovenBase { On = true; Stages = []; - EffectEndTS = []; + AffectedPlayers = []; SirenId = playerId; Instances.Add(this); } @@ -94,7 +95,7 @@ public class Siren : CovenBase { foreach (Siren instance in Instances) { - if (instance.EffectEndTS.ContainsKey(playerId)) + if (instance.AffectedPlayers.Contains(playerId)) { float vision = ReducedVision.GetFloat(); opt.SetVision(false); @@ -117,7 +118,7 @@ public class Siren : CovenBase public override bool OnVanish(PlayerControl pc) { - PlayerControl[] nearbyPlayers = Utils.GetPlayersInRadius(SingRange.GetFloat(), pc.Pos()).Without(pc).Where(x => !x.Is(Team.Coven)).ToArray(); + PlayerControl[] nearbyPlayers = FastVector2.GetPlayersInRange(SingRange.GetFloat(), pc.Pos()).Without(pc).Where(x => !x.Is(Team.Coven)).ToArray(); foreach (PlayerControl player in nearbyPlayers) { @@ -129,13 +130,28 @@ public class Siren : CovenBase switch (stage) { case 1: - EffectEndTS[player.PlayerId] = Utils.TimeStamp + Stage1EffectsLength.GetInt(); + AffectedPlayers.Add(player.PlayerId); + _ = new CountdownTimer(Stage1EffectsLength.GetInt(), () => + { + AffectedPlayers.Remove(player.PlayerId); + player.MarkDirtySettings(); + }, onCanceled: () => AffectedPlayers.Remove(player.PlayerId)); player.MarkDirtySettings(); Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player); break; case 2: + AffectedPlayers.Add(player.PlayerId); + _ = new CountdownTimer(Stage2EffectsLength.GetInt(), () => + { + AffectedPlayers.Remove(player.PlayerId); + ReportDeadBodyPatch.CanReport[player.PlayerId] = true; + player.MarkDirtySettings(); + }, onCanceled: () => + { + AffectedPlayers.Remove(player.PlayerId); + ReportDeadBodyPatch.CanReport[player.PlayerId] = true; + }); ReportDeadBodyPatch.CanReport[player.PlayerId] = false; - EffectEndTS[player.PlayerId] = Utils.TimeStamp + Stage2EffectsLength.GetInt(); player.MarkDirtySettings(); Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player); break; @@ -148,16 +164,6 @@ public class Siren : CovenBase return false; } - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !GameStates.IsInTask || ExileController.Instance || !pc.IsAlive() || !EffectEndTS.TryGetValue(pc.PlayerId, out var endTS) || endTS > Utils.TimeStamp) return; - - ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; - Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - EffectEndTS.Remove(pc.PlayerId); - pc.MarkDirtySettings(); - } - public override bool KnowRole(PlayerControl player, PlayerControl target) { if (base.KnowRole(player, target)) return true; diff --git a/Roles/Standard/Crewmate/Chaos/Randomizer.cs b/Roles/Standard/Crewmate/Chaos/Randomizer.cs index ed99e9626..d2afa7784 100644 --- a/Roles/Standard/Crewmate/Chaos/Randomizer.cs +++ b/Roles/Standard/Crewmate/Chaos/Randomizer.cs @@ -236,8 +236,16 @@ internal static class EffectExtenstions break; case Effect.TimeBomb: - Bombs.TryAdd(PickRandomPlayer().Pos(), (Utils.TimeStamp, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration))); - Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 1, Bombs.Last().Key, Bombs.Last().Value.PlaceTimeStamp, Bombs.Last().Value.ExplosionDelay); + Vector2 randomPlayerPos = PickRandomPlayer().Pos(); + int bombTime = IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration); + Bombs.TryAdd(randomPlayerPos, new CountdownTimer(bombTime, () => + { + foreach (PlayerControl pc in FastVector2.GetPlayersInRange(RandomFloat, randomPlayerPos)) + pc.Suicide(PlayerState.DeathReason.RNG, randomizer); + + Bombs.Remove(randomPlayerPos); + }, onTick: () => Utils.NotifyRoles(SendOption: SendOption.None), onCanceled: () => Bombs.Remove(randomPlayerPos))); + Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 1, randomPlayerPos, bombTime); break; case Effect.Tornado: Tornado.SpawnTornado(PickRandomPlayer()); @@ -457,7 +465,7 @@ internal class Randomizer : RoleBase public static Dictionary AllPlayerDefaultSpeed = []; public static Dictionary Rifts = []; - public static Dictionary Bombs = []; + public static Dictionary Bombs = []; public static Stopwatch TimeSinceLastMeeting; private static Dictionary LastTP = []; @@ -553,7 +561,7 @@ internal class Randomizer : RoleBase if (!ReportDeadBodyPatch.MeetingStarted && TimeSinceLastMeeting.IsRunning && TimeSinceLastMeeting.Elapsed.TotalSeconds > 10f) { - Effect effect = PickRandomEffect(pc.PlayerId); + Effect effect = PickRandomEffect(); effect.Apply(pc); } @@ -585,7 +593,7 @@ internal class Randomizer : RoleBase CurrentEffects[pc.PlayerId].TryAdd(effect, (Utils.TimeStamp, duration)); } - private static Effect PickRandomEffect(byte id) + private static Effect PickRandomEffect() { long now = Utils.TimeStamp; @@ -595,7 +603,6 @@ internal class Randomizer : RoleBase if (effect == Effect.GhostPlayer) { if (LastDeathEffect + 60 > now) return Effect.AddonRemove; - LastDeathEffect = now; } @@ -661,7 +668,7 @@ internal class Randomizer : RoleBase LastDeathEffect = Utils.TimeStamp; Rifts.Clear(); Bombs.Clear(); - Utils.SendRPC(CustomRPC.SyncRoleData, PlayerIdList[0], 3); + Utils.SendRPC(CustomRPC.SyncRoleData, PlayerIdList[0], 2); foreach (PlayerControl pc in Main.EnumeratePlayerControls()) { @@ -678,41 +685,15 @@ internal class Randomizer : RoleBase LastDeathEffect = Utils.TimeStamp; } - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) - { - try - { - if (lowLoad || !Exists || !GameStates.IsInTask || Bombs.Count == 0 || Main.HasJustStarted) return; - - long now = Utils.TimeStamp; - PlayerControl randomizer = Utils.GetPlayerById(PlayerIdList.FirstOrDefault()); - - foreach (KeyValuePair bomb in Bombs) - { - if (bomb.Value.PlaceTimeStamp + bomb.Value.ExplosionDelay < now) - { - IEnumerable players = Utils.GetPlayersInRadius(RandomFloat, bomb.Key); - foreach (PlayerControl pc in players) pc.Suicide(PlayerState.DeathReason.RNG, randomizer); - - Bombs.Remove(bomb.Key); - Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 2, bomb.Key); - } - } - } - catch (Exception ex) { Logger.Exception(ex, "Randomizer"); } - } - public void ReceiveRPC(MessageReader reader) { switch (reader.ReadPackedInt32()) { case 1: - Bombs.TryAdd(NetHelpers.ReadVector2(reader), (long.Parse(reader.ReadString()), reader.ReadPackedInt32())); + Vector2 key = NetHelpers.ReadVector2(reader); + Bombs.TryAdd(key, new CountdownTimer(reader.ReadPackedInt32(), () => Bombs.Remove(key), onCanceled: () => Bombs.Remove(key))); break; case 2: - Bombs.Remove(NetHelpers.ReadVector2(reader)); - break; - case 3: Bombs.Clear(); break; } @@ -720,11 +701,8 @@ internal class Randomizer : RoleBase public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) { - if (seer == null || seer.PlayerId != target.PlayerId || Bombs.Count == 0) return string.Empty; - - KeyValuePair bomb = Bombs.FirstOrDefault(x => Vector2.Distance(x.Key, seer.Pos()) <= 5f); - long time = bomb.Value.ExplosionDelay - (Utils.TimeStamp - bomb.Value.PlaceTimeStamp) + 1; - return time < 0 ? string.Empty : $"<#ffff00>⚠ {time}"; + if (seer == null || seer.PlayerId != target.PlayerId || Bombs.Count == 0 || !Bombs.FindFirst(x => FastVector2.DistanceWithinRange(x.Key, seer.Pos(), 5f), out KeyValuePair kvp)) return string.Empty; + return $"<#ffff00>⚠ {(int)kvp.Value.Remaining.TotalSeconds}"; } public override void OnCheckPlayerPosition(PlayerControl pc) diff --git a/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs b/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs index 520a33b90..7b7b8ffc7 100644 --- a/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs +++ b/Roles/Standard/Crewmate/Chaos/ToiletMaster.cs @@ -216,7 +216,7 @@ public class ToiletMaster : RoleBase int duration = RedPoopRoleBlockDuration.GetInt(); List affectedPlayers = []; - Utils.GetPlayersInRadius(RedPoopRadius.GetFloat(), pos).Without(pc).Do(x => + FastVector2.GetPlayersInRange(RedPoopRadius.GetFloat(), pos).Without(pc).Do(x => { x.BlockRole(duration); Main.AllPlayerSpeed[x.PlayerId] = Main.MinSpeed; diff --git a/Roles/Standard/Crewmate/Chaos/Tree.cs b/Roles/Standard/Crewmate/Chaos/Tree.cs index b8507be5e..d06842b0e 100644 --- a/Roles/Standard/Crewmate/Chaos/Tree.cs +++ b/Roles/Standard/Crewmate/Chaos/Tree.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; namespace EHR.Roles; @@ -84,7 +85,7 @@ public class Tree : RoleBase bool allKill = true; bool any = false; - Utils.GetPlayersInRadius(FallRadius.GetFloat(), pc.Pos()).Without(pc).Do(x => + FastVector2.GetPlayersInRange(FallRadius.GetFloat(), pc.Pos()).Without(pc).Do(x => { any = true; if (IRandom.Instance.Next(100) < chance) x.Suicide(PlayerState.DeathReason.Fall, pc); diff --git a/Roles/Standard/Crewmate/Investigate/Leery.cs b/Roles/Standard/Crewmate/Investigate/Leery.cs index 3dc3efa1b..f94f4aa5f 100644 --- a/Roles/Standard/Crewmate/Investigate/Leery.cs +++ b/Roles/Standard/Crewmate/Investigate/Leery.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; namespace EHR.Roles; @@ -52,7 +53,7 @@ public class Leery : RoleBase Vector2 pos = pc.Pos(); float radius = Radius.GetFloat(); - PlayerControl[] nearbyPlayers = Utils.GetPlayersInRadius(radius, pos).Without(pc).ToArray(); + PlayerControl[] nearbyPlayers = FastVector2.GetPlayersInRange(radius, pos).Without(pc).ToArray(); if (nearbyPlayers.Length == 0) { diff --git a/Roles/Standard/Crewmate/Killing/Sentinel.cs b/Roles/Standard/Crewmate/Killing/Sentinel.cs index 6225560fb..aa01e4ec7 100644 --- a/Roles/Standard/Crewmate/Killing/Sentinel.cs +++ b/Roles/Standard/Crewmate/Killing/Sentinel.cs @@ -26,7 +26,7 @@ public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRa 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 IEnumerable NearbyKillers => FastVector2.GetPlayersInRange(PatrolRadius, StartingPosition).Where(x => !x.Is(Team.Crewmate) && (!Sentinel.IsMadmate() || !x.Is(Team.Impostor)) && SentinelId != x.PlayerId); public void SetPlayer() { diff --git a/Roles/Standard/Ghost/Bloodmoon.cs b/Roles/Standard/Ghost/Bloodmoon.cs index 04495566a..0fe487d39 100644 --- a/Roles/Standard/Ghost/Bloodmoon.cs +++ b/Roles/Standard/Ghost/Bloodmoon.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.Modules.Extensions; using Hazel; namespace EHR.Roles; @@ -82,7 +83,7 @@ internal class Bloodmoon : IGhostRole if (pc.RpcCheckAndMurder(player, true)) player.Suicide(PlayerState.DeathReason.LossOfBlood, pc); } - Utils.GetPlayersInRadius(7f, pc.Pos()).DoIf(x => !x.Is(Team.Impostor), x => x.Notify(string.Format(Translator.GetString("BloodmoonNearYou"), CustomRoles.Bloodmoon.ToColoredString()), sendOption: SendOption.None)); + FastVector2.GetPlayersInRange(7f, pc.Pos()).DoIf(x => !x.Is(Team.Impostor), x => x.Notify(string.Format(Translator.GetString("BloodmoonNearYou"), CustomRoles.Bloodmoon.ToColoredString()), sendOption: SendOption.None)); } public static void OnMeetingStart() diff --git a/Roles/Standard/Impostor/Concealing/Stealth.cs b/Roles/Standard/Impostor/Concealing/Stealth.cs index 40a41cede..d4b22ea92 100644 --- a/Roles/Standard/Impostor/Concealing/Stealth.cs +++ b/Roles/Standard/Impostor/Concealing/Stealth.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; using UnityEngine; @@ -67,13 +68,13 @@ public sealed class Stealth : RoleBase Collider2D roomArea = room.roomArea; SystemTypes roomName = room.RoomId; RpcDarken(roomName); - return Main.EnumerateAlivePlayerControls().Where(player => player != StealthPC && player.Collider.IsTouching(roomArea)); + return Main.EnumerateAlivePlayerControls().Where(player => player != StealthPC && roomArea.bounds.Contains(player.Pos())); } private IEnumerable FindPlayersInRange() { var pos = StealthPC.Pos(); - var inRange = Utils.GetPlayersInRadius(blindingRadius, pos).Without(StealthPC); + var inRange = FastVector2.GetPlayersInRange(blindingRadius, pos).Without(StealthPC); if (excludeImpostors) inRange = inRange.Where(p => !p.Is(CustomRoleTypes.Impostor)); return inRange; } diff --git a/Roles/Standard/Impostor/Concealing/Venerer.cs b/Roles/Standard/Impostor/Concealing/Venerer.cs index b2c07491e..35e49dbaf 100644 --- a/Roles/Standard/Impostor/Concealing/Venerer.cs +++ b/Roles/Standard/Impostor/Concealing/Venerer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; using UnityEngine; @@ -133,7 +134,7 @@ public class Venerer : RoleBase yield break; } - HashSet nearbyPlayers = Utils.GetPlayersInRadius(radius, pc.Pos()).Without(pc).Select(x => x.PlayerId).ToHashSet(); + HashSet nearbyPlayers = FastVector2.GetPlayersInRange(radius, pc.Pos()).Without(pc).Select(x => x.PlayerId).ToHashSet(); frozenPlayers.Except(nearbyPlayers).Do(x => { PlayerControl p = Utils.GetPlayerById(x); diff --git a/Roles/Standard/Impostor/Killing/Sapper.cs b/Roles/Standard/Impostor/Killing/Sapper.cs index 5d7ed7ef1..9e976ec19 100644 --- a/Roles/Standard/Impostor/Killing/Sapper.cs +++ b/Roles/Standard/Impostor/Killing/Sapper.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; using static EHR.Options; using static EHR.Translator; using static EHR.Utils; @@ -131,7 +132,7 @@ public class Sapper : RoleBase foreach (KeyValuePair bomb in Bombs.Where(bomb => bomb.Value + Delay.GetInt() < TimeStamp).ToArray()) { var b = false; - IEnumerable players = GetPlayersInRadius(Radius.GetFloat(), bomb.Key); + IEnumerable players = FastVector2.GetPlayersInRange(Radius.GetFloat(), bomb.Key); foreach (PlayerControl tg in players) { diff --git a/Roles/Standard/Neutral/Killing/Bubble.cs b/Roles/Standard/Neutral/Killing/Bubble.cs index 1efd69b34..fe1e0d153 100644 --- a/Roles/Standard/Neutral/Killing/Bubble.cs +++ b/Roles/Standard/Neutral/Killing/Bubble.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; using static EHR.Options; using static EHR.Utils; @@ -135,7 +136,7 @@ internal class Bubble : RoleBase return; } - IEnumerable players = GetPlayersInRadius(ExplosionRadius.GetFloat(), encasedPc.Pos()); + IEnumerable players = FastVector2.GetPlayersInRange(ExplosionRadius.GetFloat(), encasedPc.Pos()); var numDied = 0; diff --git a/Roles/Standard/Neutral/Killing/Explosivist.cs b/Roles/Standard/Neutral/Killing/Explosivist.cs index d45754cbc..0c841ad01 100644 --- a/Roles/Standard/Neutral/Killing/Explosivist.cs +++ b/Roles/Standard/Neutral/Killing/Explosivist.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using EHR.Modules.Extensions; namespace EHR.Roles; @@ -106,7 +107,7 @@ public class Explosivist : RoleBase if (Explosive != null && (ExplodeTS <= Utils.TimeStamp || !pc.IsAlive())) { - Utils.GetPlayersInRadius(ExplosionRadius.GetFloat(), Explosive.Position).Without(pc).Do(x => x.Suicide(PlayerState.DeathReason.Bombed, pc)); + FastVector2.GetPlayersInRange(ExplosionRadius.GetFloat(), Explosive.Position).Without(pc).Do(x => x.Suicide(PlayerState.DeathReason.Bombed, pc)); pc.RevertFreeze(RealPosition); diff --git a/Roles/Standard/Neutral/Killing/Magician.cs b/Roles/Standard/Neutral/Killing/Magician.cs index c6a823ce8..f7d34d710 100644 --- a/Roles/Standard/Neutral/Killing/Magician.cs +++ b/Roles/Standard/Neutral/Killing/Magician.cs @@ -192,7 +192,7 @@ public class Magician : RoleBase case 1: // Slowness for everyone nearby if (TempSpeeds.Count > 0) RevertSpeedChanges(true); - IEnumerable list = GetPlayersInRadius(SlownessRadius.GetFloat(), pc.Pos()); + IEnumerable list = FastVector2.GetPlayersInRange(SlownessRadius.GetFloat(), pc.Pos()); foreach (PlayerControl x in list) { @@ -260,7 +260,7 @@ public class Magician : RoleBase break; case 6: // Blind everyone nearby - IEnumerable players = GetPlayersInRadius(BlindRadius.GetFloat(), pc.Pos()); + IEnumerable players = FastVector2.GetPlayersInRange(BlindRadius.GetFloat(), pc.Pos()); foreach (PlayerControl x in players) { @@ -370,7 +370,7 @@ public class Magician : RoleBase foreach (KeyValuePair bomb in Bombs.Where(bomb => bomb.Value + BombDelay.GetInt() < TimeStamp).ToArray()) { var b = false; - IEnumerable players = GetPlayersInRadius(BombRadius.GetFloat(), bomb.Key); + IEnumerable players = FastVector2.GetPlayersInRange(BombRadius.GetFloat(), bomb.Key); foreach (PlayerControl tg in players) { diff --git a/Roles/Standard/Neutral/Killing/Mycologist.cs b/Roles/Standard/Neutral/Killing/Mycologist.cs index 9b58c8dd4..cc2e13edd 100644 --- a/Roles/Standard/Neutral/Killing/Mycologist.cs +++ b/Roles/Standard/Neutral/Killing/Mycologist.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Modules.Extensions; using Hazel; using static EHR.Options; using static EHR.Translator; @@ -152,7 +153,7 @@ internal class Mycologist : RoleBase LateTask.New(() => { - InfectedPlayers.AddRange(GetPlayersInRadius(InfectRadius.GetFloat(), MycologistPC.Pos()).Select(x => x.PlayerId)); + InfectedPlayers.AddRange(FastVector2.GetPlayersInRange(InfectRadius.GetFloat(), MycologistPC.Pos()).Select(x => x.PlayerId)); SendRPC(); NotifyRoles(SpecifySeer: MycologistPC); }, InfectTime.GetFloat(), "Mycologist Infect Time");