diff --git a/CustomRoles.cs b/CustomRoles.cs index d5da6ad29..c0de42c18 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -234,7 +234,6 @@ public enum CustomRoles Medic, Medium, Merchant, - Telecommunication, Mole, Monarch, Mortician, @@ -277,6 +276,7 @@ public enum CustomRoles Swapper, Tar, TaskManager, + Telecommunication, Telekinetic, Tether, TimeManager, diff --git a/Gamemodes/BedWars.cs b/Gamemodes/BedWars.cs index eba391f5f..ee9cb71c3 100644 --- a/Gamemodes/BedWars.cs +++ b/Gamemodes/BedWars.cs @@ -450,7 +450,7 @@ public static class BedWars yield return WaitFrameIfNecessary(); - Utils.SetChatVisibleForAll(); + Main.AllAlivePlayerControls.SetChatVisible(true); yield return WaitFrameIfNecessary(); diff --git a/Gamemodes/Deathrace.cs b/Gamemodes/Deathrace.cs index 2a08e84ae..96a3bdf54 100644 --- a/Gamemodes/Deathrace.cs +++ b/Gamemodes/Deathrace.cs @@ -263,7 +263,7 @@ public static class Deathrace Utils.NotifyRoles(); Main.AllPlayerSpeed.SetAllValues(Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod)); - Main.EnumerateAlivePlayerControls().Do(x => x.SyncSettings()); + Utils.SyncAllSettings(); GameGoing = true; } diff --git a/Gamemodes/KingOfTheZones.cs b/Gamemodes/KingOfTheZones.cs index 7c69de84c..91bed6d33 100644 --- a/Gamemodes/KingOfTheZones.cs +++ b/Gamemodes/KingOfTheZones.cs @@ -249,7 +249,7 @@ public static class KingOfTheZones AllRooms.RemoveWhere(x => x.ToString().Contains("Decontamination")); if (SubmergedCompatibility.IsSubmerged()) AllRooms.RemoveWhere(x => (byte)x > 135); - Zones = DefaultZones[Main.CurrentMap][NumZones.GetInt() - 1]; + Zones = Main.LIMap ? ShipStatus.Instance.AllRooms.Select(x => x.RoomId).TakeRandomToList(NumZones.GetInt()) : DefaultZones[Main.CurrentMap][NumZones.GetInt() - 1]; Points = Enum.GetValues().ToDictionary(x => x, _ => 0); PlayerPoints = Main.PlayerStates.Keys.ToDictionary(x => x, _ => 0); @@ -449,24 +449,41 @@ public static class KingOfTheZones yield return StartingCountdown(); } - KeyValuePair[] spawnsConst = RandomSpawn.SpawnMap.GetSpawnMap().Positions.ExceptBy(Zones, x => x.Key).ToArray(); - List> spawns = spawnsConst.ToList(); - - foreach (PlayerControl player in aapc) + if (!Main.LIMap) { - try + KeyValuePair[] spawnsConst = RandomSpawn.SpawnMap.GetSpawnMap().Positions.ExceptBy(Zones, x => x.Key).ToArray(); + List> spawns = spawnsConst.ToList(); + + foreach (PlayerControl player in aapc) { - player.SetKillCooldown(GetKillCooldown(player)); + try + { + player.SetKillCooldown(GetKillCooldown(player)); - KeyValuePair spawn = spawns.RandomElement(); - player.TP(spawn.Value); - spawns.RemoveAll(x => x.Key == spawn.Key); + KeyValuePair spawn = spawns.RandomElement(); + player.TP(spawn.Value); + spawns.RemoveAll(x => x.Key == spawn.Key); - if (spawns.Count == 0) spawns = spawnsConst.ToList(); + if (spawns.Count == 0) spawns = spawnsConst.ToList(); + } + catch (Exception e) { Utils.ThrowException(e); } + + yield return null; } - catch (Exception e) { Utils.ThrowException(e); } + } + else + { + foreach (PlayerControl player in aapc) + { + try + { + player.SetKillCooldown(GetKillCooldown(player)); + player.TPToRandomVent(); + } + catch (Exception e) { Utils.ThrowException(e); } - yield return null; + yield return null; + } } TimeLeft = GameEndsByTimeLimit.GetBool() ? MaxGameLength.GetInt() : 0; @@ -817,7 +834,10 @@ public static class KingOfTheZones if (!player) continue; player.ReviveFromTemporaryExile(); - player.TP(RandomSpawn.SpawnMap.GetSpawnMap().Positions.ExceptBy(Zones, x => x.Key).RandomElement().Value); + + if (!Main.LIMap) player.TP(RandomSpawn.SpawnMap.GetSpawnMap().Positions.ExceptBy(Zones, x => x.Key).RandomElement().Value); + else player.TPToRandomVent(); + RPC.PlaySoundRPC(player.PlayerId, Sounds.SpawnSound); Utils.NotifyRoles(SpecifyTarget: player, SendOption: SendOption.None); diff --git a/Gamemodes/Mingle.cs b/Gamemodes/Mingle.cs index 94baab8cb..81bdac0fb 100644 --- a/Gamemodes/Mingle.cs +++ b/Gamemodes/Mingle.cs @@ -370,7 +370,7 @@ public static class Mingle if (TimeEndTS == now && PlayerControl.AllPlayerControls.Count < 50) { Main.AllPlayerSpeed.SetAllValues(Main.MinSpeed); - Main.EnumerateAlivePlayerControls().Do(x => x.SyncSettings()); + Utils.SyncAllSettings(); } else if (TimeEndTS < now) { diff --git a/Gamemodes/NaturalDisasters.cs b/Gamemodes/NaturalDisasters.cs index 39905f8f2..9365c53c2 100644 --- a/Gamemodes/NaturalDisasters.cs +++ b/Gamemodes/NaturalDisasters.cs @@ -139,6 +139,8 @@ public static class NaturalDisasters if (Options.CurrentGameMode != CustomGameMode.NaturalDisasters && !Options.IntegrateNaturalDisasters.GetBool()) return; FixedUpdatePatch.WaitTime = Chat ? 10 : 5; + + RebuildSuffixText(); List rooms = Main.LIMap ? ShipStatus.Instance.AllRooms.Select(x => new Vector2(x.transform.position.x, x.transform.position.y)).ToList() @@ -151,8 +153,6 @@ public static class NaturalDisasters const float extend = 5f; MapBounds = ((x.Min() - extend, x.Max() + extend), (y.Min() - extend, y.Max() + extend)); - - RebuildSuffixText(); } public static void ApplyGameOptions(IGameOptions opt, byte id) diff --git a/Gamemodes/Quiz.cs b/Gamemodes/Quiz.cs index efeff8bff..8ec0a8d50 100644 --- a/Gamemodes/Quiz.cs +++ b/Gamemodes/Quiz.cs @@ -539,7 +539,7 @@ public static class Quiz if (Chat) { - Utils.SetChatVisibleForAll(); + Main.AllAlivePlayerControls.SetChatVisible(true); yield return new WaitForSecondsRealtime(1f); } diff --git a/Gamemodes/RoomRush.cs b/Gamemodes/RoomRush.cs index 6b7be191a..eab7ab3c3 100644 --- a/Gamemodes/RoomRush.cs +++ b/Gamemodes/RoomRush.cs @@ -713,7 +713,8 @@ public static class RoomRush if (WinByPointsInsteadOfDeaths.GetBool()) Points = Main.EnumeratePlayerControls().ToDictionary(x => x.PlayerId, _ => 0); - Map = RandomSpawn.SpawnMap.GetSpawnMap(); + try { Map = RandomSpawn.SpawnMap.GetSpawnMap(); } + catch { } yield return new WaitForSecondsRealtime(Main.CurrentMap == MapNames.Airship ? 8f : 3f); @@ -850,7 +851,7 @@ public static class RoomRush { MapNames.MiraHQ => previous is SystemTypes.Laboratory or SystemTypes.Reactor ^ RoomGoal is SystemTypes.Laboratory or SystemTypes.Reactor, MapNames.Polus => previous == SystemTypes.Specimens || RoomGoal == SystemTypes.Specimens, - (MapNames)6 => (previous == (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.Ballast) ^ (RoomGoal == (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.Ballast), + (MapNames)6 => previous == (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.Ballast || RoomGoal == (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.Ballast, _ => false }; diff --git a/Gamemodes/Snowdown.cs b/Gamemodes/Snowdown.cs index 35dbf9aa6..18cb52587 100644 --- a/Gamemodes/Snowdown.cs +++ b/Gamemodes/Snowdown.cs @@ -221,11 +221,14 @@ public static class Snowdown Data = Main.EnumeratePlayerControls().ToDictionary(x => x.PlayerId, _ => new PlayerData()); Snowballs = []; - Dictionary.ValueCollection rooms = RandomSpawn.SpawnMap.GetSpawnMap().Positions?.Values; + List rooms = Main.LIMap + ? ShipStatus.Instance.AllRooms.Select(x => new Vector2(x.transform.position.x, x.transform.position.y)).ToList() + : RandomSpawn.SpawnMap.GetSpawnMap().Positions?.Values.ToList(); + if (rooms == null) return; - float[] x = rooms.Select(r => r.x).ToArray(); - float[] y = rooms.Select(r => r.y).ToArray(); + List x = rooms.ConvertAll(r => r.x); + List y = rooms.ConvertAll(r => r.y); const float extend = 5f; MapBounds = ((x.Min() - extend, x.Max() + extend), (y.Min() - extend, y.Max() + extend)); diff --git a/Gamemodes/SoloPVP.cs b/Gamemodes/SoloPVP.cs index f58ca7621..5817a0426 100644 --- a/Gamemodes/SoloPVP.cs +++ b/Gamemodes/SoloPVP.cs @@ -244,7 +244,9 @@ internal static class SoloPVP LastHurt[pc.PlayerId] = Utils.TimeStamp; pc.ReviveFromTemporaryExile(); RPC.PlaySoundRPC(pc.PlayerId, Sounds.SpawnSound); - SpawnMap.GetSpawnMap().RandomTeleport(pc); + + if (Main.LIMap) pc.TPToRandomVent(); + else SpawnMap.GetSpawnMap().RandomTeleport(pc); } private static IEnumerator OnPlayerDead(PlayerControl target) @@ -256,7 +258,9 @@ internal static class SoloPVP PlayerHP[target.PlayerId] = PlayerHPMax[target.PlayerId]; LastHurt[target.PlayerId] = Utils.TimeStamp; RPC.PlaySoundRPC(target.PlayerId, Sounds.SpawnSound); - SpawnMap.GetSpawnMap().RandomTeleport(target); + + if (Main.LIMap) target.TPToRandomVent(); + else SpawnMap.GetSpawnMap().RandomTeleport(target); yield break; } diff --git a/Gamemodes/TheMindGame.cs b/Gamemodes/TheMindGame.cs index 01e69a49f..1e98f768d 100644 --- a/Gamemodes/TheMindGame.cs +++ b/Gamemodes/TheMindGame.cs @@ -340,7 +340,7 @@ public static class TheMindGame Main.Instance.StartCoroutine(PreventMovingOutOfGroupRooms()); - Utils.SetChatVisibleForAll(); + Main.AllAlivePlayerControls.SetChatVisible(true); yield return NotifyEveryone("TMG.Tutorial.Basics", 6); if (Stop) yield break; diff --git a/Modules/AutoHaunt.cs b/Modules/AutoHaunt.cs index 5f6172427..1e83cc1fb 100644 --- a/Modules/AutoHaunt.cs +++ b/Modules/AutoHaunt.cs @@ -41,21 +41,29 @@ public static class AutoHaunt { while (Main.AutoHaunt.Value) { + float waitTime = 5f; + if (HudManager.InstanceExists && GameStates.IsInTask && !ExileController.Instance && !AntiBlackout.SkipTasks && !PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.Data.RoleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost && !ExtendedPlayerControl.TempExiled.Contains(PlayerControl.LocalPlayer.PlayerId)) { - if (HauntMenuMinigameStartPatch.Instance != null) + if (HauntMenuMinigameStartPatch.Instance) { PlayerControl currentTarget = HauntMenuMinigameStartPatch.Instance.HauntTarget; PlayerControl preferredTarget = GetPreferredHauntTarget(); - if (preferredTarget != null && currentTarget != preferredTarget) + if (preferredTarget && currentTarget != preferredTarget) HauntMenuMinigameSetHauntTargetPatch.Prefix(HauntMenuMinigameStartPatch.Instance, preferredTarget); + + if (Options.CurrentGameMode == CustomGameMode.HotPotato) + waitTime = 0.5f; } else + { HudManager.Instance.AbilityButton.DoClick(); + waitTime = 1f; + } } - yield return new WaitForSecondsRealtime(5f); + yield return new WaitForSecondsRealtime(waitTime); } if (GameStates.IsInTask && !ExileController.Instance && !AntiBlackout.SkipTasks && !PlayerControl.LocalPlayer.IsAlive() && HauntMenuMinigameStartPatch.Instance != null) diff --git a/Modules/ClientControlGUI.cs b/Modules/ClientControlGUI.cs index 8dd4eea3d..a44ff46cc 100644 --- a/Modules/ClientControlGUI.cs +++ b/Modules/ClientControlGUI.cs @@ -634,7 +634,7 @@ public class ClientControlGUI : MonoBehaviour Btn(ref y, Label("Open Your Chat", "SHIFT + ENTER + C"), _sHost, () => HudManager.Instance.Chat.SetVisible(true) ); - Btn(ref y, Label("Open Chat for All", "CTRL + SHIFT + ENTER + C"), _sHost, Utils.SetChatVisibleForAll); + Btn(ref y, Label("Open Chat for All", "CTRL + SHIFT + ENTER + C"), _sHost, () => Main.AllAlivePlayerControls.SetChatVisible(true)); if (noGameEnd) Btn(ref y, Label("Force Game End", "SHIFT + ENTER + L"), _sDanger, () => diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs index d0e7b8021..c8bd5beb1 100644 --- a/Modules/CustomNetObject.cs +++ b/Modules/CustomNetObject.cs @@ -122,16 +122,47 @@ namespace EHR catch (Exception e) { Utils.ThrowException(e); } } - protected void Hide(PlayerControl player) + protected void Hide(IEnumerable players) { - if (!AmongUsClient.Instance.AmHost) return; + bool hasValue = false; + MessageWriter packedWriter = MessageWriter.Get(SendOption.Reliable); + packedWriter.StartMessage(26); + packedWriter.WritePacked(AmongUsClient.Instance.GameId); + + foreach (PlayerControl player in players) + { + if (packedWriter.Length > 500) + { + hasValue = false; + packedWriter.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(packedWriter); + packedWriter.Clear(SendOption.Reliable); + packedWriter.StartMessage(26); + packedWriter.WritePacked(AmongUsClient.Instance.GameId); + } + + hasValue |= Hide(player, packedWriter); + } + + if (hasValue) + { + packedWriter.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(packedWriter); + } + + packedWriter.Recycle(); + } + + private bool Hide(PlayerControl player, MessageWriter packedWriter) + { + if (!AmongUsClient.Instance.AmHost) return false; Logger.Info($" Hide Custom Net Object {GetType().Name} (ID {Id}) from {player.GetNameWithRole()}", "CNO.Hide"); if (player.AmOwner) { LateTask.New(() => playerControl.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(false), 0.1f); playerControl.Visible = false; - return; + return false; } if (this is not ShapeshiftMenuElement) @@ -149,16 +180,15 @@ namespace EHR }, 0.4f); } - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(player.OwnerId); - writer.StartMessage(5); - writer.WritePacked(playerControl.NetId); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + packedWriter.StartMessage(6); + packedWriter.Write(AmongUsClient.Instance.GameId); + packedWriter.WritePacked(player.OwnerId); + packedWriter.StartMessage(5); + packedWriter.WritePacked(playerControl.NetId); + packedWriter.EndMessage(); + packedWriter.EndMessage(); + + return true; } protected virtual void OnFixedUpdate() @@ -428,7 +458,7 @@ namespace EHR { SpawnTimeStamp = Utils.TimeStamp; CreateNetObject("\u2588\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588\u2588\u2588
\u2588<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588\u2588
<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588
<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#636363>\u2588<#636363>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588
<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#636363>\u2588<#636363>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588
<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588
\u2588<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588\u2588
\u2588\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588\u2588\u2588
", position); - LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId)), 0.4f); } protected override void OnFixedUpdate() @@ -463,7 +493,7 @@ namespace EHR public PlayerDetector(Vector2 position, List visibleList, out int id) { CreateNetObject("\u2588\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588\u2588\u2588
\u2588<#33e6b0>\u2588\u2588\u2588\u2588\u2588<#33e6b0>\u2588\u2588
<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588<#33e6b0>\u2588<#000000>\u2588<#000000>\u2588<#33e6b0>\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588<#33e6b0>\u2588<#000000>\u2588<#000000>\u2588<#33e6b0>\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588
\u2588<#33e6b0>\u2588\u2588\u2588\u2588\u2588<#33e6b0>\u2588\u2588
\u2588\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588\u2588\u2588
", position); - LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId)), 0.4f); id = Id; } } @@ -477,7 +507,7 @@ namespace EHR Resource = resource; (char Icon, Color Color) data = Adventurer.ResourceDisplayData[resource]; CreateNetObject($"{Utils.ColorString(data.Color, data.Icon.ToString())}", position); - LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId)), 0.4f); } } @@ -486,7 +516,7 @@ namespace EHR internal Toilet(Vector2 position, IEnumerable hideList) { CreateNetObject("\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588\u2588
<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588
<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588
\u2588<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588\u2588
\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#d3d4ce>\u2588<#dedede>\u2588<#dedede>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588\u2588
\u2588<#bfbfbf>\u2588<#454545>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#bfbfbf>\u2588
\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#bfbfbf>\u2588
\u2588\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588
\u2588\u2588\u2588<#dedede>\u2588<#dedede>\u2588<#dedede>\u2588<#dedede>\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588<#dedede>\u2588<#dedede>\u2588\u2588\u2588\u2588\u2588
", position); - LateTask.New(() => hideList.Do(Hide), 0.4f); + LateTask.New(() => Hide(hideList), 0.4f); } } @@ -509,7 +539,7 @@ namespace EHR public SprayedArea(Vector2 position, IEnumerable visibleList) { CreateNetObject("\u2588\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588\u2588
\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588
<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588
<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588
\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588
\u2588\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588\u2588
", position); - LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId)), 0.4f); } public override void OnMeeting() @@ -523,7 +553,7 @@ namespace EHR public CatcherTrap(Vector2 position, PlayerControl catcher) { CreateNetObject("\u2588\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588\u2588
\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588
<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588
<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588
\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588
\u2588\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588\u2588
", position); - LateTask.New(() => Main.EnumerateAlivePlayerControls().Without(catcher).Do(Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumerateAlivePlayerControls().Without(catcher)), 0.4f); } public override void OnMeeting() @@ -553,7 +583,7 @@ namespace EHR public SoulObject(Vector2 position, PlayerControl whisperer) { CreateNetObject("\u2588\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588\u2588
\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588
<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#cfcfcf>\u2588<#cfcfcf>\u2588
<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588
\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588
\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588\u2588<#fcfcfc>\u2588
", position); - LateTask.New(() => Main.EnumerateAlivePlayerControls().Without(whisperer).Do(Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumerateAlivePlayerControls().Without(whisperer)), 0.4f); } } @@ -776,7 +806,7 @@ namespace EHR public ShapeshiftMenuElement(byte visibleTo) { CreateNetObject(string.Empty, new Vector2(0f, 0f)); - LateTask.New(() => Main.EnumeratePlayerControls().DoIf(x => x.PlayerId != visibleTo, Hide), 0.4f); + LateTask.New(() => Hide(Main.EnumeratePlayerControls().Where(x => x.PlayerId != visibleTo)), 0.4f); } } diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index 9ef5294e7..97a111ecb 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -103,6 +103,9 @@ internal static class CustomRoleSelector switch (role) { + case CustomRoles.Bargainer when Main.LIMap: + case CustomRoles.AntiAdminer when Main.LIMap: + case CustomRoles.CameraMan when Main.LIMap: case CustomRoles.Ventriloquist when GameStates.CurrentServerType == GameStates.ServerType.Vanilla: case CustomRoles.Weatherman when Main.LIMap || GameStates.CurrentServerType == GameStates.ServerType.Vanilla: case CustomRoles.Doctor when Options.EveryoneSeesDeathReasons.GetBool(): diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 8f80bd101..8df151200 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -20,6 +20,7 @@ public class CustomRpcSender { BeforeInit = 0, // Cannot do anything before initialization Ready, // Ready to send - StartMessage and SendMessage can be executed + InRootPackedMessage, // State where only GameDataTo submessages can be started InRootMessage, // State between StartMessage and EndMessage - StartRpc and EndMessage can be executed InRpc, // State between StartRpc and EndRpc - Write and EndRpc can be executed Finished // Nothing can be done after sending @@ -39,6 +40,8 @@ public class CustomRpcSender // -2: Not set private int currentRpcTarget; + private bool packed; + private State currentState = State.BeforeInit; private int messages; public MessageWriter stream; @@ -54,6 +57,7 @@ public class CustomRpcSender this.sendOption = sendOption; this.log = log; currentRpcTarget = -2; + packed = false; onSendDelegate = () => { }; messages = 0; @@ -99,9 +103,19 @@ public class CustomRpcSender { if (targetClientId == -2) targetClientId = -1; - if (currentState is not State.Ready and not State.InRootMessage) + if (currentState is not State.Ready and not State.InRootPackedMessage and not State.InRootMessage) { - var errorMsg = $"Tried to start RPC automatically, but State is not Ready or InRootMessage (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})"; + var errorMsg = $"Tried to start RPC automatically, but State is not Ready or InRootPackedMessage or InRootMessage (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + if (currentState == State.InRootPackedMessage && targetClientId < 0) + { + var errorMsg = $"Tried to start RPC automatically, but State is InRootPackedMessage and the requested targetClientId is negative. Only GameDataTo messages can be started in this state. (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); @@ -114,11 +128,23 @@ public class CustomRpcSender // StartMessage processing if (currentState == State.InRootMessage) EndMessage(startNew: true); - else if (messages > 0) // state is Ready + else if (messages > 0) // state is Ready or InRootPackedMessage { - doneStreams.Add(stream); - stream = MessageWriter.Get(sendOption); - messages = 0; + if (currentState == State.InRootPackedMessage) + { + stream.EndMessage(); + currentState = State.Ready; + doneStreams.Add(stream); + stream = MessageWriter.Get(sendOption); + messages = 0; + StartPackedMessage(); // assume the next message should be in a PackedGameDataTo message as well + } + else // state is Ready + { + doneStreams.Add(stream); + stream = MessageWriter.Get(sendOption); + messages = 0; + } } StartMessage(targetClientId); @@ -132,6 +158,8 @@ public class CustomRpcSender public void SendMessage(bool dispose = false) { if (currentState == State.InRootMessage) EndMessage(); + + if (currentState == State.InRootPackedMessage) EndMessage(); if (currentState != State.Ready && !dispose) { @@ -170,6 +198,9 @@ public class CustomRpcSender onSendDelegate(); } + packed = false; + currentRpcTarget = -2; + messages = 0; currentState = State.Finished; stream.Recycle(); } @@ -178,7 +209,7 @@ public class CustomRpcSender { if (currentState != State.InRpc) { - var errorMsg = $"Tried to write RPC, but State is not Write (in: \"{name}\")"; + var errorMsg = $"Tried to write RPC, but State is not InRpc (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); @@ -197,9 +228,19 @@ public class CustomRpcSender public CustomRpcSender StartMessage(int targetClientId = -1) { - if (currentState != State.Ready) + if (currentState is not State.Ready and not State.InRootPackedMessage) { - var errorMsg = $"Tried to start Message but State is not Ready (in: \"{name}\")"; + var errorMsg = $"Tried to start Message but State is not Ready or InRootPackedMessage (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + if (currentState == State.InRootPackedMessage && targetClientId < 0) + { + var errorMsg = $"Tried to start RPC automatically, but State is InRootPackedMessage and the requested targetClientId is negative. Only GameDataTo messages can be started in this state. (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); @@ -209,9 +250,20 @@ public class CustomRpcSender if (stream.Length > 500) { - doneStreams.Add(stream); - stream = MessageWriter.Get(sendOption); - messages = 0; + if (currentState == State.InRootPackedMessage) + { + stream.EndMessage(); + doneStreams.Add(stream); + stream = MessageWriter.Get(sendOption); + messages = 0; + StartPackedMessage(); + } + else + { + doneStreams.Add(stream); + stream = MessageWriter.Get(sendOption); + messages = 0; + } } if (targetClientId < 0) @@ -233,11 +285,11 @@ public class CustomRpcSender return this; } - public CustomRpcSender EndMessage(bool startNew = false) + public CustomRpcSender StartPackedMessage() { - if (currentState != State.InRootMessage) + if (currentState != State.Ready) { - var errorMsg = $"Tried to exit Message but State is not InRootMessage (in: \"{name}\")"; + var errorMsg = $"Tried to start Message but State is not Ready (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); @@ -245,17 +297,65 @@ public class CustomRpcSender throw new InvalidOperationException(errorMsg); } - stream.EndMessage(); - - if (startNew) + if (stream.Length > 500) { doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; } + stream.StartMessage(26); + stream.WritePacked(AmongUsClient.Instance.GameId); + currentRpcTarget = -2; - currentState = State.Ready; + currentState = State.InRootPackedMessage; + packed = true; + return this; + } + + public CustomRpcSender EndMessage(bool startNew = false) + { + if (currentState is not State.InRootMessage and not State.InRootPackedMessage) + { + var errorMsg = $"Tried to exit Message but State is not InRootMessage or InRootPackedMessage (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + bool wasPackedContext = packed; + bool closingPackedRoot = currentState == State.InRootPackedMessage; + + stream.EndMessage(); + + if (closingPackedRoot) + packed = false; + + if (startNew) + { + if (wasPackedContext && !closingPackedRoot) + { + // Close outer packed root too + stream.EndMessage(); + } + + doneStreams.Add(stream); + stream = MessageWriter.Get(sendOption); + messages = 0; + + currentState = State.Ready; + currentRpcTarget = -2; + + if (wasPackedContext) + StartPackedMessage(); + + return this; + } + + currentRpcTarget = -2; + currentState = packed ? State.InRootPackedMessage : State.Ready; return this; } diff --git a/Modules/CustomSabotage.cs b/Modules/CustomSabotage.cs index bd2960077..8432acd52 100644 --- a/Modules/CustomSabotage.cs +++ b/Modules/CustomSabotage.cs @@ -134,13 +134,13 @@ public class GrabOxygenMaskSabotage : CustomSabotage MapNames.Airship => SystemTypes.Medical, MapNames.Fungle => SystemTypes.Kitchen, (MapNames)6 => (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.Filtration, - _ => SystemTypes.Outside + _ => ShipStatus.Instance.AllRooms.RandomElement().RoomId }; TimeLimit = Math.Min(60, GetDefaultSabotageTimeLimit(map)); AdjustTimeLimitBasedOnPlayerSpeed(ref TimeLimit); - RoomPosition = RandomSpawn.SpawnMap.GetSpawnMap().Positions.GetValueOrDefault(TargetRoom, TargetRoom.GetRoomClass().transform.position); + RoomPosition = Main.LIMap ? TargetRoom.GetRoomClass().transform.position : RandomSpawn.SpawnMap.GetSpawnMap().Positions.GetValueOrDefault(TargetRoom, TargetRoom.GetRoomClass().transform.position); Main.EnumerateAlivePlayerControls().Do(x => LocateArrow.Add(x.PlayerId, RoomPosition)); } diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index 071fbbcd0..68a23e04e 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -13,7 +13,7 @@ namespace EHR; internal static class DisableDevice { public static readonly List DesyncComms = []; - private static int frame; + private static int Frame; public static readonly Dictionary DevicePos = new() { @@ -39,7 +39,7 @@ internal static class DisableDevice ["SubmergedCamera"] = new(-3.41f, -34.56f) }; - public static bool DoDisable => Options.DisableDevices.GetBool(); + private static bool DoDisable => Options.DisableDevices.GetBool(); public static float UsableDistance => Main.CurrentMap switch { @@ -54,8 +54,8 @@ internal static class DisableDevice public static void FixedUpdate() { - frame = frame == 3 ? 0 : ++frame; - if (frame != 0) return; + Frame = Frame == 3 ? 0 : ++Frame; + if (Frame != 0) return; bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5d752faf9..3c3a436e4 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -192,7 +192,7 @@ internal static class ExtendedPlayerControl player.Visible = false; } - public void SetChatVisible(bool visible) + public void SetChatVisible(bool visible, MessageWriter packedWriter = null) { if (!AmongUsClient.Instance.AmHost) return; @@ -213,10 +213,14 @@ internal static class ExtendedPlayerControl return; } - DataFlagRateLimiter.Enqueue(() => + if (packedWriter == null) DataFlagRateLimiter.Enqueue(Action, calls: 3); + else Action(); + return; + + void Action() { bool dead = player.Data.IsDead; - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + MessageWriter writer = packedWriter ?? MessageWriter.Get(SendOption.Reliable); writer.StartMessage(6); writer.Write(AmongUsClient.Instance.GameId); writer.WritePacked(player.OwnerId); @@ -250,9 +254,10 @@ internal static class ExtendedPlayerControl writer.WritePacked(netIdCnt); writer.EndMessage(); writer.EndMessage(); + if (packedWriter != null) return; AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); - }, calls: 3); + } } public ClientData GetClient() @@ -751,7 +756,7 @@ internal static class ExtendedPlayerControl MapNames.Skeld or MapNames.Dleks when room => true, MapNames.Polus when overlapPointNonAlloc >= 1 => true, MapNames.Polus when pos.y is >= -26.11f and <= -6.41f && pos.x is >= 3.56f and <= 32.68f => true, - (MapNames)6 => true, + (MapNames)6 or (MapNames)7 => true, _ => false }; } @@ -1037,7 +1042,7 @@ internal static class ExtendedPlayerControl public void SyncSettings() { - PlayerGameOptionsSender.SendImmediately(player.PlayerId); + PlayerGameOptionsSender.ForceSendImmediately(player.PlayerId); } public TaskState GetTaskState() diff --git a/Modules/GameOptionsSender/GameOptionsSender.cs b/Modules/GameOptionsSender/GameOptionsSender.cs index 3b71d4e8a..b6bd72aa1 100644 --- a/Modules/GameOptionsSender/GameOptionsSender.cs +++ b/Modules/GameOptionsSender/GameOptionsSender.cs @@ -169,4 +169,4 @@ public abstract class GameOptionsSender protected static bool ForceWaitFrame; #endregion -} +} \ No newline at end of file diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 9d147e811..3e4ac3e8d 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -38,7 +38,7 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS } } - public static void SendImmediately(byte playerId) + public static void ForceSendImmediately(byte playerId) { for (var index = 0; index < AllSenders.Count; index++) { @@ -54,6 +54,22 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS } } + public static void SendAllImmediately() + { + ForceWaitFrame = true; + + for (var index = 0; index < AllSenders.Count; index++) + { + GameOptionsSender allSender = AllSenders[index]; + + if (allSender is PlayerGameOptionsSender { IsDirty: true } sender) + { + sender.SendGameOptions(); + sender.IsDirty = false; + } + } + } + public static void SetDirtyToAll() { for (var index = 0; index < AllSenders.Count; index++) @@ -106,11 +122,11 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS IsDirty = true; } - protected override void SendGameOptions() - { - if (player.AmOwner) - { - IGameOptions opt = BuildGameOptions(); + protected override void SendGameOptions() + { + if (player.AmOwner) + { + IGameOptions opt = BuildGameOptions(); if (GameManager.Instance?.LogicComponents != null) { @@ -127,11 +143,11 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS base.SendGameOptions(); } - protected override IEnumerator SendGameOptionsAsync() - { - if (player.AmOwner) - { - IGameOptions opt = BuildGameOptions(); + protected override IEnumerator SendGameOptionsAsync() + { + if (player.AmOwner) + { + IGameOptions opt = BuildGameOptions(); if (GameManager.Instance?.LogicComponents != null) { diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 20f76c237..8a24a0dc6 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -617,7 +617,6 @@ public static class GuessManager playerVoteArea.UnsetVote(); meetingHud.SetDirtyBit(1U); - AmongUsClient.Instance.SendAllStreamedObjects(); PlayerControl voteAreaPlayer = Utils.GetPlayerById(playerVoteArea.TargetPlayerId); if (!voteAreaPlayer) continue; @@ -626,7 +625,6 @@ public static class GuessManager { meetingHud.RpcClearVote(voteAreaPlayer.OwnerId); meetingHud.SetDirtyBit(1U); - AmongUsClient.Instance.SendAllStreamedObjects(); } else meetingHud.ClearVote(); @@ -668,10 +666,8 @@ public static class GuessManager if (AmongUsClient.Instance.AmHost) { meetingHud.SetDirtyBit(1U); - AmongUsClient.Instance.SendAllStreamedObjects(); meetingHud.RpcClearVote(pc.OwnerId); meetingHud.SetDirtyBit(1U); - AmongUsClient.Instance.SendAllStreamedObjects(); } } @@ -1427,7 +1423,7 @@ public static class GuessManager for (var i = 0; i < alivePlayerControls.Count && (skipped ? i - 1 : i) < data.Length; i++) { string choice = data[skipped ? i - 1 : i].choice; - string namePlateId = data[skipped ? i - 1 : i].namePlateId; + //string namePlateId = data[skipped ? i - 1 : i].namePlateId; PlayerControl pc = alivePlayerControls[i]; if (pc.PlayerId == guesserId) @@ -1445,7 +1441,7 @@ public static class GuessManager if (textIndex % 3 == 0) sb.AppendLine(); else sb.Append(' '); - if (writer.Length > 500 || messages + 2 > packingLimit) + if (writer.Length > 500 || messages >= packingLimit) { messages = 0; writer.EndMessage(); @@ -1464,14 +1460,7 @@ public static class GuessManager writer.Write(false); writer.EndMessage(); - writer.StartMessage(2); - writer.WritePacked(pc.NetId); - writer.Write((byte)RpcCalls.SetNamePlateStr); - writer.Write(namePlateId); - writer.Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)); - writer.EndMessage(); - - messages += 2; + messages++; } writer.EndMessage(); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 7d0e67120..5bfacca0a 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -588,7 +588,7 @@ internal static class RPCHandlerPatch var cno = reader.ReadNetObject(); bool active = reader.ReadBoolean(); - if (cno != null) + if (cno) { cno.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(active); cno.Collider.enabled = false; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 67aada868..36fd9d2cf 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1861,7 +1861,7 @@ public static class Utils return writer; } - if (sender.AmOwner && sender.Data.IsDead) + if (sender.AmOwner && sender.Data.IsDead && (sendTo == byte.MaxValue || !receiver.Data.IsDead)) { bool delayMessage = false; @@ -3406,6 +3406,12 @@ public static class Utils PlayerGameOptionsSender.SetDirtyToAllV4(); } + public static void SyncAllSettings() + { + PlayerGameOptionsSender.SetDirtyToAll(); + PlayerGameOptionsSender.SendAllImmediately(); + } + public static bool RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit, CustomRpcSender writer = null, SendOption sendOption = SendOption.Reliable) { if (!AmongUsClient.Instance.AmHost) return false; @@ -3750,7 +3756,7 @@ public static class Utils try { if (Lovers.PrivateChat.GetBool() && Main.LoversPlayers.TrueForAll(x => x.IsAlive())) - Main.LoversPlayers.ForEach(x => x.SetChatVisible(true)); + Main.LoversPlayers.SetChatVisible(true); } catch (Exception e) { ThrowException(e); } @@ -4484,22 +4490,58 @@ public static class Utils return new(r, g, b, color.a); } - public static void SetChatVisibleForAll() + public static void SetChatVisible(this IReadOnlyList players, bool visible) { - if (!GameStates.IsInGame) return; - - var aapc = Main.AllAlivePlayerControls; - - if (Options.CurrentGameMode is CustomGameMode.Mingle or CustomGameMode.Quiz or CustomGameMode.NaturalDisasters) + switch (players.Count) { - foreach (var pc in aapc) + case 0: { - var dummyImp = aapc.FirstOrDefault(x => x != pc); - if (dummyImp) dummyImp.RpcSetRoleDesync(RoleTypes.Impostor, pc.OwnerId); + return; + } + case 1: + { + players[0].SetChatVisible(visible); + return; + } + default: + { + if (Options.CurrentGameMode is CustomGameMode.Mingle or CustomGameMode.Quiz or CustomGameMode.NaturalDisasters) + { + foreach (var pc in players) + { + var dummyImp = players.FirstOrDefault(x => x != pc); + if (dummyImp) dummyImp.RpcSetRoleDesync(RoleTypes.Impostor, pc.OwnerId); + } + } + + DataFlagRateLimiter.Enqueue(() => + { + MessageWriter packedWriter = MessageWriter.Get(SendOption.Reliable); + packedWriter.StartMessage(26); + packedWriter.WritePacked(AmongUsClient.Instance.GameId); + + foreach (PlayerControl pc in players) + { + if (packedWriter.Length > 500) + { + packedWriter.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(packedWriter); + packedWriter.Clear(SendOption.Reliable); + packedWriter.StartMessage(26); + packedWriter.WritePacked(AmongUsClient.Instance.GameId); + } + + pc.SetChatVisible(visible, packedWriter); + } + + packedWriter.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(packedWriter); + packedWriter.Recycle(); + }, calls: 3); // WAITING FOR THE SLOTHS TO VERIFY THIS (3 or 3 * players.Count) + + return; } } - - aapc.Do(x => x.SetChatVisible(true)); } public static bool TryCast(this Il2CppObjectBase obj, out T casted) where T : Il2CppObjectBase diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 48ab0730c..9eb295892 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -133,7 +133,7 @@ internal static class ControllerManagerUpdatePatch } if (KeysDown(KeyCode.Return, KeyCode.C, KeyCode.LeftShift, KeyCode.LeftControl) && GameStates.IsInGame) - Utils.SetChatVisibleForAll(); + Main.AllAlivePlayerControls.SetChatVisible(true); if (KeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) { diff --git a/Patches/DleksPatch.cs b/Patches/DleksPatch.cs index f92634069..1c0f9e558 100644 --- a/Patches/DleksPatch.cs +++ b/Patches/DleksPatch.cs @@ -14,7 +14,7 @@ internal static class AllMapIconsPatch [HarmonyPrefix] public static void GameStartManagerStart_Prefix(GameStartManager __instance) { - if (!__instance.AllMapIcons.ToArray().Any(x => x.Name == MapNames.Dleks)) + if (__instance.AllMapIcons.TrueForAll((Il2CppSystem.Predicate)(x => x.Name != MapNames.Dleks))) { __instance.AllMapIcons.Insert((int)MapNames.Dleks, new MapIconByName { @@ -24,7 +24,7 @@ internal static class AllMapIconsPatch } if (SubmergedCompatibility.Loaded) { - if (!__instance.AllMapIcons.ToArray().Any(x => x.Name == (MapNames)6)) + if (__instance.AllMapIcons.TrueForAll((Il2CppSystem.Predicate)(x => x.Name != (MapNames)6))) { __instance.AllMapIcons.Insert((int)(MapNames)6, new MapIconByName { @@ -40,7 +40,7 @@ internal static class AllMapIconsPatch { try { - if (__instance == null) return; + if (!__instance) return; LateTask.New(() => { @@ -88,7 +88,7 @@ public static class CreateGameOptionsPatch [HarmonyPrefix] public static bool MapChangedPrefix(CreateGameOptions __instance) { - if (__instance.mapPicker.GetSelectedID() is (int)MapNames.Dleks) + if (__instance.mapPicker.GetSelectedID() == (int)MapNames.Dleks) { __instance.mapBanner.flipX = false; __instance.rendererBGCrewmates.sprite = __instance.bgCrewmates[0]; @@ -98,10 +98,7 @@ public static class CreateGameOptionsPatch __instance.SetCrewmateGraphic(__instance.capacityOption.Value - 1f); return false; } - if (SubmergedCompatibility.Loaded && __instance.mapPicker.GetSelectedID() is (int)(MapNames)6) - return false; - - return true; + return !SubmergedCompatibility.Loaded || __instance.mapPicker.GetSelectedID() != 6; } [HarmonyPatch(typeof(CreateGameOptions), nameof(CreateGameOptions.Start))] [HarmonyPrefix] diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 3329a269c..60346c415 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -88,10 +88,10 @@ internal static class ExileControllerWrapUpPatch PetsHelper.RpcRemovePet(pc); } - if (Options.RandomSpawn.GetBool() && Main.CurrentMap != MapNames.Airship) + if (Options.RandomSpawn.GetBool() && Main.CurrentMap != MapNames.Airship && !Main.LIMap) { var map = RandomSpawn.SpawnMap.GetSpawnMap(); - Main.EnumerateAlivePlayerControls().Do(player => map.RandomTeleport(player)); + Main.EnumerateAlivePlayerControls().Do(map.RandomTeleport); } FallFromLadder.Reset(); diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index b827db214..adde524cb 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -696,11 +696,8 @@ internal static class SetHudActivePatch [HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.SetFromSettings))] static class AbilityButtonSetFromSettingsPatch { - public static Exception Finalizer(Exception __exception) + public static Exception Finalizer() { - if (__exception != null) - Utils.ThrowException(__exception); - return null; } } @@ -708,11 +705,8 @@ static class AbilityButtonSetFromSettingsPatch [HarmonyPatch(typeof(ActionButton), nameof(ActionButton.SetEnabled))] static class ActionButtonSetEnabledPatch { - public static Exception Finalizer(Exception __exception) + public static Exception Finalizer() { - if (__exception != null) - Utils.ThrowException(__exception); - return null; } } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 4066b7adb..33bfeb71a 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -1179,7 +1179,7 @@ internal static class IntroCutsceneDestroyPatch case CustomGameMode.HideAndSeek when CustomHnS.Chat: case CustomGameMode.NaturalDisasters when NaturalDisasters.Chat: case CustomGameMode.Standard when Options.ChatDuringGame.GetBool(): - LateTask.New(Utils.SetChatVisibleForAll, 4f); + LateTask.New(() => Main.AllAlivePlayerControls.SetChatVisible(true), 4f); break; } @@ -1283,7 +1283,7 @@ internal static class IntroCutsceneDestroyPatch } catch (Exception e) { Utils.ThrowException(e); } - if (Options.RandomSpawn.GetBool() && Main.CurrentMap != MapNames.Airship && AmongUsClient.Instance.AmHost && Options.CurrentGameMode is not CustomGameMode.CaptureTheFlag and not CustomGameMode.KingOfTheZones and not CustomGameMode.BedWars and not CustomGameMode.Deathrace) + if (Options.RandomSpawn.GetBool() && Main.CurrentMap != MapNames.Airship && !Main.LIMap && AmongUsClient.Instance.AmHost && Options.CurrentGameMode is not CustomGameMode.CaptureTheFlag and not CustomGameMode.KingOfTheZones and not CustomGameMode.BedWars and not CustomGameMode.Deathrace) { var map = RandomSpawn.SpawnMap.GetSpawnMap(); aapc.Do(map.RandomTeleport); @@ -1410,7 +1410,7 @@ internal static class IntroCutsceneDestroyPatch if (!AmongUsClient.Instance.AmHost || !Lovers.PrivateChat.GetBool()) return; - Main.LoversPlayers.ForEach(x => x.SetChatVisible(true)); + Main.LoversPlayers.SetChatVisible(true); }, 1f, log: false); LateTask.New(() => diff --git a/Patches/MapPickerMenuPatch.cs b/Patches/MapPickerMenuPatch.cs index 50640210c..847defeb8 100644 --- a/Patches/MapPickerMenuPatch.cs +++ b/Patches/MapPickerMenuPatch.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using HarmonyLib; -using UnityEngine; using UnityEngine.SceneManagement; namespace EHR.Patches; @@ -25,7 +23,7 @@ public static class GameOptionsMapPickerPatch [HarmonyPrefix] public static void Postfix_Prefix(GameOptionsMapPicker __instance) { - if (!__instance.AllMapIcons.ToArray().Any(x => x.Name == MapNames.Dleks)) + if (__instance.AllMapIcons.TrueForAll((Il2CppSystem.Predicate)(x => x.Name != MapNames.Dleks))) { __instance.AllMapIcons.Insert((int)MapNames.Dleks, new MapIconByName { @@ -37,7 +35,7 @@ public static class GameOptionsMapPickerPatch } if (SubmergedCompatibility.Loaded) { - if (!__instance.AllMapIcons.ToArray().Any(x => x.Name == (MapNames)6)) + if (__instance.AllMapIcons.TrueForAll((Il2CppSystem.Predicate)(x => x.Name != (MapNames)6))) { __instance.AllMapIcons.Insert((int)(MapNames)6, new MapIconByName { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 5fb90b056..9b344d456 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1420,13 +1420,13 @@ internal static class MeetingHudCastVotePatch return false; } - var voteCanceled = false; - - if (!Main.DontCancelVoteList.Contains(srcPlayerId) && !skip && pcSrc.GetCustomRole().CancelsVote() && !pcSrc.UsesMeetingShapeshift() && Main.PlayerStates[srcPlayerId].Role.OnVote(pcSrc, pcTarget)) - { - ShouldCancelVoteList.TryAdd(srcPlayerId, (__instance, pvaSrc, pcSrc)); - voteCanceled = true; - } + var voteCanceled = false; + + if (!Main.DontCancelVoteList.Contains(srcPlayerId) && !skip && pcSrc.GetCustomRole().CancelsVote() && !pcSrc.UsesMeetingShapeshift() && Main.PlayerStates[srcPlayerId].Role.OnVote(pcSrc, pcTarget)) + { + ShouldCancelVoteList.TryAdd(srcPlayerId, (__instance, pvaSrc, pcSrc)); + voteCanceled = true; + } if (Silencer.ForSilencer.Contains(srcPlayerId)) { @@ -1436,7 +1436,7 @@ internal static class MeetingHudCastVotePatch Logger.Info($"{pcSrc.GetNameWithRole()} => {(skip ? "Skip" : pcTarget.GetNameWithRole())}{(voteCanceled ? " (Canceled)" : string.Empty)}", "Vote"); - return skip || !voteCanceled; // skips and invalid votes are never canceled + return skip || !voteCanceled; // skips and invalid votes are never canceled } public static void Postfix([HarmonyArgument(0)] byte srcPlayerId) @@ -1447,13 +1447,11 @@ internal static class MeetingHudCastVotePatch { info.SourcePVA.UnsetVote(); info.MeetingHud.SetDirtyBit(1U); - AmongUsClient.Instance.SendAllStreamedObjects(); } catch { } info.MeetingHud.RpcClearVote(info.SourcePC.OwnerId); info.MeetingHud.SetDirtyBit(1U); - AmongUsClient.Instance.SendAllStreamedObjects(); info.SourcePVA.VotedFor = byte.MaxValue; @@ -1610,4 +1608,4 @@ internal static class ExileControllerBeginPatch if (CheckForEndVotingPatch.EjectionText.EndsWith("") && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.TheMindGame && init is { outfit: not null }) __instance.completeString = CheckForEndVotingPatch.EjectionText[..^8]; } -} +} diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 2be5af1fe..e55b3f040 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1228,24 +1228,29 @@ internal static class ReportDeadBodyPatch { try { - Main.PlayerStates.Values.DoIf(x => !x.IsDead, x => x.IsBlackOut = true); - Main.EnumerateAlivePlayerControls().Do(x => x.SyncSettings()); + Main.PlayerStates.Values.DoIf(x => !x.IsDead, x => + { + x.IsBlackOut = true; + x.Player.MarkDirtySettings(); + }); + PlayerGameOptionsSender.SendAllImmediately(); } catch (Exception e) { ThrowException(e); } } LateTask.New(() => { - Main.PlayerStates.Values.Do(x => x.IsBlackOut = false); - MarkEveryoneDirtySettings(); - }, 3f, "RemoveBlackout"); + Main.PlayerStates.Values.DoIf(x => x.IsBlackOut, x => + { + x.IsBlackOut = false; + x.Player.MarkDirtySettings(); + }); + }, 15f, "RemoveBlackout"); if (Main.GameTimer.IsRunning && !Options.GameTimeLimitRunsDuringMeetings.GetBool()) Main.GameTimer.Stop(); - - var allCNO = CustomNetObject.AllObjects.ToArray(); - foreach (CustomNetObject cno in allCNO) + foreach (CustomNetObject cno in CustomNetObject.AllObjects) { try { @@ -1259,7 +1264,7 @@ internal static class ReportDeadBodyPatch LateTask.New(() => { - foreach (CustomNetObject cno in allCNO) + foreach (CustomNetObject cno in CustomNetObject.AllObjects) { try { diff --git a/Resources/Config/template/English.txt b/Resources/Config/template/English.txt index beed55cb5..e422bb2f8 100644 --- a/Resources/Config/template/English.txt +++ b/Resources/Config/template/English.txt @@ -1,3 +1,3 @@ welcome:Welcome!\nThis is a Modded Lobby.\nYou're playing Endless Host Roles (EHR) v{{ModVersion}}.\nThe mod was made by Gurge44.\ndiscord . gg / ehr\n\n/r → Show all active roles\n/r [role name] → Show description & settings for that role -OnMeeting:/m → See what your role does\n/r → Show all active roles\n/r [role name] → Show description & settings for that role\n/bt [ID] [role name] → Guess that player's role +OnMeeting:/cmd m → See what your role does\n/cmd r → Show all active roles\n/cmd r [role name] → Show description & settings for that role\n/cmd guess [ID] [role name] → Guess that player's role help:ENDLESS HOST ROLES\nQUICK GUIDE\n(Guide by @fluxwux on Discord)\nTake notes, class.\n\nEHR is a pretty complicated mod, but don't worry! I'm here to teach you all the basics of the game so you can become the best player there is! First off, let's start with the most integral part of the mod...\n\nROLES\nEach player will be assigned a role at the start of the game. There are 4 role types. Impostor, Crewmate, Neutral, & Add-Ons.\n\nImpostors & Crewmates\nSelf explanatory. These roles are given to the impostors or crewmates.\n\nNeutrals\nThis isn't a team. These roles are for any roles that don't side with the impostors or crewmates. Usually, they work alone toward a goal, depending on what their role does. that goal can range from getting their target voted out to win alone, killing everyone in the game, simply staying alive until the end to win with the winning team, and more.\n\nAdd-Ons\nAdd-Ons are roles that you can get on top of your team role.Usually, they provide minor benefits, such as your killer automatically self-reporting your body, giving you a red screen flash when a body dies, and more. Some are even major additions, like being able to kill people after your death. But be weary, as you may be assigned a harmful Add-On!These roles may give you reduced vision, an inability to report bodies, an inability to fix sabotages, and more. Remember that most (not all) Add-Ons are not team specific, and can be assigned to anyone unless the host individually choses which roles go on what.\n\nIt is important to remember that nobody should reveal their role unless they have a reason to (eg. Snitch finishing their tasks to out the impostors)\n\nCHAT COMMANDS\nChat commands are a very useful asset, and each one has its own very useful effect. Examples of some meeting commands can be found below vvvv\n\n/m: Explains your role and Add-On(s) in detail. A MUST USE if you don't know what your role does.\n/r [role name]: Shows you what any role in the game does. Just type in the role. Typing /r on its own shows every role enabled in the host's settings.\n/kcount (can be disabled by host): Displays the amount of impostors and neutral killers remaining.\n/l (not i, L): Used in the lobby to show the results of the previous game. Displays who died to whom, everyone's roles, and who won the last game. Usually, hosts will make this message appear automatically after the round ends.\n\nGUESSING\nGuessing, or variants of guessing, are abilities that can be used by a select few roles. Usually, they involve guessing a certain player's role in order to kill them during a meeting, or achieve other effects. Sometimes, hosts will make it so entire teams or even all players can guess.\n\nHOW TO GUESS\nThis guide will show the most common way to guess using the /bt command.\n\n#1. Figure out the player's role\nBefore you can guess, you need to find out what their role is. If you've found out what their role is, you can guess them in the next meeting. For example, I know blue is the Snitch, so moving on to step 2...\n\n#2. Type /id\nTyping /id will show a list of everyone and their respective ID, represented by a number from 1-15. I type /id and blue's ID is 7. Sometimes, you may already see the IDs of all players without having to type this command. Now all that's left is...\n\n#3. Type /bt [player ID] [role}\nIn my case, it would be /bt 7 snitch. Now, if correct, I have killed blue, and now blue will have to wallow in their shame as a ghost. How sad. But if I misguess, I have to wallow in my shame as I die if I guess wrong. How embarrasing.\n\nNow, you should be caught up on all things EHR related. Now go out there and win some games! diff --git a/Roles/Standard/Coven/Banshee.cs b/Roles/Standard/Coven/Banshee.cs index 30f153f24..a47d0c6bd 100644 --- a/Roles/Standard/Coven/Banshee.cs +++ b/Roles/Standard/Coven/Banshee.cs @@ -113,7 +113,7 @@ public class Banshee : CovenBase public override void OnReportDeadBody() { - LateTask.New(() => Instances.SelectMany(x => x.ScreechedPlayers).Distinct().ToValidPlayers().DoIf(x => x.IsAlive(), x => x.SetChatVisible(false)), 4f, "Set Chat Hidden For Banshee Victims"); + LateTask.New(() => Instances.SelectMany(x => x.ScreechedPlayers).Distinct().ToValidPlayers().Where(x => x.IsAlive()).ToArray().SetChatVisible(false), 4f, "Set Chat Hidden For Banshee Victims"); PlayerControl pc = BansheeId.GetPlayer(); diff --git a/Roles/Standard/Crewmate/Chaos/Tornado.cs b/Roles/Standard/Crewmate/Chaos/Tornado.cs index 50049fcba..94bd1f109 100644 --- a/Roles/Standard/Crewmate/Chaos/Tornado.cs +++ b/Roles/Standard/Crewmate/Chaos/Tornado.cs @@ -60,10 +60,8 @@ internal class Tornado : RoleBase Map = RandomSpawn.SpawnMap.GetSpawnMap(); CanUseMap = true; } - catch (ArgumentOutOfRangeException) + catch { - Logger.CurrentMethod(); - Logger.Error("Unsupported Map", "Torando"); CanUseMap = false; } } diff --git a/Roles/Standard/Crewmate/Power/Astral.cs b/Roles/Standard/Crewmate/Power/Astral.cs index e37b2e737..36fbf8e37 100644 --- a/Roles/Standard/Crewmate/Power/Astral.cs +++ b/Roles/Standard/Crewmate/Power/Astral.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AmongUs.GameOptions; using EHR.Modules; using EHR.Modules.Extensions; @@ -103,7 +104,7 @@ public class Astral : RoleBase if (!pc.IsInsideMap()) { Vector2 playerPosition = pc.Pos(); - Vector2 closestSpawnPosition = FastVector2.TryGetClosest(playerPosition, RandomSpawn.SpawnMap.GetSpawnMap().Positions.Values, out Vector2 closest1) ? closest1 : new Vector2(50f, 50f); + Vector2 closestSpawnPosition = FastVector2.TryGetClosest(playerPosition, Main.LIMap ? ShipStatus.Instance.AllRooms.Select(x => new Vector2(x.transform.position.x, x.transform.position.y)) : RandomSpawn.SpawnMap.GetSpawnMap().Positions.Values, out Vector2 closest1) ? closest1 : new Vector2(50f, 50f); Vector3 closestVentPosition = pc.GetClosestVent()?.transform.position ?? closestSpawnPosition; pc.TP(Vector2.Distance(playerPosition, closestVentPosition) < Vector2.Distance(playerPosition, closestSpawnPosition) ? closestVentPosition : closestSpawnPosition); } diff --git a/Roles/Standard/Crewmate/Support/TimeMaster.cs b/Roles/Standard/Crewmate/Support/TimeMaster.cs index 4b2785e49..3a80abe50 100644 --- a/Roles/Standard/Crewmate/Support/TimeMaster.cs +++ b/Roles/Standard/Crewmate/Support/TimeMaster.cs @@ -226,18 +226,14 @@ internal class TimeMaster : RoleBase if (!commsSaboActive) { - switch (Main.NormalOptions.MapId) + doComms = Main.NormalOptions.MapId switch { - case 2: - doComms |= FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["PolusVital"], usableDistance); - break; - case 4: - doComms |= FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["AirshipVital"], usableDistance); - break; - case 5: - doComms |= FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["FungleVital"], usableDistance); - break; - } + 2 => FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["PolusVital"], usableDistance), + 4 => FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["AirshipVital"], usableDistance), + 5 => FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["FungleVital"], usableDistance), + 6 when SubmergedCompatibility.IsSubmerged() => FastVector2.DistanceWithinRange(pos, DisableDevice.DevicePos["SubmergedVital"], usableDistance), + _ => false + }; } var sender = CustomRpcSender.Create("DisableDevice.FixedUpdate", SendOption.Reliable, log: false); diff --git a/Roles/Standard/Impostor/Miscellaneous/AntiAdminer.cs b/Roles/Standard/Impostor/Miscellaneous/AntiAdminer.cs index 71018d2a5..44aa6e551 100644 --- a/Roles/Standard/Impostor/Miscellaneous/AntiAdminer.cs +++ b/Roles/Standard/Impostor/Miscellaneous/AntiAdminer.cs @@ -353,7 +353,7 @@ internal class AntiAdminer : RoleBase private void OpenDoors(PlayerControl pc) { if (!IsTelecommunication) return; - if (pc == null) return; + if (!pc) return; if (Main.CurrentMap == MapNames.MiraHQ) return; if (pc.GetAbilityUseLimit() >= 1) @@ -402,5 +402,4 @@ internal class AntiAdminer : RoleBase else hud.AbilityButton?.OverrideText(Translator.GetString("VeteranVentButtonText")); } - -} +} \ No newline at end of file diff --git a/Roles/Standard/Neutral/Benign/RoomRusher.cs b/Roles/Standard/Neutral/Benign/RoomRusher.cs index 2cfd03666..5911e3c7a 100644 --- a/Roles/Standard/Neutral/Benign/RoomRusher.cs +++ b/Roles/Standard/Neutral/Benign/RoomRusher.cs @@ -94,7 +94,7 @@ public class RoomRusher : RoleBase MapNames.Airship => SystemTypes.MainHall, MapNames.Fungle => SystemTypes.Dropship, (MapNames)6 => (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.UpperCentral, - _ => throw new ArgumentOutOfRangeException(map.ToString(), "Invalid map") + _ => AllRooms.RandomElement() }; if (!initial && !dontCount) CompletedNum++; diff --git a/Roles/Standard/Neutral/Killing/Bargainer.cs b/Roles/Standard/Neutral/Killing/Bargainer.cs index 107474586..c49a6bacc 100644 --- a/Roles/Standard/Neutral/Killing/Bargainer.cs +++ b/Roles/Standard/Neutral/Killing/Bargainer.cs @@ -79,8 +79,7 @@ internal class Bargainer : RoleBase { var mapName = Main.CurrentMap.ToString(); if (SubmergedCompatibility.IsSubmerged()) mapName = "Submerged"; - IEnumerable> devices = DisableDevice.DevicePos.SkipWhile(x => !x.Key.StartsWith(mapName)).TakeWhile(x => x.Key.StartsWith(mapName)); - return devices.Select(x => x.Value); + return DisableDevice.DevicePos.Where(x => x.Key.StartsWith(mapName)).Select(x => x.Value); } } diff --git a/Roles/Standard/Neutral/Killing/Chemist.cs b/Roles/Standard/Neutral/Killing/Chemist.cs index aafe1bedf..70ca0db38 100644 --- a/Roles/Standard/Neutral/Killing/Chemist.cs +++ b/Roles/Standard/Neutral/Killing/Chemist.cs @@ -490,13 +490,13 @@ internal class Chemist : RoleBase Factory beforeFactory = CurrentFactory; PlainShipRoom room = pc.GetPlainShipRoom(); - if (ItemCounts[Item.ThermalWater] < 50 && room != null && room.RoomId == HottestRoom[Main.CurrentMap]) + if (ItemCounts[Item.ThermalWater] < 50 && room && room.RoomId == HottestRoom[Main.CurrentMap]) { ItemCounts[Item.ThermalWater]++; Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.ThermalWater, 1); } - if (room != null) + if (room) { CurrentFactory = FactoryLocations.GetValueOrDefault(room.RoomId); Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)CurrentFactory);