Files
EndlessHostRoles/Modules/GameOptionsSender/GameOptionsSender.cs
T
2026-09-24 20:26:16 +02:00

183 lines
6.1 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using AmongUs.GameOptions;
using Hazel;
using UnityEngine;
namespace EHR.Modules;
public abstract class GameOptionsSender
{
protected abstract bool IsDirty { get; set; }
private byte[] BuildOptionArray()
{
IGameOptions opt = BuildGameOptions();
var currentGameMode = AprilFoolsMode.IsAprilFoolsModeToggledOn ? opt.AprilFoolsOnMode : opt.GameMode;
// option => byte[]
MessageWriter writer = MessageWriter.Get();
writer.Write(opt.Version);
writer.StartMessage(0);
writer.Write((byte)currentGameMode);
#if IL2CPP
if (opt.TryCastFast(out NormalGameOptionsV11 normalOpt))
NormalGameOptionsV11.Serialize(writer, normalOpt);
else if (opt.TryCastFast(out HideNSeekGameOptionsV11 hnsOpt))
#else
if (opt is NormalGameOptionsV11 normalOpt)
NormalGameOptionsV11.Serialize(writer, normalOpt);
else if (opt is HideNSeekGameOptionsV11 hnsOpt)
#endif
HideNSeekGameOptionsV11.Serialize(writer, hnsOpt);
else
Logger.Error("Option cast failed", ToString());
writer.EndMessage();
byte[] optionArray = writer.ToByteArray(false);
writer.Recycle();
return optionArray;
}
protected virtual void SendGameOptions()
{
byte[] optionArray = BuildOptionArray();
SendOptionsArray(optionArray);
}
protected virtual IEnumerator SendGameOptionsAsync()
{
byte[] optionArray = BuildOptionArray();
yield return SendOptionsArrayAsync(optionArray);
}
private void SendOptionsArray(byte[] optionArray)
{
int count = GameManager.Instance.LogicComponents.Count;
for (byte i = 0; i < count; i++)
{
GameLogicComponent logicComponent = GameManager.Instance.LogicComponents[i];
if (logicComponent is LogicOptions) SendOptionsArray(optionArray, i);
}
}
private IEnumerator SendOptionsArrayAsync(byte[] optionArray)
{
int count = GameManager.Instance.LogicComponents.Count;
for (byte i = 0; i < count; i++)
{
GameLogicComponent logicComponent = GameManager.Instance.LogicComponents[i];
if (logicComponent is LogicOptions) yield return SendOptionsArrayAsync(optionArray, i);
yield return WaitFrameIfNecessary();
}
}
protected abstract void SendOptionsArray(byte[] optionArray, byte logicOptionsIndex);
protected abstract IEnumerator SendOptionsArrayAsync(byte[] optionArray, byte logicOptionsIndex);
public abstract IGameOptions BuildGameOptions();
protected virtual bool AmValid()
{
return true;
}
#region Static
public static readonly List<GameOptionsSender> AllSenders = [new NormalGameOptionsSender()];
protected static MessageWriter PackedWriter;
protected static int PackedWriterMessages;
// Currently, a LogicOptions serialize is 155-156 bytes. 11 bytes are needed for the packet headers.
// Safe to pack until the length is over 1000, as it's impossible to exceed 1200 with 156 bytes per message.
public static IEnumerator SendDirtyGameOptionsContinuously()
{
try
{
while (GameStates.InGame || GameStates.IsLobby)
{
if (AntiBlackout.SkipTasks)
{
yield return new WaitForSecondsRealtime(1f);
continue;
}
if (GameStates.InGame)
{
PackedWriterMessages = 0;
PackedWriter = MessageWriter.Get(SendOption.Reliable);
PackedWriter.StartMessage(26);
PackedWriter.WritePacked(AmongUsClient.Instance.GameId);
}
for (var index = 0; index < AllSenders.Count; index++)
{
yield return WaitFrameIfNecessary();
if (index >= AllSenders.Count) break;
GameOptionsSender sender = AllSenders[index];
if (sender == null || !sender.AmValid())
{
AllSenders.RemoveAt(index);
index--;
continue;
}
if (sender.IsDirty)
yield return sender.SendGameOptionsAsync();
sender.IsDirty = false;
}
yield return WaitFrameIfNecessary();
if (PackedWriterMessages > 0 && PackedWriter != null)
{
PackedWriter.EndMessage();
yield return DataFlagRateLimiter.Enqueue(() => AmongUsClient.Instance.SendOrDisconnect(PackedWriter)).Wait();
Logger.Info($"PackedWriter flush finished - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendDirtyGameOptionsContinuously");
}
PackedWriter?.Recycle();
PackedWriter = null;
PackedWriterMessages = 0;
ForceWaitFrame = true;
yield return WaitFrameIfNecessary();
}
}
finally
{
ActiveCoroutine = null;
PackedWriter?.Recycle();
PackedWriter = null;
PackedWriterMessages = 0;
}
}
protected static IEnumerator WaitFrameIfNecessary()
{
if (ForceWaitFrame || Stopwatch.ElapsedMilliseconds >= FrameBudget)
{
ForceWaitFrame = false;
Stopwatch.Reset();
yield return null;
Stopwatch.Start();
}
}
public static Coroutine ActiveCoroutine;
private static readonly Stopwatch Stopwatch = new();
private const int FrameBudget = 3; // in milliseconds
protected static bool ForceWaitFrame;
#endregion
}