From b2ea6d47455a8a244ea2ed12dea2cf61dd440568 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Mon, 1 Jun 2026 20:44:35 +0200 Subject: [PATCH] just some casual bug fixing --- EHR.sln.DotSettings | 1 + Gamemodes/BedWars.cs | 2 +- Main.cs | 6 +- Modules/CustomNetObject.cs | 3 +- Modules/CustomRoleSelector.cs | 2 +- Modules/CustomRpcSender.cs | 95 ++++++++----- Modules/CustomSounds.cs | 14 +- Modules/CustomTeamManager.cs | 131 +++++++++++++++--- Modules/ExtendedPlayerControl.cs | 15 +- Modules/Extensions/CollectionExtensions.cs | 9 +- .../PlayerGameOptionsSender.cs | 12 +- Modules/LobbySharingAPI.cs | 1 + Modules/RPC.cs | 22 ++- Modules/Utils.cs | 75 +++++----- Patches/IntroPatch.cs | 17 +-- Patches/MeetingHudPatch.cs | 1 + Patches/OnGameStartedPatch.cs | 14 ++ Patches/PetActionsPatch.cs | 12 +- Patches/PlayerControlPatch.cs | 34 +++-- Patches/SabotageSystemPatch.cs | 4 +- Roles/Standard/Addons/Crewmate/Workhorse.cs | 47 +++---- Roles/Standard/Crewmate/Investigate/Rabbit.cs | 6 +- .../Crewmate/Miscellaneous/Carrier.cs | 3 +- .../Crewmate/Support/DonutDelivery.cs | 7 +- Roles/Standard/Crewmate/Support/TimeMaster.cs | 40 ++++-- Roles/Standard/Ghost/Bloodmoon.cs | 4 +- Roles/Standard/Impostor/Concealing/Penguin.cs | 61 +++----- Roles/Standard/Impostor/Concealing/Stasis.cs | 19 ++- .../Standard/Impostor/Killing/BountyHunter.cs | 2 +- Roles/Standard/Impostor/Killing/Butcher.cs | 9 +- Roles/Standard/Impostor/Support/Capitalist.cs | 12 +- Roles/Standard/Neutral/Evil/Vector.cs | 13 +- Roles/Standard/Neutral/Evil/Workaholic.cs | 7 +- Roles/Standard/Neutral/Killing/Pelican.cs | 17 +-- 34 files changed, 462 insertions(+), 255 deletions(-) diff --git a/EHR.sln.DotSettings b/EHR.sln.DotSettings index 24140ddf2..7e7dcc2e4 100644 --- a/EHR.sln.DotSettings +++ b/EHR.sln.DotSettings @@ -36,6 +36,7 @@ FPS GA GM + HNS HP HQ HUD diff --git a/Gamemodes/BedWars.cs b/Gamemodes/BedWars.cs index ee200ed61..7847ced0b 100644 --- a/Gamemodes/BedWars.cs +++ b/Gamemodes/BedWars.cs @@ -1734,8 +1734,8 @@ public static class BedWars if (!vanillaServer) { if (lastSelected) finalSb.Append(baseColor); - finalSb.Append("▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔"); finalSb.AppendLine(); + finalSb.AppendLine("▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔"); finalSb.Append(Utils.ColorString(Color.white, bottomText)); finalSb.AppendLine(""); } diff --git a/Main.cs b/Main.cs index c9d2dde25..a8fda79bd 100644 --- a/Main.cs +++ b/Main.cs @@ -260,13 +260,14 @@ public class Main : BasePlugin // ################# - WARNING!!! - ##################### // Don't use Enumerate(Alive)PlayerControls if it updates every frame or every second - // Better use CachedAll/AlivePlayerControls, but in Coroutines (Async) functions need use "for (...)" loop + // Better use CachedAll/AlivePlayerControls, but in Coroutines (Async) functions, use "for (...)" loop public static IEnumerable EnumeratePlayerControls() { // foreach can throw System.InvalidOperationException: Collection was modified; enumeration operation may not execute. // if the code waits frames between iterations, so the safest way is to use a for loop backwards for (int index = PlayerControl.AllPlayerControls.Count - 1; index >= 0; index--) { + if (PlayerControl.AllPlayerControls.Count <= index) continue; PlayerControl pc = PlayerControl.AllPlayerControls[index]; if (!pc || pc.PlayerId >= 254) continue; yield return pc; @@ -276,6 +277,7 @@ public class Main : BasePlugin { for (int index = PlayerControl.AllPlayerControls.Count - 1; index >= 0; index--) { + if (PlayerControl.AllPlayerControls.Count <= index) continue; PlayerControl pc = PlayerControl.AllPlayerControls[index]; if (!pc.IsAliveWithConditions() || pc.PlayerId >= 254) continue; yield return pc; @@ -319,7 +321,7 @@ public class Main : BasePlugin } // ################# - WARNING!!! - ##################### // Don't use CachedAll/AlivePlayerControls witch "foreach (...)" loop in Coroutines (Async) functions - // In async functions use a "for (...)" loop or Enumerate(Alive)PlayerControls or CachedAll/AlivePlayerControls witch LINQ functions (Like: ToList() or ToArray()) + // In async functions use a "for (...)" loop or Enumerate(Alive)PlayerControls or CachedAll(Alive)PlayerControls with LINQ functions (Like: ToList() or ToArray()) // ###################################### public static List CachedAllPlayerControls() { diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs index a39513ecf..1fb2a94fe 100644 --- a/Modules/CustomNetObject.cs +++ b/Modules/CustomNetObject.cs @@ -241,7 +241,8 @@ namespace EHR { if (!AmongUsClient.Instance.AmHost) return; - if (SnapToSendFrameCount++ < 5) return; + // max 30 calls per second in total + if (++SnapToSendFrameCount < Math.Max(10, AllObjects.Count)) return; SnapToSendFrameCount = 0; if (AmongUsClient.Instance.AmClient) diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index 98702794d..cc9e57797 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -248,7 +248,7 @@ internal static class CustomRoleSelector Dictionary> allRoles = roles.ToDictionary(x => x.Key, x => x.Value.ToList()); - roles.Keys.ToArray().Do(type => ApplySubCategoryLimits(type, subCategoryLimits)); + roles.Keys.Do(type => ApplySubCategoryLimits(type, subCategoryLimits)); Logger.Msg("===================================================", "PreSelectedRoles"); Logger.Info(string.Join(", ", roles[RoleAssignType.Impostor].Select(x => x.Role.ToString())), "PreSelectedImpostorRoles"); diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 1230069d3..4404fad63 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -36,12 +36,14 @@ public class CustomRpcSender private readonly OnSendDelegateType onSendDelegate; public readonly SendOption sendOption; + public bool checkLength; + // 0~: targetClientId (GameDataTo) // -1: All players (GameData) // -2: Not set private int currentRpcTarget; - private bool packed; + public bool packed; private State currentState/* = State.BeforeInit*/; public int messages; @@ -57,6 +59,7 @@ public class CustomRpcSender this.log = log; currentRpcTarget = -2; packed = false; + checkLength = true; onSendDelegate = () => { }; messages = 0; @@ -177,7 +180,7 @@ public class CustomRpcSender return count; } - if (1 + GetPackedIntSize(AmongUsClient.Instance.GameId) >= stream.Length) + if (3 + GetPackedIntSize(AmongUsClient.Instance.GameId) >= stream.Length) dispose = true; else EndMessage(); @@ -271,7 +274,7 @@ public class CustomRpcSender throw new InvalidOperationException(errorMsg); } - if (stream.Length > 500) + if (checkLength && stream.Length > 500) { if (currentState == State.InRootPackedMessage) { @@ -320,7 +323,7 @@ public class CustomRpcSender throw new InvalidOperationException(errorMsg); } - if (stream.Length > 500) + if (checkLength && stream.Length > 500) { doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); @@ -529,6 +532,63 @@ public class CustomRpcSender public static class CustomRpcSenderExtensions { + public static void RpcSetName(ref CustomRpcSender sender, PlayerControl player, string name, PlayerControl seer = null) + { + bool seerIsNull = !seer; + int targetClientId = seerIsNull ? -1 : seer.OwnerId; + + name = name.Replace("color=", string.Empty); + + switch (seerIsNull) + { + case true when Main.LastNotifyNames.Where(x => x.Key.Item1 == player.PlayerId).All(x => x.Value == name): + case false when Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] == name: + return; + case true: + Main.EnumeratePlayerControls().Do(x => Main.LastNotifyNames[(player.PlayerId, x.PlayerId)] = name); + break; + default: + Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; + break; + } + + sender.checkLength = false; + + if (sender.stream.Length + GetSetNameRpcSize(player.NetId, name) > 1100) + { + bool packed = sender.packed; + sender.SendMessage(); + sender = CustomRpcSender.Create(sender.name, sender.sendOption); + if (packed) sender.StartPackedMessage(); + } + + sender.AutoStartRpc(player.NetId, RpcCalls.SetName, targetClientId) + .Write(player.Data.NetId) + .Write(name) + .Write(false) + .EndRpc(); + + return; + + static int GetSetNameRpcSize(uint netId, string playerName) + { + int byteCount = System.Text.Encoding.UTF8.GetByteCount(playerName); + return 3 + PackedUIntSize(netId) + 1 + 4 + PackedUIntSize((uint)byteCount) + byteCount + 1; + } + + static int PackedUIntSize(uint value) + { + return value switch + { + < 0x80 => 1, + < 0x4000 => 2, + < 0x20_0000 => 3, + < 0x1000_0000 => 4, + _ => 5 + }; + } + } + extension(CustomRpcSender sender) { public bool RpcSetRole(PlayerControl player, RoleTypes role, int targetClientId = -1, bool noRpcForSelf = true, bool changeRoleMap = false) @@ -568,33 +628,6 @@ public static class CustomRpcSenderExtensions return true; } - public void RpcSetName(PlayerControl player, string name, PlayerControl seer = null) - { - bool seerIsNull = !seer; - int targetClientId = seerIsNull ? -1 : seer.OwnerId; - - name = name.Replace("color=", string.Empty); - - switch (seerIsNull) - { - case true when Main.LastNotifyNames.Where(x => x.Key.Item1 == player.PlayerId).All(x => x.Value == name): - case false when Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] == name: - return; - case true: - Main.EnumeratePlayerControls().Do(x => Main.LastNotifyNames[(player.PlayerId, x.PlayerId)] = name); - break; - default: - Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; - break; - } - - sender.AutoStartRpc(player.NetId, RpcCalls.SetName, targetClientId) - .Write(player.Data.NetId) - .Write(name) - .Write(false) - .EndRpc(); - } - public bool RpcGuardAndKill(PlayerControl killer, PlayerControl target = null, bool forObserver = false, bool fromSetKCD = false) { if (!AmongUsClient.Instance.AmHost) diff --git a/Modules/CustomSounds.cs b/Modules/CustomSounds.cs index 6047ad4a1..5f6af1bcd 100644 --- a/Modules/CustomSounds.cs +++ b/Modules/CustomSounds.cs @@ -12,18 +12,24 @@ public static class CustomSoundsManager { private static readonly string SoundsPath = $"{Environment.CurrentDirectory.Replace(@"\", "/")}/BepInEx/resources/"; + public static long LastSoundRPCTS; + public static void RPCPlayCustomSound(this PlayerControl pc, string sound, float volume = 1f, float pitch = 1f, bool force = false) { try { - if (!force && (!AmongUsClient.Instance.AmHost || !pc.IsModdedClient())) return; - if (!pc || pc.AmOwner) { Play(sound, volume, pitch); return; } + if (!force && (!AmongUsClient.Instance.AmHost || !pc.IsModdedClient())) return; + + long now = Utils.TimeStamp; + if (now == LastSoundRPCTS) return; + LastSoundRPCTS = now; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlayCustomSound, SendOption.None, pc.OwnerId); writer.Write(sound); writer.Write(volume); @@ -40,6 +46,10 @@ public static class CustomSoundsManager if (!AmongUsClient.Instance.AmHost) return; Play(sound, volume, pitch); + + long now = Utils.TimeStamp; + if (now == LastSoundRPCTS) return; + LastSoundRPCTS = now; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlayCustomSound, SendOption.None); writer.Write(sound); diff --git a/Modules/CustomTeamManager.cs b/Modules/CustomTeamManager.cs index 3f6699163..5a5105d2c 100644 --- a/Modules/CustomTeamManager.cs +++ b/Modules/CustomTeamManager.cs @@ -14,6 +14,7 @@ internal static class CustomTeamManager public static CustomTeam WinnerTeam; public static Dictionary> CustomTeamPlayerIds = []; public static List CustomTeamOptions = []; + public static Dictionary> CustomTeamOptionsCache = []; public static void LoadCustomTeams() { @@ -72,7 +73,16 @@ internal static class CustomTeamManager private static void UpdateEnabledTeams() { - EnabledCustomTeams = CustomTeamOptions.Where(x => x.Enabled.GetBool()).Select(x => x.Team).ToHashSet(); + EnabledCustomTeams.Clear(); + CustomTeamOptionsCache.Clear(); + + foreach (CustomTeamOptionGroup customTeamOptionGroup in CustomTeamOptions) + { + if (customTeamOptionGroup.Enabled.GetBool()) + EnabledCustomTeams.Add(customTeamOptionGroup.Team); + + CustomTeamOptionsCache[customTeamOptionGroup.Team] = customTeamOptionGroup.AllOptions.ConvertAll(x => x.GetBool()); + } } public static void InitializeCustomTeamPlayers() @@ -127,6 +137,44 @@ internal static class CustomTeamManager }); } + if (Utils.DoRPC) + { + var msg = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CTA, SendOption.Reliable); + msg.WritePacked(1); + msg.WritePacked(EnabledCustomTeams.Count); + EnabledCustomTeams.Do(x => x.Serialize(msg)); + AmongUsClient.Instance.FinishRpcImmediately(msg); + msg.Recycle(); + + msg = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CTA, SendOption.Reliable); + msg.WritePacked(2); + msg.WritePacked(CustomTeamPlayerIds.Count); + + foreach ((CustomTeam customTeam, HashSet ids) in CustomTeamPlayerIds) + { + msg.WritePacked(ids.Count); + ids.Do(x => msg.Write(x)); + msg.Write(customTeam.TeamName); + } + + AmongUsClient.Instance.FinishRpcImmediately(msg); + msg.Recycle(); + + msg = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CTA, SendOption.Reliable); + msg.WritePacked(3); + msg.WritePacked(CustomTeamOptionsCache.Count); + + foreach ((CustomTeam customTeam, List bools) in CustomTeamOptionsCache) + { + msg.WritePacked(bools.Count); + bools.ForEach(x => msg.Write(x)); + msg.Write(customTeam.TeamName); + } + + AmongUsClient.Instance.FinishRpcImmediately(msg); + msg.Recycle(); + } + return; static HashSet TakeAsManyAsSet(IEnumerable playerIds, CustomTeam customTeam) @@ -169,7 +217,44 @@ internal static class CustomTeamManager return CustomTeamPlayerIds[GetCustomTeam(seer.PlayerId)].Aggregate(string.Empty, (s, id) => s + Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), TargetArrow.GetArrows(seer, id))); } - private static readonly Dictionary> AliveTeamPlayers = []; + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + { + EnabledCustomTeams.Clear(); + Loop.Times(reader.ReadPackedInt32(), _ => EnabledCustomTeams.Add(new CustomTeam(reader.ReadString()))); + break; + } + case 2: + { + CustomTeamPlayerIds.Clear(); + Loop.Times(reader.ReadPackedInt32(), _ => + { + HashSet ids = []; + Loop.Times(reader.ReadPackedInt32(), _ => ids.Add(reader.ReadByte())); + string teamName = reader.ReadString(); + CustomTeamPlayerIds[EnabledCustomTeams.First(x => x.TeamName == teamName)] = ids; + }); + break; + } + case 3: + { + CustomTeamOptionsCache.Clear(); + Loop.Times(reader.ReadPackedInt32(), _ => + { + List bools = []; + Loop.Times(reader.ReadPackedInt32(), _ => bools.Add(reader.ReadBoolean())); + string teamName = reader.ReadString(); + CustomTeamOptionsCache[EnabledCustomTeams.First(x => x.TeamName == teamName)] = bools; + }); + break; + } + } + } + + private static readonly List AliveTeamPlayers = []; private static readonly List ToRemove = []; public static bool CheckCustomTeamGameEnd() { @@ -210,27 +295,20 @@ internal static class CustomTeamManager AliveTeamPlayers.Clear(); foreach ((CustomTeam teamKey, HashSet originalSet) in CustomTeamPlayerIds) { - HashSet aliveSet = null; foreach (var id in originalSet) { var pc = Utils.GetPlayerById(id); - if (pc.IsAlive()) + if (pc && pc.IsAlive()) { - aliveSet ??= []; - aliveSet.Add(id); + AliveTeamPlayers.Add(teamKey); + break; } } - if (aliveSet != null) - AliveTeamPlayers[teamKey] = aliveSet; } if (AliveTeamPlayers.Count == 1) { - var aliveTeam = AliveTeamPlayers.GetEnumerator(); - aliveTeam.MoveNext(); - - var onlyTeam = aliveTeam.Current.Key; - var winners = aliveTeam.Current.Value; + var onlyTeam = AliveTeamPlayers[0]; for (int i = 0; i < aapc.Count; i++) { @@ -243,7 +321,7 @@ internal static class CustomTeamManager WinnerTeam = onlyTeam; CustomWinnerHolder.SetWinnerOrAdditonalWinner(CustomWinner.CustomTeam); - CustomWinnerHolder.WinnerIds = winners; + CustomWinnerHolder.WinnerIds = CustomTeamPlayerIds[onlyTeam]; return true; } } @@ -287,9 +365,7 @@ internal static class CustomTeamManager public static bool IsSettingEnabledForTeam(CustomTeam team, CTAOption setting) { - CustomTeamOptionGroup optionsGroup = CustomTeamOptions.First(x => x.Team.Equals(team)); - List values = optionsGroup.AllOptions.ConvertAll(x => x.GetBool()); - return values[(int)setting]; + return CustomTeamOptionsCache[team][(int)setting]; } internal class CustomTeam @@ -330,6 +406,27 @@ internal static class CustomTeamManager { return TeamName.GetHashCode(); } + + public void Serialize(MessageWriter writer) + { + StringBuilder sb = new(); + sb.Append(TeamName); + sb.Append(';'); + sb.Append(RoleRevealScreenTitle); + sb.Append(';'); + sb.Append(RoleRevealScreenSubtitle); + sb.Append(';'); + sb.Append(RoleRevealScreenBackgroundColor); + sb.Append(';'); + + for (var index = 0; index < TeamMembers.Count; index++) + { + sb.Append(TeamMembers[index].ToString()); + sb.Append(','); + } + + writer.Write(sb.ToString()); + } } internal class CustomTeamOptionGroup diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5b6bb920f..f697ecea0 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -10,6 +10,7 @@ using EHR.Modules; using EHR.Modules.Extensions; using EHR.Roles; using Hazel; +using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; using UnityEngine; @@ -509,9 +510,11 @@ internal static class ExtendedPlayerControl public void RpcChangeRoleBasis(CustomRoles newCustomRole, bool loggerRoleMap = false, bool forced = false) { + if (!AmongUsClient.Instance.AmHost) return; + if (!forced) { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || !player || !player.IsAlive()) return; + if (!GameStates.IsInGame || !player || !player.IsAlive()) return; if (AntiBlackout.SkipTasks || ExileController.Instance) { @@ -1299,12 +1302,16 @@ internal static class ExtendedPlayerControl { try { - string name = isMeeting ? player.Data.PlayerName : player.name; - return name.RemoveHtmlTags(); + return (isMeeting ? player.Data.PlayerName : player.name).RemoveHtmlTags(); } catch (NullReferenceException nullReferenceException) { - Logger.Error($"{nullReferenceException.Message} - player is null? {!player}", "GetRealName"); + Logger.Warn($"{nullReferenceException.Message} - player is null? {!player}", "GetRealName"); + return string.Empty; + } + catch (Il2CppException il2CppException) + { + Logger.Warn($"{il2CppException.Message} - player is null? {!player}", "GetRealName"); return string.Empty; } catch (Exception exception) diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs index 85db2d463..1be5243f6 100644 --- a/Modules/Extensions/CollectionExtensions.cs +++ b/Modules/Extensions/CollectionExtensions.cs @@ -34,6 +34,11 @@ public static class CollectionExtensions return default(TKey); } + + // foreach will only throw System.InvalidOperationException: collection was modified; enumeration operation may not execute. + // if the number of elements inside the dictionary changes. + // If Count stays the same for the entire iteration, it can be safely enumerated without ToList or ToArray. + // Changing values does NOT modify the collection! /// /// Sets the value for all existing keys in a dictionary to a specific value @@ -41,7 +46,7 @@ public static class CollectionExtensions /// public void SetAllValues(TValue value) { - foreach (TKey key in dictionary.Keys.ToArray()) + foreach (TKey key in dictionary.Keys) dictionary[key] = value; } @@ -51,7 +56,7 @@ public static class CollectionExtensions /// The function to adjust the values with public void AdjustAllValues(Func adjust) { - foreach (TKey key in dictionary.Keys.ToArray()) + foreach (TKey key in dictionary.Keys) dictionary[key] = adjust(dictionary[key]); } diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index f32bbebac..b6028773f 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -53,7 +53,7 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS } } - public static void SendAllImmediately() + public static DataFlagRateLimiter.QueuedAction SendAllImmediately() { ForceWaitFrame = true; @@ -83,20 +83,22 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS sender.IsDirty = false; } } + + DataFlagRateLimiter.QueuedAction qa = null; if (PackedWriter != null) { if (PackedWriterMessages > 0) { PackedWriter.EndMessage(); + Logger.Info($"PackedWriter flush queued - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendAllImmediately"); var capturedWriter = PackedWriter; - DataFlagRateLimiter.Enqueue(() => + qa = DataFlagRateLimiter.Enqueue(() => { AmongUsClient.Instance.SendOrDisconnect(capturedWriter); capturedWriter.Recycle(); Logger.Info("PackedWriter flush queue finished and sent", "SendAllImmediately"); }, cleanup: capturedWriter.Recycle); - Logger.Info($"PackedWriter flush queued - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendAllImmediately"); } else { @@ -106,6 +108,8 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS PackedWriter = null; PackedWriterMessages = 0; + + return qa; } public static void SetDirtyToAll() @@ -292,6 +296,7 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS if (PackedWriter.Length > 1000 || PackedWriterMessages >= AmongUsClient.Instance.GetMaxMessagePackingLimit()) { PackedWriter.EndMessage(); + Logger.Info($"PackedWriter flush queued - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendOptionsArray"); var capturedWriter = PackedWriter; DataFlagRateLimiter.Enqueue(() => { @@ -299,7 +304,6 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS capturedWriter.Recycle(); Logger.Info("PackedWriter flush queue finished and sent", "SendOptionsArray"); }, cleanup: capturedWriter.Recycle); - Logger.Info($"PackedWriter flush queued - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendOptionsArray"); PackedWriterMessages = 0; PackedWriter = MessageWriter.Get(SendOption.Reliable); PackedWriter.StartMessage(26); diff --git a/Modules/LobbySharingAPI.cs b/Modules/LobbySharingAPI.cs index 408e9a1b7..3b32b9783 100644 --- a/Modules/LobbySharingAPI.cs +++ b/Modules/LobbySharingAPI.cs @@ -161,6 +161,7 @@ internal static class ExitGamePatch Logger.Msg($"Exiting game - reason: {reason}", "ExitGamePatch.Prefix"); GameStates.InGame = false; + GameEndChecker.LoadingEndScreen = false; Main.RealOptionsData?.Restore(GameOptionsManager.Instance.CurrentGameOptions); DataFlagRateLimiter.DropQueue(); diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 05b350cb6..4af9e413e 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -163,6 +163,7 @@ public enum CustomRPC Necronomicon, Stained, Entombed, + CTA, // Game Modes RoomRushDataSync, @@ -713,7 +714,7 @@ internal static class RPCHandlerPatch { byte bountyId = reader.ReadByte(); byte targetId = reader.ReadByte(); - (Main.PlayerStates[bountyId].Role as BountyHunter)?.ReceiveRPC(targetId); + (Main.PlayerStates[bountyId].Role as BountyHunter)?.ReceiveRPCTarget(targetId); break; } case CustomRPC.SyncBargainer: @@ -824,7 +825,7 @@ internal static class RPCHandlerPatch } case CustomRPC.SyncRabbit: { - Rabbit.ReceiveRPC(reader); + Rabbit.ReceiveRPCStatic(reader); break; } case CustomRPC.SyncYinYanger: @@ -961,12 +962,12 @@ internal static class RPCHandlerPatch if (operate == 1) { byte victim = reader.ReadByte(); - (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPC(victim); + (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPCVictim(victim); } else { - float timer = reader.ReadSingle(); - (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPC(timer); + bool reset = reader.ReadBoolean(); + (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPCTimer(reset); } break; @@ -1374,6 +1375,11 @@ internal static class RPCHandlerPatch LateTask.New(() => Stained.VioletNameList.Remove(id), 3f); break; } + case CustomRPC.CTA: + { + CustomTeamManager.ReceiveRPC(reader); + break; + } } } catch (Exception e) { Utils.ThrowException(e); } @@ -1390,7 +1396,7 @@ internal static class RPC if (targetId != -1) { ClientData client = Utils.GetClientById(targetId); - if (client == null || client.Character == null || !Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) return; + if (client == null || !client.Character || !Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) return; } if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1) return; @@ -1417,6 +1423,10 @@ internal static class RPC public static void PlaySoundRPC(byte playerID, Sounds sound) { if (AmongUsClient.Instance.AmHost) PlaySound(playerID, sound); + + long now = Utils.TimeStamp; + if (now == CustomSoundsManager.LastSoundRPCTS) return; + CustomSoundsManager.LastSoundRPCTS = now; SendOption sendOption = SendOption.Reliable; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 65d140f39..bb35c1ea5 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -169,11 +169,11 @@ public static class Utils switch (Vector2.Distance(pc.Pos(), location)) { - // case < 0.3f: - // { - // if (log) Logger.Warn($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is too close to the destination - Teleporting canceled", "TP"); - // return false; - // } + case < 0.3f: + { + if (log) Logger.Warn($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is too close to the destination - Teleporting canceled", "TP"); + return false; + } case < 1.5f when !GameStates.IsLobby: { if (log) Logger.Msg($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is too close to the destination - Changed to SendOption.None", "TP"); @@ -191,12 +191,18 @@ public static class Utils nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328)); nt.SetDirtyBit(uint.MaxValue); - if (NumSnapToCallsThisRound > 80) + if (NumSnapToCallsThisRound >= 80) { - if (log) Logger.Warn($"Too many SnapTo calls this round ({NumSnapToCallsThisRound}) - Changed to SendOption.None", "TP"); + if (log) Logger.Warn($"Too many Reliable SnapTo calls this round ({NumSnapToCallsThisRound}) - Changed to SendOption.None", "TP"); sendOption = SendOption.None; } + if (NumSnapToCallsThisRound >= 100) + { + if (log) Logger.Warn($"Too many Total SnapTo calls this round and this second ({NumSnapToCallsThisRound}) - Canceled", "TP"); + return false; + } + if (GameStates.CurrentServerType != GameStates.ServerType.Vanilla) sendOption = SendOption.Reliable; @@ -214,7 +220,7 @@ public static class Utils SubmergedCompatibility.CheckOutOfBoundsElevator(pc); } - if (sendOption == SendOption.Reliable) NumSnapToCallsThisRound++; + NumSnapToCallsThisRound++; return true; } @@ -2748,13 +2754,17 @@ public static class Utils if (!AmongUsClient.Instance.AmHost || GameStates.IsMeeting) yield break; const int frameBudget = 3; // milliseconds per frame - var stopwatch = new Stopwatch(); + var stopwatch = Stopwatch.StartNew(); var aapc = Main.CachedAlivePlayerControls(); + var hasValue = false; + var sender = CustomRpcSender.Create("NotifyRoles", sendOption, log: false); + sender.StartPackedMessage(); for (int seerIndex = 0; seerIndex < aapc.Count; seerIndex++) { PlayerControl seer = aapc[seerIndex]; - NotifyRoles(SpecifySeer: seer, NoCache: noCache, SendOption: sendOption); + hasValue |= WriteSetNameRpcsToSender(ref sender, false, noCache, false, false, false, false, seer, [seer], aapc, out bool senderWasCleared, sendOption); + if (senderWasCleared) hasValue = false; if (stopwatch.ElapsedMilliseconds >= frameBudget) { @@ -2764,6 +2774,8 @@ public static class Utils stopwatch.Start(); } } + + sender.SendMessage(!hasValue || sender.stream.Length <= 11); } [SuppressMessage("ReSharper", "InconsistentNaming")] @@ -2779,24 +2791,18 @@ public static class Utils SeerList = SpecifySeer ? [SpecifySeer] : apc; TargetList = SpecifyTarget ? [SpecifyTarget] : apc; - var sender = CustomRpcSender.Create("NotifyRoles", SendOption, log: false); var hasValue = false; + var sender = CustomRpcSender.Create("NotifyRoles", SendOption, log: false); + sender.StartPackedMessage(); for (byte seerIndex = 0; seerIndex < SeerList.Count; seerIndex++) { PlayerControl seer = SeerList[seerIndex]; hasValue |= WriteSetNameRpcsToSender(ref sender, ForMeeting, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting, MushroomMixup, seer, SeerList, TargetList, out bool senderWasCleared, SendOption); if (senderWasCleared) hasValue = false; - - if (sender.stream.Length > 500) - { - sender.SendMessage(); - sender = CustomRpcSender.Create("NotifyRoles", SendOption, log: false); - hasValue = false; - } } - sender.SendMessage(!hasValue || sender.stream.Length <= 3); + sender.SendMessage(!hasValue || sender.stream.Length <= 11); if (Options.CurrentGameMode != CustomGameMode.Standard) return; @@ -2838,26 +2844,26 @@ public static class Utils var selfTeamName = $"{iconTextLeft} {ColorString(seerTeam.GetColor(), $"{seerTeam}")} {iconTextRight}\n \n"; selfName = $"{selfTeamName}\r\n{seerRole.ToColoredString()}{roleNameUp}"; - sender.RpcSetName(seer, selfName, seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, seer, selfName, seer); return true; } if (seer.Is(CustomRoles.Car) && !forMeeting && !GameStates.IsEnded) { - sender.RpcSetName(seer, Car.Name); + CustomRpcSenderExtensions.RpcSetName(ref sender, seer, Car.Name); return true; } if (Main.PlayerStates.TryGetValue(seer.PlayerId, out var seerState) && seerState.Role is Tree { TreeSpriteActive: true } && !forMeeting && !GameStates.IsEnded) { - sender.RpcSetName(seer, Tree.Sprite); + CustomRpcSenderExtensions.RpcSetName(ref sender, seer, Tree.Sprite); return true; } if (forMeeting && Magistrate.CallCourtNextMeeting) { selfName = seer.Is(CustomRoles.Magistrate) ? GetString("Magistrate.CourtName") : GetString("Magistrate.JuryName"); - sender.RpcSetName(seer, selfName); + CustomRpcSenderExtensions.RpcSetName(ref sender, seer, selfName); return true; } @@ -3183,7 +3189,7 @@ public static class Utils if (selfName.EndsWith("")) selfName = selfName.Remove(selfName.Length - 7); if (selfName.EndsWith("")) selfName = selfName.Remove(selfName.Length - 8); - sender.RpcSetName(seer, selfName, seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, seer, selfName, seer); hasValue = true; bool onlySelfNameUpdateRequired = Options.CurrentGameMode switch @@ -3214,24 +3220,24 @@ public static class Utils if (target.PlayerId == seer.PlayerId) continue; if ((IsActive(SystemTypes.MushroomMixupSabotage) || mushroomMixup) && !forMeeting && target.IsAlive() && !seer.Is(CustomRoleTypes.Impostor) && seer.HasDesyncRole()) - sender.RpcSetName(target, "", seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, target, "", seer); else { if (target.Is(CustomRoles.Car) && !forMeeting && !GameStates.IsEnded) { - sender.RpcSetName(target, Car.Name, seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, target, Car.Name, seer); continue; } if (Main.PlayerStates.TryGetValue(target.PlayerId, out var targetState) && targetState.Role is Tree { TreeSpriteActive: true } && !forMeeting && !GameStates.IsEnded) { - sender.RpcSetName(target, Tree.Sprite, seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, target, Tree.Sprite, seer); continue; } if (forMeeting && Magistrate.CallCourtNextMeeting) { - sender.RpcSetName(target, GetString(target.Is(CustomRoles.Magistrate) ? "Magistrate.CourtName" : "Magistrate.JuryName"), seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, target, GetString(target.Is(CustomRoles.Magistrate) ? "Magistrate.CourtName" : "Magistrate.JuryName"), seer); return true; } @@ -3500,17 +3506,9 @@ public static class Utils if (targetName.EndsWith("")) targetName = targetName.Remove(targetName.Length - 7); if (targetName.EndsWith("")) targetName = targetName.Remove(targetName.Length - 8); - sender.RpcSetName(target, targetName, seer); + CustomRpcSenderExtensions.RpcSetName(ref sender, target, targetName, seer); hasValue = true; senderWasCleared = false; - - if (sender.stream.Length > 500) - { - sender.SendMessage(); - sender = CustomRpcSender.Create(sender.name, sender.sendOption); - hasValue = false; - senderWasCleared = true; - } } } catch (Exception ex) @@ -4153,7 +4151,7 @@ public static class Utils CustomRpcSender sender = CustomRpcSender.Create("Bloodlust fix", SendOption.Reliable); bool hasValue = false; hasValue |= sender.RpcSetRole(pc, RoleTypes.Impostor, pc.OwnerId); - Main.EnumeratePlayerControls().DoIf(x => x.IsImpostor(), x => hasValue |= sender.RpcSetRole(x, RoleTypes.Crewmate, pc.OwnerId)); + Main.EnumerateAlivePlayerControls().DoIf(x => x.IsImpostor(), x => hasValue |= sender.RpcSetRole(x, RoleTypes.Crewmate, pc.OwnerId)); sender.SendMessage(dispose: !hasValue); LateTask.New(() => pc.SetKillCooldown(), 0.2f, log: false); } @@ -5171,6 +5169,7 @@ public static class Utils DataFlagRateLimiter.Enqueue(() => { + if (!deadBodyParent) return; CreateDeadBody(position, colorId, deadBodyParent); PlayerControl playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); playerControl.PlayerId = deadBodyParent.PlayerId; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index bb7d1229a..721bfb4cf 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -1252,28 +1252,29 @@ internal static class IntroCutsceneDestroyPatch { lp.Notify(GetString("GLHF"), 2f); + bool hasValue = false; + var sender = CustomRpcSender.Create("Shapeshift After Pet Assign On Game Start", SendOption.Reliable); + sender.StartPackedMessage(); + foreach (PlayerControl pc in aapc) { - if (pc.IsHost()) continue; // Skip the host + if (pc.AmOwner) continue; // Skip the host try { - var sender = CustomRpcSender.Create("Shapeshift After Pet Assign On Game Start", SendOption.Reliable); - - if (AmongUsClient.Instance.AmClient) - pc.Shapeshift(pc, false); - - sender.AutoStartRpc(pc.NetId, 46); + sender.AutoStartRpc(pc.NetId, 46, pc.OwnerId); sender.WriteNetObject(pc); sender.Write(false); sender.EndRpc(); sender.Notify(pc, GetString("GLHF"), 2f); - sender.SendMessage(); + hasValue = true; } catch (Exception ex) { Logger.Fatal(ex.ToString(), "IntroPatch.RpcShapeshift"); } } + + sender.SendMessage(dispose: !hasValue); }, 4f, "Show Pet For Everyone"); LateTask.New(() => Main.ProcessShapeshifts = true, 2f, "Enable SS Processing"); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 2b8d6bed6..d77243b4f 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -818,6 +818,7 @@ internal static class MeetingHudStartPatch { string playername = pc.GetRealName(); if (Doppelganger.DoppelVictim.TryGetValue(pc.PlayerId, out string value)) playername = value; + playername = Main.AllPlayerNames.GetValueOrDefault(pc.PlayerId, playername); AddMsg(string.Format(GetString("SilencerDead"), playername, pc.PlayerId, CustomRoles.Silencer.ColoredTextByRole(GetString("SilencerKillTitle")))); } diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index 01d1ddcdd..383801d0a 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -377,6 +377,20 @@ internal static class ChangeRoleSettings { RPC.SyncCustomSettingsRPC(); } + else + { + CustomTeamManager.CustomTeams.Clear(); + CustomTeamManager.EnabledCustomTeams.Clear(); + CustomTeamManager.CustomTeamPlayerIds.Clear(); + CustomTeamManager.CustomTeamOptionsCache.Clear(); + StartGameHostPatch.BasisChangingAddons[CustomRoles.Bloodlust] = []; + StartGameHostPatch.BasisChangingAddons[CustomRoles.Nimble] = []; + StartGameHostPatch.BasisChangingAddons[CustomRoles.Examiner] = []; + StartGameHostPatch.BasisChangingAddons[CustomRoles.Finder] = []; + StartGameHostPatch.BasisChangingAddons[CustomRoles.Physicist] = []; + StartGameHostPatch.BasisChangingAddons[CustomRoles.Noisy] = []; + StartGameHostPatch.BasisChangingAddons[CustomRoles.Venom] = []; + } FallFromLadder.Reset(); CustomSabotage.Reset(); diff --git a/Patches/PetActionsPatch.cs b/Patches/PetActionsPatch.cs index 45bd760fd..fc224ecb7 100644 --- a/Patches/PetActionsPatch.cs +++ b/Patches/PetActionsPatch.cs @@ -32,12 +32,11 @@ internal static class LocalPetPatch AFKDetector.SetNotAFK(__instance.PlayerId); - if (!LastProcess.ContainsKey(__instance.PlayerId)) LastProcess.TryAdd(__instance.PlayerId, Utils.TimeStamp - 2); + LastProcess.TryAdd(__instance.PlayerId, Utils.TimeStamp - 2); if (LastProcess[__instance.PlayerId] + 1 >= Utils.TimeStamp) return true; ExternalRpcPetPatch.Prefix(__instance.MyPhysics, (byte)RpcCalls.Pet); - LastProcess[__instance.PlayerId] = Utils.TimeStamp; return !Main.CancelPetAnimation.Value || !__instance.GetCustomRole().PetActivatedAbility(); } @@ -68,6 +67,10 @@ internal static class ExternalRpcPetPatch AFKDetector.SetNotAFK(pc.PlayerId); + LastProcess.TryAdd(pc.PlayerId, Utils.TimeStamp - 2); + if (LastProcess[pc.PlayerId] + 1 >= Utils.TimeStamp) return; + LastProcess[pc.PlayerId] = Utils.TimeStamp; + if (!pc.inVent && !pc.inMovingPlat && !pc.walkingToVent @@ -89,11 +92,6 @@ internal static class ExternalRpcPetPatch } } - if (!LastProcess.ContainsKey(pc.PlayerId)) LastProcess.TryAdd(pc.PlayerId, Utils.TimeStamp - 2); - if (LastProcess[pc.PlayerId] + 1 >= Utils.TimeStamp) return; - - LastProcess[pc.PlayerId] = Utils.TimeStamp; - Logger.Info($"Player {pc.GetNameWithRole().RemoveHtmlTags()} petted their pet", "PetActionTrigger"); LateTask.New(() => OnPetUse(pc), 0.2f, $"OnPetUse: {pc.GetNameWithRole().RemoveHtmlTags()}", false); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b17b22670..09959c184 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1037,8 +1037,12 @@ internal static class ReportDeadBodyPatch Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target?.Object?.GetNameWithRole() ?? "null"}", "ReportDeadBody"); - foreach (KeyValuePair kvp in Main.PlayerStates) - kvp.Value.LastRoom = GetPlayerById(kvp.Key).GetPlainShipRoom(); + try + { + foreach (KeyValuePair kvp in Main.PlayerStates) + kvp.Value.LastRoom = GetPlayerById(kvp.Key)?.GetPlainShipRoom(); + } + catch (Exception e) { ThrowException(e); } if (!AmongUsClient.Instance.AmHost) return true; @@ -1639,6 +1643,9 @@ internal static class FixedUpdatePatch if (self) { + if (NumSnapToCallsThisRound > 80) + NumSnapToCallsThisRound = 80; + Zoom.OnFixedUpdate(); TextBoxPatch.CheckChatOpen(); @@ -2391,26 +2398,29 @@ internal static class GameDataCompleteTaskPatch public static void Postfix(PlayerControl pc, uint taskId) { - if (MeetingHud.Instance && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating) return; - - if (Options.CurrentGameMode == CustomGameMode.HideAndSeek && CustomHnS.PlayerRoles[pc.PlayerId].Interface.Team == Team.Crewmate && pc.IsAlive()) - { - var task = pc.myTasks.Find((Il2CppSystem.Predicate)(x => taskId == x.Id)); - Hider.OnSpecificTaskComplete(pc, task); - } - try { + if (MeetingHud.Instance && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating) return; + + if (Options.CurrentGameMode == CustomGameMode.HideAndSeek && CustomHnS.PlayerRoles[pc.PlayerId].Interface.Team == Team.Crewmate && pc.IsAlive()) + { + var task = pc.myTasks.Find((Il2CppSystem.Predicate)(x => taskId == x.Id)); + Hider.OnSpecificTaskComplete(pc, task); + } + + if (pc.IsAlive()) { var task = pc.myTasks.Find((Il2CppSystem.Predicate)(x => taskId == x.Id)); Benefactor.OnTaskComplete(pc, task); } + + Logger.Info($"TaskComplete: {pc.GetNameWithRole().RemoveHtmlTags()}", "CompleteTask"); } catch (Exception e) { ThrowException(e); } - - Logger.Info($"TaskComplete: {pc.GetNameWithRole().RemoveHtmlTags()}", "CompleteTask"); + Main.PlayerStates[pc.PlayerId].UpdateTask(pc); + NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); GameEndChecker.SetDirtyCheckEnd(); } diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 17a5e15d0..9376e6fc1 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -287,7 +287,7 @@ public static class ElectricTaskInitializePatch foreach (PlayerControl pc in Main.CachedAlivePlayerControls()) { if (pc.GetCustomRole().NeedUpdateOnLights() || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) - Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true, SendOption: SendOption.None); + Utils.NotifyRoles(SpecifyTarget: pc, SendOption: SendOption.None); } } @@ -313,7 +313,7 @@ public static class ElectricTaskCompletePatch CustomRoles role = pc.GetCustomRole(); if (role.NeedUpdateOnLights() || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) - Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true, SendOption: SendOption.None); + Utils.NotifyRoles(SpecifyTarget: pc, SendOption: SendOption.None); if (role == CustomRoles.Wiper) { diff --git a/Roles/Standard/Addons/Crewmate/Workhorse.cs b/Roles/Standard/Addons/Crewmate/Workhorse.cs index c65b95e35..c8a77e2d3 100644 --- a/Roles/Standard/Addons/Crewmate/Workhorse.cs +++ b/Roles/Standard/Addons/Crewmate/Workhorse.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using UnityEngine; using static EHR.Options; @@ -79,32 +80,30 @@ public class Workhorse : IAddon return canBeTarget; } - public static bool OnCompleteTask(PlayerControl pc) + public static void OnCompleteTask(PlayerControl pc) { - if (!CustomRoles.Workhorse.IsEnable() || PlayerIdList.Count >= CustomRoles.Workhorse.GetCount()) return false; - - if (CurrentGameMode != CustomGameMode.Standard) return false; - - if (pc.Is(CustomRoles.Snitch) && !OptionSnitchCanBeWorkhorse.GetBool()) return false; - - if (!IsAssignTarget(pc)) return false; - - if (IRandom.Instance.Next(100) >= SpawnChance.GetInt()) return false; - - pc.RpcSetCustomRole(CustomRoles.Workhorse); - TaskState taskState = pc.GetTaskState(); - taskState.AllTasksCount += NumLongTasks + NumShortTasks; - taskState.CompletedTasksCount++; - - if (AmongUsClient.Instance.AmHost) + try { - Add(pc.PlayerId); - pc.RpcResetTasks(false); - pc.SyncSettings(); - Main.PlayerStates[pc.PlayerId].TaskState.AllTasksCount = pc.Data.Tasks.Count; - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } + if (!CustomRoles.Workhorse.IsEnable() || PlayerIdList.Count >= CustomRoles.Workhorse.GetCount()) return; + if (CurrentGameMode != CustomGameMode.Standard) return; + if (pc.Is(CustomRoles.Snitch) && !OptionSnitchCanBeWorkhorse.GetBool()) return; + if (!IsAssignTarget(pc)) return; + if (IRandom.Instance.Next(100) >= SpawnChance.GetInt()) return; - return true; + pc.RpcSetCustomRole(CustomRoles.Workhorse); + TaskState taskState = pc.GetTaskState(); + taskState.AllTasksCount += NumLongTasks + NumShortTasks; + taskState.CompletedTasksCount++; + + if (AmongUsClient.Instance.AmHost) + { + Add(pc.PlayerId); + pc.RpcResetTasks(false); + pc.SyncSettings(); + Main.PlayerStates[pc.PlayerId].TaskState.AllTasksCount = pc.Data.Tasks.Count; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + } + catch (System.Exception e) { Utils.ThrowException(e); } } } \ No newline at end of file diff --git a/Roles/Standard/Crewmate/Investigate/Rabbit.cs b/Roles/Standard/Crewmate/Investigate/Rabbit.cs index 54d6f7c88..c3c82ae26 100644 --- a/Roles/Standard/Crewmate/Investigate/Rabbit.cs +++ b/Roles/Standard/Crewmate/Investigate/Rabbit.cs @@ -40,7 +40,7 @@ internal class Rabbit : RoleBase RabbitStates.Remove(playerId); } - public static void ReceiveRPC(MessageReader reader) + public static void ReceiveRPCStatic(MessageReader reader) { byte id = reader.ReadByte(); bool hasArrow = reader.ReadBoolean(); @@ -49,14 +49,14 @@ internal class Rabbit : RoleBase public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) { - if (pc == null || !RabbitStates.TryGetValue(pc.PlayerId, out RabbitState state)) return; + if (!RabbitStates.TryGetValue(pc.PlayerId, out RabbitState state)) return; state.OnTaskComplete(); } public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) { - if (seer == null || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || !RabbitStates.TryGetValue(seer.PlayerId, out RabbitState state)) return string.Empty; + if (seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || !RabbitStates.TryGetValue(seer.PlayerId, out RabbitState state)) return string.Empty; string suffix = state.Suffix; return hud ? $"{suffix}" : suffix; diff --git a/Roles/Standard/Crewmate/Miscellaneous/Carrier.cs b/Roles/Standard/Crewmate/Miscellaneous/Carrier.cs index 1bc6ce394..0eade9303 100644 --- a/Roles/Standard/Crewmate/Miscellaneous/Carrier.cs +++ b/Roles/Standard/Crewmate/Miscellaneous/Carrier.cs @@ -58,7 +58,8 @@ public class Carrier : RoleBase public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - if (!shapeshifting || !Location.HasValue) return true; + if (!shapeshifting) return true; + if (!Location.HasValue) return false; target.TP(Location.Value); target.Notify(Translator.GetString("Carrier.TargetNotify")); return false; diff --git a/Roles/Standard/Crewmate/Support/DonutDelivery.cs b/Roles/Standard/Crewmate/Support/DonutDelivery.cs index a988b5da8..e8ca67216 100644 --- a/Roles/Standard/Crewmate/Support/DonutDelivery.cs +++ b/Roles/Standard/Crewmate/Support/DonutDelivery.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using AmongUs.GameOptions; using EHR.Modules; using static EHR.Options; @@ -11,7 +10,6 @@ public class DonutDelivery : RoleBase { private const int Id = 642700; private static List Instances = []; - private static Dictionary StartingSpeed = []; private static OptionItem CD; private static OptionItem UseLimit; @@ -66,7 +64,6 @@ public class DonutDelivery : RoleBase public override void Init() { Instances = []; - StartingSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); } public override void Add(byte playerId) @@ -99,7 +96,7 @@ public class DonutDelivery : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { - if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; + if (!IsEnable) return false; killer.RpcRemoveAbilityUse(); @@ -123,7 +120,7 @@ public class DonutDelivery : RoleBase LateTask.New(() => { - Main.AllPlayerSpeed[target.PlayerId] = StartingSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); target.MarkDirtySettings(); }, SEDuration.GetFloat(), log: false); }, SEDelay.GetFloat(), log: false); diff --git a/Roles/Standard/Crewmate/Support/TimeMaster.cs b/Roles/Standard/Crewmate/Support/TimeMaster.cs index 999a26996..af7a3c686 100644 --- a/Roles/Standard/Crewmate/Support/TimeMaster.cs +++ b/Roles/Standard/Crewmate/Support/TimeMaster.cs @@ -131,27 +131,31 @@ internal class TimeMaster : RoleBase private static IEnumerator Rewind() { - const float delay = 0.3f; - long now = Utils.TimeStamp; - int length = TimeMasterRewindTimeLength.GetInt(); - Dictionary originalSpeeds = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); - Main.AllPlayerSpeed.SetAllValues(Main.MinSpeed); - ReportDeadBodyPatch.CanReport.SetAllValues(false); - + try { Rewinding = true; + const float delay = 0.3f; + long now = Utils.TimeStamp; + int length = TimeMasterRewindTimeLength.GetInt(); + float duration = length * delay + 0.55f; + + Main.AllPlayerSpeed.SetAllValues(Main.MinSpeed); + ReportDeadBodyPatch.CanReport.SetAllValues(false); + string notify = Utils.ColorString(Color.yellow, string.Format(Translator.GetString("TimeMasterRewindStart"), CustomRoles.TimeMaster.ToColoredString())); foreach (PlayerControl player in Main.EnumeratePlayerControls()) { if (player.inVent || player.MyPhysics?.Animations?.IsPlayingEnterVentAnimation() == true) player.MyPhysics?.RpcExitVent(player.GetClosestVent().Id); - player.ReactorFlash(flashDuration: length * delay + 0.55f); - player.Notify(notify, Math.Max((length * delay) + 0.55f, 4f)); + player.ReactorFlash(flashDuration: duration); + player.Notify(notify, Math.Max(duration, 4f)); player.MarkDirtySettings(); } + + yield return PlayerGameOptionsSender.SendAllImmediately().Wait(); yield return new WaitForSecondsRealtime(0.55f); @@ -196,21 +200,29 @@ internal class TimeMaster : RoleBase } } } - - } finally { Rewinding = false; - Main.AllPlayerSpeed = originalSpeeds; - ReportDeadBodyPatch.CanReport.SetAllValues(true); + Main.AllPlayerSpeed.AddRange(originalSpeeds); Utils.MarkEveryoneDirtySettings(); + ReportDeadBodyPatch.CanReport.SetAllValues(true); } } + public override void OnReportDeadBody() + { + AfterMeetingTasks(); + } + public override void AfterMeetingTasks() { - Rewinding = false; + if (Rewinding) + { + Rewinding = false; + Main.AllPlayerSpeed.SetAllValues(Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod)); + ReportDeadBodyPatch.CanReport.SetAllValues(true); + } } public override void OnFixedUpdate(PlayerControl player) diff --git a/Roles/Standard/Ghost/Bloodmoon.cs b/Roles/Standard/Ghost/Bloodmoon.cs index c3418913c..a2e37f73f 100644 --- a/Roles/Standard/Ghost/Bloodmoon.cs +++ b/Roles/Standard/Ghost/Bloodmoon.cs @@ -82,7 +82,7 @@ internal class Bloodmoon : IGhostRole if (pc.RpcCheckAndMurder(player, true)) player.Suicide(PlayerState.DeathReason.LossOfBlood, pc); } - var alivePlayers = Main.CachedAlivePlayerControls(); + /*var alivePlayers = Main.CachedAlivePlayerControls(); for (int index = 0; index < alivePlayers.Count; index++) { PlayerControl target = alivePlayers[index]; @@ -90,7 +90,7 @@ internal class Bloodmoon : IGhostRole if (!FastVector2.DistanceWithinRange(target.Pos(), pc.Pos(), 4f)) continue; target.Notify(string.Format(Translator.GetString("BloodmoonNearYou"), CustomRoles.Bloodmoon.ToColoredString()), sendOption: SendOption.None); - } + }*/ } public static void OnMeetingStart() diff --git a/Roles/Standard/Impostor/Concealing/Penguin.cs b/Roles/Standard/Impostor/Concealing/Penguin.cs index b92e6ae10..44d7af0ce 100644 --- a/Roles/Standard/Impostor/Concealing/Penguin.cs +++ b/Roles/Standard/Impostor/Concealing/Penguin.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using AmongUs.GameOptions; using EHR.Modules; using Hazel; @@ -16,13 +17,12 @@ public class Penguin : RoleBase private static OptionItem OptionSpeedDuringDrag; private static OptionItem OptionVictimCanUseAbilities; - private float AbductTimer; + private Stopwatch AbductTimer; private float AbductTimerLimit; private PlayerControl AbductVictim; private float Cooldown; - private int Count; private float DefaultSpeed; private bool IsGoose; @@ -33,7 +33,6 @@ public class Penguin : RoleBase private byte PenguinId = byte.MaxValue; private float SpeedDuringDrag; - private bool stopCount; private bool VictimCanUseAbilities; public override bool IsEnable => PenguinId != byte.MaxValue; @@ -101,8 +100,7 @@ public class Penguin : RoleBase PenguinId = playerId; Penguin_ = Utils.GetPlayerById(playerId); - AbductTimer = 255f; - stopCount = false; + AbductTimer = null; LastNotify = 0; } @@ -142,23 +140,25 @@ public class Penguin : RoleBase AmongUsClient.Instance.FinishRpcImmediately(writer); } - public void ReceiveRPC(byte victim) + public void ReceiveRPCVictim(byte victim) { if (victim == 255) { AbductVictim = null; - AbductTimer = 255f; + AbductTimer = null; + Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, true); } else { AbductVictim = Utils.GetPlayerById(victim); - AbductTimer = AbductTimerLimit; + AbductTimer = Stopwatch.StartNew(); + Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, false); } } - public void ReceiveRPC(float timer) + public void ReceiveRPCTimer(bool reset) { - AbductTimer = timer; + AbductTimer = reset ? null : Stopwatch.StartNew(); } private void AddVictim(PlayerControl target) @@ -168,7 +168,8 @@ public class Penguin : RoleBase if (Thanos.IsImmune(target)) return; AbductVictim = target; - AbductTimer = AbductTimerLimit; + AbductTimer = Stopwatch.StartNew(); + Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, false); Main.AllPlayerSpeed[PenguinId] = SpeedDuringDrag; Penguin_.MarkDirtySettings(); LogSpeed(); @@ -209,7 +210,8 @@ public class Penguin : RoleBase }, 1f, log: false); } - AbductTimer = 255f; + AbductTimer = null; + Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, true); Main.AllPlayerSpeed[PenguinId] = DefaultSpeed; Penguin_.MarkDirtySettings(); LogSpeed(); @@ -268,8 +270,6 @@ public class Penguin : RoleBase { if (!IsEnable) return; - stopCount = true; - if (!AmongUsClient.Instance.AmHost) return; if (!AbductVictim) return; @@ -301,31 +301,12 @@ public class Penguin : RoleBase if (!IsEnable) return; if (AbductVictim) Penguin_.MarkDirtySettings(); - - stopCount = false; } public override void OnFixedUpdate(PlayerControl pc) { if (!IsEnable) return; - if (!AmongUsClient.Instance.AmHost) return; - - if (!GameStates.IsInTask) return; - - if (!stopCount) - { - AbductTimer -= Time.fixedDeltaTime; - - Count++; - - if (Count >= 30) - { - Count = 0; - Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, AbductTimer); - } - } - if (AbductVictim) { if (!Penguin_.IsAlive() || Pelican.IsEaten(PenguinId) || !AbductVictim.IsAlive()) @@ -340,7 +321,7 @@ public class Penguin : RoleBase LastNotify = Utils.TimeStamp; } - if (AbductTimer <= 0f && !Penguin_.MyPhysics.Animations.IsPlayingAnyLadderAnimation() && !Penguin_.inMovingPlat && !Penguin_.onLadder) + if (AbductTimer.Elapsed.TotalSeconds >= AbductTimerLimit && !Penguin_.MyPhysics.Animations.IsPlayingAnyLadderAnimation() && !Penguin_.inMovingPlat && !Penguin_.onLadder) { // Set IsDead to true first (prevents ladder chase) if (!IsGoose) @@ -395,17 +376,21 @@ public class Penguin : RoleBase { LateTask.New(() => { - if (AbductVictim != null) Utils.TP(AbductVictim.NetTransform, position, log: false); + if (AbductVictim) Utils.TP(AbductVictim.NetTransform, position, log: false); }, 0.25f, log: false); } } } - else if (AbductTimer <= 100f) AbductTimer = 255f; + else + { + AbductTimer = null; + Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, true); + } } public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) { - if (seer == null || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || !Main.PlayerStates.TryGetValue(seer.PlayerId, out PlayerState state) || state.Role is not Penguin pg || pg.AbductVictim == null) return string.Empty; - return $"\u21b9 {(int)(pg.AbductTimer + 1f)}s"; + if (seer.PlayerId != PenguinId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || AbductTimer == null) return string.Empty; + return $"\u21b9 {(int)(AbductTimerLimit - AbductTimer.Elapsed.TotalSeconds + 1f)}s"; } } \ No newline at end of file diff --git a/Roles/Standard/Impostor/Concealing/Stasis.cs b/Roles/Standard/Impostor/Concealing/Stasis.cs index e2147af9c..3b2d51976 100644 --- a/Roles/Standard/Impostor/Concealing/Stasis.cs +++ b/Roles/Standard/Impostor/Concealing/Stasis.cs @@ -128,7 +128,7 @@ public class Stasis : RoleBase } } - Utils.MarkEveryoneDirtySettings(); + Utils.SyncAllSettings(); Main.Instance.StartCoroutine(Countdown()); return; @@ -150,11 +150,26 @@ public class Stasis : RoleBase catch (Exception e) { Utils.ThrowException(e); } ReportDeadBodyPatch.CanReport.SetAllValues(true); - Main.AllPlayerSpeed = originalSpeeds; + Main.AllPlayerSpeed.AddRange(originalSpeeds); Main.PlayerStates.Values.Do(x => x.IsBlackOut = false); Utils.MarkEveryoneDirtySettings(); pc.RpcResetAbilityCooldown(); } } + + public override void OnReportDeadBody() + { + if (UsingAbility) + { + UsingAbility = false; + ReportDeadBodyPatch.CanReport.SetAllValues(true); + Main.AllPlayerSpeed.SetAllValues(Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod)); + } + } + + public override void AfterMeetingTasks() + { + OnReportDeadBody(); + } } diff --git a/Roles/Standard/Impostor/Killing/BountyHunter.cs b/Roles/Standard/Impostor/Killing/BountyHunter.cs index 2c6c7ef2d..99ea08950 100644 --- a/Roles/Standard/Impostor/Killing/BountyHunter.cs +++ b/Roles/Standard/Impostor/Killing/BountyHunter.cs @@ -82,7 +82,7 @@ public class BountyHunter : RoleBase AmongUsClient.Instance.FinishRpcImmediately(writer); } - public void ReceiveRPC(byte targetId) + public void ReceiveRPCTarget(byte targetId) { Target = targetId; } diff --git a/Roles/Standard/Impostor/Killing/Butcher.cs b/Roles/Standard/Impostor/Killing/Butcher.cs index da333e43a..80be40afe 100644 --- a/Roles/Standard/Impostor/Killing/Butcher.cs +++ b/Roles/Standard/Impostor/Killing/Butcher.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using EHR.Modules; using Hazel; +using UnityEngine; namespace EHR.Roles; @@ -70,7 +71,6 @@ internal class Butcher : RoleBase RPCHandlerPatch.WhiteListFromRateLimitUntil(target.PlayerId, Utils.TimeStamp + 5); Vector2 ops = target.Pos(); - Vector2 originPos = killer.Pos(); var rd = IRandom.Instance; Main.Instance.StartCoroutine(SpawnFakeDeadBodies()); @@ -85,15 +85,12 @@ internal class Butcher : RoleBase Utils.RpcCreateDeadBody(location, (byte)target.CurrentOutfit.ColorId, target, SendOption.None); - if (i % 4 == 0) yield return null; + yield return new WaitForSecondsRealtime(0.2f); } - - yield return null; - killer.TP(originPos); } }, 0.05f, "Butcher Murder"); } - return base.OnCheckMurder(killer, target); + return true; } } \ No newline at end of file diff --git a/Roles/Standard/Impostor/Support/Capitalist.cs b/Roles/Standard/Impostor/Support/Capitalist.cs index 8122fd10b..6709cf05d 100644 --- a/Roles/Standard/Impostor/Support/Capitalist.cs +++ b/Roles/Standard/Impostor/Support/Capitalist.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using static EHR.Options; namespace EHR.Roles; @@ -51,10 +52,11 @@ internal class Capitalist : RoleBase }); } - public static bool AddTaskForPlayer(PlayerControl player) + public static void AddTaskForPlayer(PlayerControl player) { - if (CapitalistAddTask.TryGetValue(player.PlayerId, out int amount)) + try { + if (!CapitalistAddTask.TryGetValue(player.PlayerId, out int amount)) return; TaskState taskState = player.GetTaskState(); taskState.AllTasksCount += amount; CapitalistAddTask.Remove(player.PlayerId); @@ -63,9 +65,7 @@ internal class Capitalist : RoleBase player.SyncSettings(); Main.PlayerStates[player.PlayerId].TaskState.AllTasksCount = player.Data.Tasks.Count; Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - return false; } - - return true; + catch (Exception e) { Utils.ThrowException(e); } } } \ No newline at end of file diff --git a/Roles/Standard/Neutral/Evil/Vector.cs b/Roles/Standard/Neutral/Evil/Vector.cs index 25aef829b..57e170150 100644 --- a/Roles/Standard/Neutral/Evil/Vector.cs +++ b/Roles/Standard/Neutral/Evil/Vector.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using Hazel; using UnityEngine; using static EHR.Options; @@ -36,8 +37,9 @@ internal class Vector : RoleBase public override void Add(byte playerId) { On = true; - VectorVentCount[playerId] = 0; - VectorVentNumWin = MapWinCounts[SubmergedCompatibility.IsSubmerged() ? MapNames.Airship : Main.CurrentMap].GetInt(); + if (!AmongUsClient.Instance.AmHost) return; + VectorVentNumWin = MapWinCounts[SubmergedCompatibility.IsSubmerged() || Main.LIMap ? MapNames.Airship : Main.CurrentMap].GetInt(); + LateTask.New(() => Utils.SendRPC(CustomRPC.SyncRoleData, playerId, VectorVentNumWin), 8f); } public override void Init() @@ -71,7 +73,7 @@ internal class Vector : RoleBase { VectorVentCount.TryAdd(pc.PlayerId, 0); VectorVentCount[pc.PlayerId]++; - if (!pc.IsModdedClient()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (!pc.IsModdedClient()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, SendOption: SendOption.None); if (pc.AmOwner) CustomSoundsManager.Play("MarioJump"); if (AmongUsClient.Instance.AmHost && VectorVentCount[pc.PlayerId] >= VectorVentNumWin) @@ -81,4 +83,9 @@ internal class Vector : RoleBase CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); } } + + public void ReceiveRPC(MessageReader reader) + { + VectorVentNumWin = reader.ReadPackedInt32(); + } } diff --git a/Roles/Standard/Neutral/Evil/Workaholic.cs b/Roles/Standard/Neutral/Evil/Workaholic.cs index 8e61175f3..33ace292e 100644 --- a/Roles/Standard/Neutral/Evil/Workaholic.cs +++ b/Roles/Standard/Neutral/Evil/Workaholic.cs @@ -69,13 +69,16 @@ internal class Workaholic : RoleBase Main.AllPlayerSpeed[playerId] = WorkaholicSpeed.GetFloat(); } - public override void OnTaskComplete(PlayerControl player, int CompletedTasksCount, int AllTasksCount) + public override void OnTaskComplete(PlayerControl player, int completedTasksCount, int allTasksCount) { - if (CompletedTasksCount + 1 >= AllTasksCount && (!WorkaholicCannotWinAtDeath.GetBool() || player.IsAlive())) + if (completedTasksCount + 1 >= allTasksCount && (!WorkaholicCannotWinAtDeath.GetBool() || player.IsAlive())) { Logger.Info("Workaholic Tasks Finished", "Workaholic"); RPC.PlaySoundRPC(player.PlayerId, Sounds.KillSound); + GameEndChecker.ShouldNotCheck = true; + LateTask.New(() => GameEndChecker.ShouldNotCheck = false, 0.2f); + foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls()) pc.Suicide(pc.PlayerId == player.PlayerId ? PlayerState.DeathReason.Overtired : PlayerState.DeathReason.Ashamed, player); diff --git a/Roles/Standard/Neutral/Killing/Pelican.cs b/Roles/Standard/Neutral/Killing/Pelican.cs index 9216853ab..4d07920a3 100644 --- a/Roles/Standard/Neutral/Killing/Pelican.cs +++ b/Roles/Standard/Neutral/Killing/Pelican.cs @@ -121,12 +121,9 @@ public class Pelican : RoleBase return false; } - public static bool CanEat(PlayerControl pc, byte id) + public static bool CanEat(PlayerControl target) { - if (!pc.Is(CustomRoles.Pelican) || GameStates.IsMeeting) return false; - - PlayerControl target = Utils.GetPlayerById(id); - return target.IsAlive() && !target.inVent && !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !target.Is(CustomRoles.Pestilence) && !IsEaten(pc, id) && !IsEaten(id); + return !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !target.Is(CustomRoles.Pestilence) && (!target.Is(CustomRoles.Guardian) || !target.AllTasksCompleted()) && !IsEaten(target.PlayerId); } public static Vector2 GetBlackRoomPS() @@ -147,7 +144,7 @@ public class Pelican : RoleBase public override void GetProgressText(byte playerId, bool comms, StringBuilder resultText) { PlayerControl player = Utils.GetPlayerById(playerId); - if (player == null) + if (!player) { resultText.Append("Invalid"); return; @@ -163,9 +160,9 @@ public class Pelican : RoleBase .Append(")"); } - public static void EatPlayer(PlayerControl pc, PlayerControl target) + private static void EatPlayer(PlayerControl pc, PlayerControl target) { - if (pc == null || target == null || !CanEat(pc, target.PlayerId)) return; + if (!pc || !target) return; if (!EatenList.ContainsKey(pc.PlayerId)) EatenList.Add(pc.PlayerId, []); @@ -217,7 +214,7 @@ public class Pelican : RoleBase { PlayerControl target = Utils.GetPlayerById(tar); PlayerControl player = Utils.GetPlayerById(pc); - if (player == null || target == null) continue; + if (!player || !target) continue; target.TP(player); Main.AllPlayerSpeed[tar] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); @@ -259,7 +256,7 @@ public class Pelican : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { - if (CanEat(killer, target.PlayerId)) + if (CanEat(target)) { EatPlayer(killer, target); killer.SetKillCooldown();