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",