diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs index 2a272c5ae..52c94f185 100644 --- a/Modules/CustomNetObject.cs +++ b/Modules/CustomNetObject.cs @@ -27,59 +27,66 @@ namespace EHR public Vector2 Position; private string Sprite; + private int SnapToSendFrameCount; + public void RpcChangeSprite(string sprite) { if (!AmongUsClient.Instance.AmHost) return; if (this is not NaturalDisaster nd || nd.SpawnTimer <= 0f) Logger.Info($" Change Custom Net Object {GetType().Name} (ID {Id}) sprite", "CNO.RpcChangeSprite"); - Sprite = sprite; - - string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; - int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; - string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; - string skinId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId; - string petId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId; - string visorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId; bool notImportant = this is BedWarsItemGenerator || (this is NaturalDisaster { SpawnTimer: > 1f } && Options.CurrentGameMode != CustomGameMode.NaturalDisasters && GameStates.CurrentServerType == GameStates.ServerType.Vanilla); - var sender = CustomRpcSender.Create("CustomNetObject.RpcChangeSprite", notImportant ? SendOption.None : SendOption.Reliable, log: false); - MessageWriter writer = sender.stream; - sender.StartMessage(); - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "
" + sprite; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = 0; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = ""; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = ""; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = ""; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = ""; - writer.StartMessage(1); + SendOption channel = notImportant ? SendOption.None : SendOption.Reliable; + + DataFlagRateLimiter.Enqueue(() => { - writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); - PlayerControl.LocalPlayer.Data.Serialize(writer, false); - } - writer.EndMessage(); + Sprite = sprite; - try { playerControl.Shapeshift(PlayerControl.LocalPlayer, false); } - catch (Exception e) { Utils.ThrowException(e); } + string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; + int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; + string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; + string skinId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId; + string petId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId; + string visorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId; + var sender = CustomRpcSender.Create("CustomNetObject.RpcChangeSprite", channel, log: false); + MessageWriter writer = sender.stream; + sender.StartMessage(); + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "
" + sprite; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = 0; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = ""; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = ""; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = ""; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = ""; + writer.StartMessage(1); + { + writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); + PlayerControl.LocalPlayer.Data.Serialize(writer, false); + } + writer.EndMessage(); - sender.StartRpc(playerControl.NetId, (byte)RpcCalls.Shapeshift) - .WriteNetObject(PlayerControl.LocalPlayer) - .Write(false) - .EndRpc(); + try { playerControl.Shapeshift(PlayerControl.LocalPlayer, false); } + catch (Exception e) { Utils.ThrowException(e); } - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = name; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = colorId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = hatId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = skinId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = petId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = visorId; - writer.StartMessage(1); - { - writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); - PlayerControl.LocalPlayer.Data.Serialize(writer, false); - } - writer.EndMessage(); + sender.StartRpc(playerControl.NetId, (byte)RpcCalls.Shapeshift) + .WriteNetObject(PlayerControl.LocalPlayer) + .Write(false) + .EndRpc(); - sender.EndMessage(); - sender.SendMessage(); + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = name; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = colorId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = hatId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = skinId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = petId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = visorId; + writer.StartMessage(1); + { + writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); + PlayerControl.LocalPlayer.Data.Serialize(writer, false); + } + writer.EndMessage(); + + sender.EndMessage(); + sender.SendMessage(); + }, channel, calls: 2); } public void TP(Vector2 position) @@ -159,6 +166,9 @@ namespace EHR try { if (!AmongUsClient.Instance.AmHost) return; + + if (SnapToSendFrameCount++ < 5) return; + SnapToSendFrameCount = 0; if (AmongUsClient.Instance.AmClient) { @@ -180,170 +190,178 @@ namespace EHR if (GameStates.IsEnded || !AmongUsClient.Instance.AmHost) return; Logger.Info($" Create Custom Net Object {GetType().Name} (ID {MaxId + 1}) at {position} - Time since game start: {Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS}s", "CNO.CreateNetObject"); - - if (Options.CurrentGameMode == CustomGameMode.Standard && (!GameStates.InGame || !Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10)) + Main.Instance.StartCoroutine(CoRoutine()); + return; + + IEnumerator CoRoutine() { - if (GameStates.InGame && (!Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10)) + bool tooEarly = !Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10; + + if (Options.CurrentGameMode == CustomGameMode.Standard && (!GameStates.InGame || tooEarly)) { - Main.Instance.StartCoroutine(CoRoutine()); - - IEnumerator CoRoutine() + if (GameStates.InGame && tooEarly) { Logger.Info("Delaying CNO Spawn", "CustomNetObject.CreateNetObject"); while (GameStates.InGame && !GameStates.IsEnded && (!Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10)) yield return null; yield return new WaitForSecondsRealtime(3f); if (!GameStates.InGame || GameStates.IsEnded || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks) yield break; - CreateNetObject(sprite, position); } + else + yield break; } - - return; - } - - playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); - playerControl.PlayerId = 254; - playerControl.isNew = false; - playerControl.notRealPlayer = true; - try { playerControl.NetTransform.SnapTo(new Vector2(50f, 50f)); } - catch (Exception e) { Utils.ThrowException(e); } - - AmongUsClient.Instance.NetIdCnt += 1U; - MessageWriter msg = MessageWriter.Get(SendOption.Reliable); - msg.StartMessage(5); - msg.Write(AmongUsClient.Instance.GameId); - msg.StartMessage(4); - SpawnGameDataMessage item = AmongUsClient.Instance.CreateSpawnMessage(playerControl, -2, SpawnFlags.None); - item.SerializeValues(msg); - msg.EndMessage(); - - if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla) - { - for (uint i = 1; i <= 3; ++i) + yield return DataFlagRateLimiter.EnqueueAndWait(() => { + playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); + playerControl.PlayerId = 254; + playerControl.isNew = false; + playerControl.notRealPlayer = true; + + try { playerControl.NetTransform.SnapTo(new Vector2(50f, 50f)); } + catch (Exception e) { Utils.ThrowException(e); } + + AmongUsClient.Instance.NetIdCnt += 1U; + MessageWriter msg = MessageWriter.Get(SendOption.Reliable); + msg.StartMessage(5); + msg.Write(AmongUsClient.Instance.GameId); msg.StartMessage(4); - msg.WritePacked(2U); - msg.WritePacked(-2); - msg.Write((byte)SpawnFlags.None); - msg.WritePacked(1); - msg.WritePacked(AmongUsClient.Instance.NetIdCnt - i); - msg.StartMessage(1); + SpawnGameDataMessage item = AmongUsClient.Instance.CreateSpawnMessage(playerControl, -2, SpawnFlags.None); + item.SerializeValues(msg); msg.EndMessage(); + + if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla) + { + for (uint i = 1; i <= 3; ++i) + { + msg.StartMessage(4); + msg.WritePacked(2U); + msg.WritePacked(-2); + msg.Write((byte)SpawnFlags.None); + msg.WritePacked(1); + msg.WritePacked(AmongUsClient.Instance.NetIdCnt - i); + msg.StartMessage(1); + msg.EndMessage(); + msg.EndMessage(); + } + } + msg.EndMessage(); - } - } + AmongUsClient.Instance.SendOrDisconnect(msg); + msg.Recycle(); + }, calls: 3); - msg.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(msg); - msg.Recycle(); + if (PlayerControl.AllPlayerControls.Contains(playerControl)) + PlayerControl.AllPlayerControls.Remove(playerControl); - if (PlayerControl.AllPlayerControls.Contains(playerControl)) - PlayerControl.AllPlayerControls.Remove(playerControl); + playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; + playerControl.cosmetics.colorBlindText.color = Color.clear; + Position = position; + Sprite = sprite; + ++MaxId; + Id = MaxId; + if (MaxId == int.MaxValue) MaxId = int.MinValue; - if (this is not ShapeshiftMenuElement) - { - LateTask.New(() => - { - string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; - int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; - string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; - string skinId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId; - string petId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId; - string visorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId; - var sender = CustomRpcSender.Create("CustomNetObject.CreateNetObject", SendOption.Reliable, log: false); - MessageWriter writer = sender.stream; - sender.StartMessage(); - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "
" + sprite; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = 0; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = ""; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = ""; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = ""; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = ""; - writer.StartMessage(1); - { - writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); - PlayerControl.LocalPlayer.Data.Serialize(writer, false); - } - writer.EndMessage(); + AllObjects.Add(this); - try { playerControl.Shapeshift(PlayerControl.LocalPlayer, false); } - catch (Exception e) { Utils.ThrowException(e); } + yield return new WaitForSecondsRealtime(0.1f); - sender.StartRpc(playerControl.NetId, (byte)RpcCalls.Shapeshift) - .WriteNetObject(PlayerControl.LocalPlayer) - .Write(false) - .EndRpc(); - - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = name; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = colorId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = hatId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = skinId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = petId; - PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = visorId; - writer.StartMessage(1); - { - writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); - PlayerControl.LocalPlayer.Data.Serialize(writer, false); - } - writer.EndMessage(); - - try { playerControl.NetTransform.SnapTo(Position); } - catch (Exception e) { Utils.ThrowException(e); } - - sender.StartRpc(playerControl.NetTransform.NetId, (byte)RpcCalls.SnapTo) - .WriteVector2(Position) - .Write(playerControl.NetTransform.lastSequenceId) - .EndRpc(); - - sender.EndMessage(); - sender.SendMessage(); - }, 0.25f); - } - - playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; - playerControl.cosmetics.colorBlindText.color = Color.clear; - Position = position; - Sprite = sprite; - ++MaxId; - Id = MaxId; - if (MaxId == int.MaxValue) MaxId = int.MinValue; - - AllObjects.Add(this); - - LateTask.New(() => - { foreach (PlayerControl pc in Main.EnumeratePlayerControls()) { if (pc.AmOwner) continue; - var sender = CustomRpcSender.Create("CustomNetObject.CreateNetObject (2)", SendOption.Reliable, log: false); - MessageWriter writer = sender.stream; - sender.StartMessage(pc.OwnerId); - writer.StartMessage(1); + yield return DataFlagRateLimiter.EnqueueAndWait(() => { - writer.WritePacked(playerControl.NetId); - writer.Write(pc.PlayerId); - } - writer.EndMessage(); + var sender = CustomRpcSender.Create("CustomNetObject.CreateNetObject (2)", SendOption.Reliable, log: false); + MessageWriter writer = sender.stream; + sender.StartMessage(pc.OwnerId); + writer.StartMessage(1); + { + writer.WritePacked(playerControl.NetId); + writer.Write(pc.PlayerId); + } + writer.EndMessage(); - sender.StartRpc(playerControl.NetId, (byte)RpcCalls.MurderPlayer) - .WriteNetObject(playerControl) - .Write((int)MurderResultFlags.FailedError) - .EndRpc(); + sender.StartRpc(playerControl.NetId, (byte)RpcCalls.MurderPlayer) + .WriteNetObject(playerControl) + .Write((int)MurderResultFlags.FailedError) + .EndRpc(); - writer.StartMessage(1); - { - writer.WritePacked(playerControl.NetId); - writer.Write((byte)254); - } - writer.EndMessage(); + writer.StartMessage(1); + { + writer.WritePacked(playerControl.NetId); + writer.Write((byte)254); + } + writer.EndMessage(); - sender.EndMessage(); - sender.SendMessage(); + sender.EndMessage(); + sender.SendMessage(); + }, calls: 2); } playerControl.CachedPlayerData = PlayerControl.LocalPlayer.Data; - }, 0.1f); + + yield return new WaitForSecondsRealtime(0.15f); + + if (this is not ShapeshiftMenuElement) + { + yield return DataFlagRateLimiter.EnqueueAndWait(() => + { + string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; + int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; + string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; + string skinId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId; + string petId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId; + string visorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId; + var sender = CustomRpcSender.Create("CustomNetObject.CreateNetObject", SendOption.Reliable, log: false); + MessageWriter writer = sender.stream; + sender.StartMessage(); + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "
" + sprite; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = 0; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = ""; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = ""; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = ""; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = ""; + writer.StartMessage(1); + { + writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); + PlayerControl.LocalPlayer.Data.Serialize(writer, false); + } + writer.EndMessage(); + + try { playerControl.Shapeshift(PlayerControl.LocalPlayer, false); } + catch (Exception e) { Utils.ThrowException(e); } + + sender.StartRpc(playerControl.NetId, (byte)RpcCalls.Shapeshift) + .WriteNetObject(PlayerControl.LocalPlayer) + .Write(false) + .EndRpc(); + + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = name; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = colorId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = hatId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = skinId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = petId; + PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = visorId; + writer.StartMessage(1); + { + writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); + PlayerControl.LocalPlayer.Data.Serialize(writer, false); + } + writer.EndMessage(); + + try { playerControl.NetTransform.SnapTo(Position); } + catch (Exception e) { Utils.ThrowException(e); } + + sender.StartRpc(playerControl.NetTransform.NetId, (byte)RpcCalls.SnapTo) + .WriteVector2(Position) + .Write(playerControl.NetTransform.lastSequenceId) + .EndRpc(); + + sender.EndMessage(); + sender.SendMessage(); + }, calls: 2); + } + } } public virtual void OnMeeting() diff --git a/Modules/DataFlagRateLimiter.cs b/Modules/DataFlagRateLimiter.cs new file mode 100644 index 000000000..3324fcea6 --- /dev/null +++ b/Modules/DataFlagRateLimiter.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Hazel; + +namespace EHR.Modules; + +public static class DataFlagRateLimiter +{ + private class QueuedAction + { + public Action Action; + public int Cost; + public bool IsDone; + } + + // ========================= + // RELIABLE + // ========================= + + private static readonly Queue ReliableQueue = new(); + private static readonly Stopwatch ReliableTimer = Stopwatch.StartNew(); + private static int ReliableSent; + + private const int ReliableRateLimitPerSecond = 23; + + // ========================= + // UNRELIABLE (SendOption.None) + // ========================= + + private static readonly Queue UnreliableQueue = new(); + private static readonly Stopwatch UnreliableTimer = Stopwatch.StartNew(); + private static int UnreliableSent; + + private const int UnreliableRateLimitPerSecond = 23; + + // ========================= + // PUBLIC API + // ========================= + + public static void Enqueue(Action action, SendOption channel = SendOption.Reliable, int calls = 1) + { + var qa = new QueuedAction + { + Action = action, + Cost = calls, + IsDone = false + }; + + // Not needed on modded regions + if (GameStates.CurrentServerType is not (GameStates.ServerType.Local or GameStates.ServerType.Vanilla)) + { + Execute(qa); + return; + } + + switch (channel) + { + case SendOption.Reliable: + EnqueueInternal(ReliableQueue, ref ReliableSent, ReliableTimer, ReliableRateLimitPerSecond, qa); + break; + + case SendOption.None: // Unreliable + EnqueueInternal(UnreliableQueue, ref UnreliableSent, UnreliableTimer, UnreliableRateLimitPerSecond, qa); + break; + } + } + + public static System.Collections.IEnumerator EnqueueAndWait(Action action, SendOption channel = SendOption.Reliable, int calls = 1) + { + var qa = new QueuedAction + { + Action = action, + Cost = calls, + IsDone = false + }; + + // Not needed on modded regions + if (GameStates.CurrentServerType is not (GameStates.ServerType.Local or GameStates.ServerType.Vanilla)) + { + Execute(qa); + yield break; + } + + switch (channel) + { + case SendOption.Reliable: + EnqueueInternal(ReliableQueue, ref ReliableSent, ReliableTimer, ReliableRateLimitPerSecond, qa); + break; + + case SendOption.None: // Unreliable + EnqueueInternal(UnreliableQueue, ref UnreliableSent, UnreliableTimer, UnreliableRateLimitPerSecond, qa); + break; + } + + // Wait until executed + while (!qa.IsDone) + yield return null; + } + + // Called once per frame + public static void OnFixedUpdate() + { + ProcessQueue(ReliableQueue, ref ReliableSent, ReliableTimer, ReliableRateLimitPerSecond); + ProcessQueue(UnreliableQueue, ref UnreliableSent, UnreliableTimer, UnreliableRateLimitPerSecond); + } + + // ========================= + // INTERNAL LOGIC + // ========================= + + private static void EnqueueInternal( + Queue queue, + ref int sent, + Stopwatch timer, + int limit, + QueuedAction qa) + { + // Reset window every second + if (timer.ElapsedMilliseconds >= 1000) + { + timer.Restart(); + sent = 0; + } + + // Try immediate execution if no backlog + if (queue.Count == 0 && sent + qa.Cost <= limit) + { + Execute(qa); + sent += qa.Cost; + return; + } + + queue.Enqueue(qa); + } + + private static void ProcessQueue( + Queue queue, + ref int sent, + Stopwatch timer, + int limit) + { + // Reset window every second + if (timer.ElapsedMilliseconds >= 1000) + { + timer.Restart(); + sent = 0; + } + + while (queue.Count > 0) + { + var next = queue.Peek(); + + if (sent + next.Cost > limit) + break; + + queue.Dequeue(); + + Execute(next); + sent += next.Cost; + } + } + + private static void Execute(QueuedAction qa) + { + try + { + qa.Action(); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + finally + { + qa.IsDone = true; + } + } +} \ No newline at end of file diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 545e4fd99..00e9d1123 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -24,7 +24,6 @@ internal static class ExtendedPlayerControl public static readonly HashSet CancelBlackScreenFix = []; private static readonly List ResultBuffer = []; public static readonly HashSet TempExiled = []; - public static bool DontLowerSendTimer; extension(PlayerControl player) { @@ -214,46 +213,46 @@ internal static class ExtendedPlayerControl return; } - bool dead = player.Data.IsDead; - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(player.OwnerId); - writer.StartMessage(4); - writer.WritePacked(HudManager.Instance.MeetingPrefab.SpawnId); - writer.WritePacked(-2); - writer.Write((byte)SpawnFlags.None); - writer.WritePacked(1); - uint netIdCnt = AmongUsClient.Instance.NetIdCnt; - AmongUsClient.Instance.NetIdCnt = netIdCnt + 1U; - writer.WritePacked(netIdCnt); - writer.StartMessage(1); - writer.WritePacked(0); - writer.EndMessage(); - writer.EndMessage(); - player.Data.IsDead = visible; - writer.StartMessage(1); - writer.WritePacked(player.Data.NetId); - player.Data.Serialize(writer, true); - writer.EndMessage(); - writer.StartMessage(2); - writer.WritePacked(netIdCnt); - writer.Write((byte)RpcCalls.CloseMeeting); - writer.EndMessage(); - player.Data.IsDead = dead; - writer.StartMessage(1); - writer.WritePacked(player.Data.NetId); - player.Data.Serialize(writer, true); - writer.EndMessage(); - writer.StartMessage(5); - writer.WritePacked(netIdCnt); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); - - if (DontLowerSendTimer) return; - AmongUsClient.Instance.timer -= AmongUsClient.Instance.MinSendInterval; + DataFlagRateLimiter.Enqueue(() => + { + bool dead = player.Data.IsDead; + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(player.OwnerId); + writer.StartMessage(4); + writer.WritePacked(HudManager.Instance.MeetingPrefab.SpawnId); + writer.WritePacked(-2); + writer.Write((byte)SpawnFlags.None); + writer.WritePacked(1); + uint netIdCnt = AmongUsClient.Instance.NetIdCnt; + AmongUsClient.Instance.NetIdCnt = netIdCnt + 1U; + writer.WritePacked(netIdCnt); + writer.StartMessage(1); + writer.WritePacked(0); + writer.EndMessage(); + writer.EndMessage(); + player.Data.IsDead = visible; + writer.StartMessage(1); + writer.WritePacked(player.Data.NetId); + player.Data.Serialize(writer, true); + writer.EndMessage(); + writer.StartMessage(2); + writer.WritePacked(netIdCnt); + writer.Write((byte)RpcCalls.CloseMeeting); + writer.EndMessage(); + player.Data.IsDead = dead; + writer.StartMessage(1); + writer.WritePacked(player.Data.NetId); + player.Data.Serialize(writer, true); + writer.EndMessage(); + writer.StartMessage(5); + writer.WritePacked(netIdCnt); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }, calls: 3); } public ClientData GetClient() @@ -2444,19 +2443,36 @@ internal static class ExtendedPlayerControl public void SendGameData() { - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(5); - writer.Write(AmongUsClient.Instance.GameId); - writer.StartMessage(1); - writer.WritePacked(player.NetId); - player.Serialize(writer, false); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + DataFlagRateLimiter.Enqueue(() => + { + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(5); + writer.Write(AmongUsClient.Instance.GameId); + writer.StartMessage(1); + writer.WritePacked(player.NetId); + player.Serialize(writer, false); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }); + } - if (DontLowerSendTimer) return; - AmongUsClient.Instance.timer -= AmongUsClient.Instance.MinSendInterval; + public IEnumerator EnqueueSendGameData() + { + yield return DataFlagRateLimiter.EnqueueAndWait(() => + { + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(5); + writer.Write(AmongUsClient.Instance.GameId); + writer.StartMessage(1); + writer.WritePacked(player.NetId); + player.Serialize(writer, false); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }); } } diff --git a/Modules/FixedUpdateCaller.cs b/Modules/FixedUpdateCaller.cs index 03831151a..0c55b706b 100644 --- a/Modules/FixedUpdateCaller.cs +++ b/Modules/FixedUpdateCaller.cs @@ -52,6 +52,9 @@ public static class FixedUpdateCaller HudSpritePatch.Postfix(hudManager); } + try { DataFlagRateLimiter.OnFixedUpdate(); } + catch (Exception e) { Utils.ThrowException(e); } + if (!PlayerControl.LocalPlayer) return; if (amongUsClient.IsGameStarted) diff --git a/Modules/GameOptionsSender/GameOptionsSender.cs b/Modules/GameOptionsSender/GameOptionsSender.cs index 44c91a10f..3b71d4e8a 100644 --- a/Modules/GameOptionsSender/GameOptionsSender.cs +++ b/Modules/GameOptionsSender/GameOptionsSender.cs @@ -1,26 +1,24 @@ -using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using AmongUs.GameOptions; -using Hazel; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using InnerNet; -using UnityEngine; -using static EHR.GameStates; +using Hazel; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using InnerNet; +using UnityEngine; namespace EHR.Modules; public abstract class GameOptionsSender { - protected abstract bool IsDirty { get; set; } + protected abstract bool IsDirty { get; set; } protected virtual int TargetClientId => -1; - private Il2CppStructArray BuildOptionArray() - { - IGameOptions opt = BuildGameOptions(); - var currentGameMode = AprilFoolsMode.IsAprilFoolsModeToggledOn ? opt.AprilFoolsOnMode : opt.GameMode; + private Il2CppStructArray BuildOptionArray() + { + IGameOptions opt = BuildGameOptions(); + var currentGameMode = AprilFoolsMode.IsAprilFoolsModeToggledOn ? opt.AprilFoolsOnMode : opt.GameMode; // option => byte[] MessageWriter writer = MessageWriter.Get(); @@ -79,7 +77,7 @@ public abstract class GameOptionsSender private static void SendOptionsArray(Il2CppStructArray optionArray, byte logicOptionsIndex, int targetClientId) { - try + DataFlagRateLimiter.Enqueue(() => { MessageWriter writer = MessageWriter.Get(SendOption.Reliable); @@ -104,11 +102,10 @@ public abstract class GameOptionsSender AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); - } - catch (Exception ex) { Logger.Fatal(ex.ToString(), "GameOptionsSender.SendOptionsArray"); } + }); } - public abstract IGameOptions BuildGameOptions(); + public abstract IGameOptions BuildGameOptions(); protected virtual bool AmValid() { @@ -172,4 +169,4 @@ public abstract class GameOptionsSender protected static bool ForceWaitFrame; #endregion -} +} diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ddb5aa319..a67fe4ac6 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -3488,42 +3488,6 @@ public static class Utils return true; } - - public static void SendGameData() - { - int messages = 0; - int packingLimit = AmongUsClient.Instance.GetMaxMessagePackingLimit(); - - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(5); - writer.Write(AmongUsClient.Instance.GameId); - - foreach (NetworkedPlayerInfo playerinfo in GameData.Instance.AllPlayers) - { - if (writer.Length > 500 || messages >= packingLimit) - { - messages = 0; - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Clear(SendOption.Reliable); - writer.StartMessage(5); - writer.Write(AmongUsClient.Instance.GameId); - } - - writer.StartMessage(1); - writer.WritePacked(playerinfo.NetId); - playerinfo.Serialize(writer, false); - writer.EndMessage(); - - messages++; - } - - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); - - AmongUsClient.Instance.timer -= AmongUsClient.Instance.MinSendInterval; - } public static void SendGameDataTo(int targetClientId) { @@ -3538,27 +3502,40 @@ public static class Utils foreach (NetworkedPlayerInfo playerinfo in GameData.Instance.AllPlayers) { if (writer.Length > 500 || messages >= packingLimit) - { - messages = 0; - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Clear(SendOption.Reliable); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(targetClientId); - } + FlushWriter(); writer.StartMessage(1); writer.WritePacked(playerinfo.NetId); playerinfo.Serialize(writer, false); writer.EndMessage(); - + messages++; } - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + FlushWriter(); + return; + + void FlushWriter() + { + writer.EndMessage(); + + // IMPORTANT: capture this specific writer instance + var capturedWriter = writer; + + DataFlagRateLimiter.Enqueue(() => + { + AmongUsClient.Instance.SendOrDisconnect(capturedWriter); + capturedWriter.Recycle(); + }, calls: messages); + + // Create a NEW writer (do NOT reuse the old one) + writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(targetClientId); + + messages = 0; + } } public static string GetGameStateData(bool clairvoyant = false) @@ -4519,21 +4496,7 @@ public static class Utils } } - CombineSendTimeLowering(() => aapc.Do(x => x.SetChatVisible(true))); - } - - /// - /// Wraps code to run while is set to true, then lowers the sending timer once. - /// - public static void CombineSendTimeLowering([Annotations.InstantHandle] Action action) - { - ExtendedPlayerControl.DontLowerSendTimer = true; - - try { action(); } - catch (Exception e) { ThrowException(e); } - - ExtendedPlayerControl.DontLowerSendTimer = false; - AmongUsClient.Instance.timer -= AmongUsClient.Instance.MinSendInterval; + aapc.Do(x => x.SetChatVisible(true)); } public static bool TryCast(this Il2CppObjectBase obj, out T casted) where T : Il2CppObjectBase @@ -4720,64 +4683,68 @@ public static class Utils public static void RpcCreateDeadBody(Vector3 position, byte colorId, PlayerControl deadBodyParent, SendOption sendOption = SendOption.Reliable) { if (!deadBodyParent || !Main.IntroDestroyed || !AmongUsClient.Instance.AmHost) return; - CreateDeadBody(position, colorId, deadBodyParent); - PlayerControl playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); - playerControl.PlayerId = deadBodyParent.PlayerId; - playerControl.isNew = false; - playerControl.notRealPlayer = true; - playerControl.NetTransform.SnapTo(position); - AmongUsClient.Instance.NetIdCnt += 1U; - var sender = CustomRpcSender.Create("Utils.RpcCreateDeadBody", sendOption, true, false); - MessageWriter writer = sender.stream; - sender.StartMessage(); - writer.StartMessage(4); - SpawnGameDataMessage item = AmongUsClient.Instance.CreateSpawnMessage(playerControl, -2, SpawnFlags.None); - item.SerializeValues(writer); - writer.EndMessage(); - - if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla) + + DataFlagRateLimiter.Enqueue(() => { - for (uint i = 1; i <= 3; ++i) + CreateDeadBody(position, colorId, deadBodyParent); + PlayerControl playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); + playerControl.PlayerId = deadBodyParent.PlayerId; + playerControl.isNew = false; + playerControl.notRealPlayer = true; + playerControl.NetTransform.SnapTo(position); + AmongUsClient.Instance.NetIdCnt += 1U; + var sender = CustomRpcSender.Create("Utils.RpcCreateDeadBody", sendOption, true, false); + MessageWriter writer = sender.stream; + sender.StartMessage(); + writer.StartMessage(4); + SpawnGameDataMessage item = AmongUsClient.Instance.CreateSpawnMessage(playerControl, -2, SpawnFlags.None); + item.SerializeValues(writer); + writer.EndMessage(); + + if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla) { - writer.StartMessage(4); - writer.WritePacked(2U); - writer.WritePacked(-2); - writer.Write((byte)SpawnFlags.None); - writer.WritePacked(1); - writer.WritePacked(AmongUsClient.Instance.NetIdCnt - i); - writer.StartMessage(1); - writer.EndMessage(); - writer.EndMessage(); + for (uint i = 1; i <= 3; ++i) + { + writer.StartMessage(4); + writer.WritePacked(2U); + writer.WritePacked(-2); + writer.Write((byte)SpawnFlags.None); + writer.WritePacked(1); + writer.WritePacked(AmongUsClient.Instance.NetIdCnt - i); + writer.StartMessage(1); + writer.EndMessage(); + writer.EndMessage(); + } } - } - if (PlayerControl.AllPlayerControls.Contains(playerControl)) - PlayerControl.AllPlayerControls.Remove(playerControl); + if (PlayerControl.AllPlayerControls.Contains(playerControl)) + PlayerControl.AllPlayerControls.Remove(playerControl); - int baseColorId = playerControl.Data.DefaultOutfit.ColorId; - sender.StartRpc(playerControl.NetId, RpcCalls.SetColor) - .Write(playerControl.Data.NetId) - .Write(colorId) - .EndRpc(); - sender.StartRpc(playerControl.NetId, RpcCalls.MurderPlayer) - .WriteNetObject(playerControl) - .Write((int)MurderResultFlags.Succeeded) - .EndRpc(); - sender.StartRpc(playerControl.NetId, RpcCalls.SetColor) - .Write(playerControl.Data.NetId) - .Write(baseColorId) - .EndRpc(); - writer.StartMessage(1); - writer.WritePacked(playerControl.Data.NetId); - playerControl.Data.Serialize(writer, false); - writer.EndMessage(); - writer.StartMessage(5); - writer.WritePacked(playerControl.NetId); - writer.EndMessage(); - AmongUsClient.Instance.RemoveNetObject(playerControl); - Object.Destroy(playerControl.gameObject); - sender.EndMessage(); - sender.SendMessage(); + int baseColorId = playerControl.Data.DefaultOutfit.ColorId; + sender.StartRpc(playerControl.NetId, RpcCalls.SetColor) + .Write(playerControl.Data.NetId) + .Write(colorId) + .EndRpc(); + sender.StartRpc(playerControl.NetId, RpcCalls.MurderPlayer) + .WriteNetObject(playerControl) + .Write((int)MurderResultFlags.Succeeded) + .EndRpc(); + sender.StartRpc(playerControl.NetId, RpcCalls.SetColor) + .Write(playerControl.Data.NetId) + .Write(baseColorId) + .EndRpc(); + writer.StartMessage(1); + writer.WritePacked(playerControl.Data.NetId); + playerControl.Data.Serialize(writer, false); + writer.EndMessage(); + writer.StartMessage(5); + writer.WritePacked(playerControl.NetId); + writer.EndMessage(); + AmongUsClient.Instance.RemoveNetObject(playerControl); + Object.Destroy(playerControl.gameObject); + sender.EndMessage(); + sender.SendMessage(); + }, calls: 4); } public static MethodBase GetStateMachineMoveNext(string methodName) diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index 23054ed6b..275aea2e3 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -1143,9 +1143,8 @@ internal static class StartGameHostPatch yield return null; pc.Data.Disconnected = true; + yield return pc.Data.EnqueueSendGameData(); } - - Utils.SendGameData(); Logger.Info("Successfully set everyone's data as Disconnected", "StartGameHost"); @@ -1165,9 +1164,8 @@ internal static class StartGameHostPatch bool disconnected = Main.PlayerStates.TryGetValue(pc.PlayerId, out var state) && state.IsDead && state.deathReason == PlayerState.DeathReason.Disconnected; pc.Data.Disconnected = disconnected; + yield return pc.Data.EnqueueSendGameData(); } - - Utils.SendGameData(); } private static bool IsBasisChangingPlayer(byte id, CustomRoles role) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index d6bbcd837..2be5af1fe 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1489,38 +1489,32 @@ internal static class ReportDeadBodyPatch { Main.ProcessShapeshifts = false; - void Action() + foreach (PlayerControl pc in Main.EnumeratePlayerControls()) { - foreach (PlayerControl pc in Main.EnumeratePlayerControls()) + try { - try + if (pc.IsAlive()) { - if (pc.IsAlive()) + if (Camouflage.IsCamouflage && !Magistrate.CallCourtNextMeeting && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId)) + Camouflage.RpcSetSkin(pc, revertToDefault: true, forceRevert: true); + + if (Magistrate.CallCourtNextMeeting) { - if (Camouflage.IsCamouflage && !Magistrate.CallCourtNextMeeting && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId)) - Camouflage.RpcSetSkin(pc, revertToDefault: true, forceRevert: true); - - if (Magistrate.CallCourtNextMeeting) - { - string name = pc.GetRealName(); - RpcChangeSkin(pc, new NetworkedPlayerInfo.PlayerOutfit().Set(name, 15, "", "", "", "", "")); - } - - if (Main.CheckShapeshift.ContainsKey(pc.PlayerId)) - pc.RpcShapeshift(pc, false); - - if (pc.Is(CustomRoles.Truant)) - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + string name = pc.GetRealName(); + RpcChangeSkin(pc, new NetworkedPlayerInfo.PlayerOutfit().Set(name, 15, "", "", "", "", "")); } - else if (!pc.Data.IsDead && (!pc.AmOwner || !TempReviveHostRunning)) - pc.RpcExileV2(); - } - catch (Exception e) { ThrowException(e); } - } - } - if (!Camouflage.IsCamouflage && !Magistrate.CallCourtNextMeeting) Action(); - else CombineSendTimeLowering(Action); + if (Main.CheckShapeshift.ContainsKey(pc.PlayerId)) + pc.RpcShapeshift(pc, false); + + if (pc.Is(CustomRoles.Truant)) + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + } + else if (!pc.Data.IsDead && (!pc.AmOwner || !TempReviveHostRunning)) + pc.RpcExileV2(); + } + catch (Exception e) { ThrowException(e); } + } RPCHandlerPatch.RemoveExpiredWhiteList(); diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 7682bf8f3..92569c7b7 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -5,6 +5,7 @@ using System.Linq; using AmongUs.GameOptions; using BepInEx; using EHR.Gamemodes; +using EHR.Modules; using EHR.Roles; using HarmonyLib; using Hazel; @@ -492,19 +493,23 @@ internal static class ShipStatusSerializePatch foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { if (pc.IsRoleBlocked()) continue; - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.OwnerId); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Comms); - __instance.Serialize(writer, false); - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + + DataFlagRateLimiter.Enqueue(() => + { + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.OwnerId); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Comms); + __instance.Serialize(writer, false); + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }); } } @@ -513,19 +518,23 @@ internal static class ShipStatusSerializePatch foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { if (Main.AllPlayerSpeed.TryGetValue(pc.PlayerId, out float speed) && Mathf.Approximately(speed, Main.MinSpeed)) continue; - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.OwnerId); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Comms); - __instance.Serialize(writer, false); - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + + DataFlagRateLimiter.Enqueue(() => + { + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.OwnerId); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Comms); + __instance.Serialize(writer, false); + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }); } } } @@ -554,70 +563,73 @@ internal static class VentilationSystemDeterioratePatch if (pc.AmOwner) continue; if (player && pc != player) continue; - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - - if (BlockVentInteraction(pc)) + DataFlagRateLimiter.Enqueue(() => { - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.OwnerId); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = ShipStatus.Instance.AllVents.Count(vent => !pc.CanUseVent(vent.Id)); - List allPlayers = []; + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) + if (BlockVentInteraction(pc)) { - if (playerInfo && !playerInfo.Disconnected) - allPlayers.Add(playerInfo); - } + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.OwnerId); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = ShipStatus.Instance.AllVents.Count(vent => !pc.CanUseVent(vent.Id)); + List allPlayers = []; - int maxVents = Math.Min(vents, allPlayers.Count); - var blockedVents = 0; - writer.WritePacked(maxVents); - - foreach (Vent vent in pc.GetVentsFromClosest()) - { - if (!pc.CanUseVent(vent.Id)) + foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) { - writer.Write(allPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; + if (playerInfo && !playerInfo.Disconnected) + allPlayers.Add(playerInfo); } - if (blockedVents >= maxVents) - break; + int maxVents = Math.Min(vents, allPlayers.Count); + var blockedVents = 0; + writer.WritePacked(maxVents); + + foreach (Vent vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent(vent.Id)) + { + writer.Write(allPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; + } + + if (blockedVents >= maxVents) + break; + } + + writer.WritePacked(__instance.PlayersInsideVents.Count); + + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); } - - writer.WritePacked(__instance.PlayersInsideVents.Count); - - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + else { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.OwnerId); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + __instance.Serialize(writer, false); + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); } - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - } - else - { - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.OwnerId); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - __instance.Serialize(writer, false); - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - } - - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }); } } } diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc index a174e91a2..74c751420 100644 --- a/Resources/Lang/en_US.jsonc +++ b/Resources/Lang/en_US.jsonc @@ -27,7 +27,7 @@ "Server": "<#00ffa5>Server: {0}", "VersionMismatch": "{0} has a different version of the mod installed", "ModMismatch": "{0} has a different mod installed that isn't compatible with EHR <#ffff00>⚠", - "PrivacyPolicy": "Have you read and agree to the Privacy Policy available at the bottom of the README in the repository?\\nYou must accept it to continue.", + "PrivacyPolicy": "Have you read and agree to the Privacy Policy and Code Of Conduct available on the mod's official GitHub repository?\\nYou must accept them to continue.", "Yes": "Yes", "No": "No", "None": "None",