Files
EndlessHostRoles/Modules/CustomRpcSender.cs
T
2026-09-27 12:48:20 +02:00

951 lines
33 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using AmongUs.GameOptions;
using EHR.Modules;
using EHR.Roles;
using HarmonyLib;
using Hazel;
using InnerNet;
using UnityEngine;
namespace EHR;
public class CustomRpcSender
{
public enum State
{
BeforeInit = 0, // Cannot do anything before initialization
Ready, // Ready to send - StartMessage and SendMessage can be executed
InRootPackedMessage, // State where only GameDataTo submessages can be started
InRootMessage, // State between StartMessage and EndMessage - StartRpc and EndMessage can be executed
InRpc, // State between StartRpc and EndRpc - Write and EndRpc can be executed
Finished // Nothing can be done after sending
}
private readonly List<MessageWriter> doneStreams = [];
private readonly bool isUnsafe;
private readonly bool log;
public readonly string name;
private readonly OnSendDelegateType onSendDelegate;
public readonly SendOption sendOption;
public bool checkLength;
// 0~: targetClientId (GameDataTo)
// -1: All players (GameData)
// -2: Not set
public int currentRpcTarget;
public bool packed;
private State currentState/* = State.BeforeInit*/;
public int messages;
public MessageWriter stream;
private CustomRpcSender(string name, SendOption sendOption, bool isUnsafe, bool log)
{
stream = MessageWriter.Get(sendOption);
this.name = name;
this.isUnsafe = isUnsafe;
this.sendOption = sendOption;
this.log = log;
currentRpcTarget = -2;
packed = false;
checkLength = true;
onSendDelegate = () => { };
messages = 0;
currentState = State.Ready;
if (log) Logger.Info($"\"{name}\" is ready", "CustomRpcSender");
}
public State CurrentState
{
get => currentState;
set
{
if (isUnsafe)
currentState = value;
else
Logger.Warn("CurrentState can only be overwritten when isUnsafe is true", "CustomRpcSender");
}
}
public static CustomRpcSender Create(string name = "No Name Sender", SendOption sendOption = SendOption.None, bool isUnsafe = false, bool log = true)
{
return new(name, sendOption, isUnsafe, log);
}
public CustomRpcSender AutoStartRpc(
uint targetNetId,
RpcCalls rpcCall,
int targetClientId = -1,
[CallerFilePath] string callerPath = "",
[CallerLineNumber] int callerLine = 0)
{
// ReSharper disable ExplicitCallerInfoArgument
return AutoStartRpc(targetNetId, (byte)rpcCall, targetClientId, callerPath, callerLine);
// ReSharper restore ExplicitCallerInfoArgument
}
public CustomRpcSender AutoStartRpc(
uint targetNetId,
byte callId,
int targetClientId = -1,
[CallerFilePath] string callerPath = "",
[CallerLineNumber] int callerLine = 0)
{
if (targetClientId == -2) targetClientId = -1;
if (currentState is not State.Ready and not State.InRootPackedMessage and not State.InRootMessage)
{
var errorMsg = $"Tried to start RPC automatically, but State is not Ready or InRootPackedMessage or InRootMessage (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
if (currentState == State.InRootPackedMessage && targetClientId < 0)
{
var errorMsg = $"Tried to start RPC automatically, but State is InRootPackedMessage and the requested targetClientId is negative. Only GameDataTo messages can be started in this state. (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
if (currentRpcTarget != targetClientId)
{
// StartMessage processing
if (currentState == State.InRootMessage)
EndMessage(startNew: !packed && messages < AmongUsClient.Instance.GetMaxMessagePackingLimit());
else if (messages > 0) // state is Ready or InRootPackedMessage
{
if (currentState == State.InRootPackedMessage)
{
stream.EndMessage();
currentState = State.Ready;
doneStreams.Add(stream);
stream = MessageWriter.Get(sendOption);
messages = 0;
StartPackedMessage(); // assume the next message should be in a PackedGameDataTo message as well
}
else // state is Ready
{
doneStreams.Add(stream);
stream = MessageWriter.Get(sendOption);
messages = 0;
}
}
StartMessage(targetClientId);
}
StartRpc(targetNetId, callId);
return this;
}
public void SendMessage(bool dispose = false)
{
if (!dispose)
{
if (currentState == State.InRootMessage) EndMessage();
if (currentState == State.InRootPackedMessage)
{
if (stream.Length <= 11)
dispose = true;
else
EndMessage();
}
if (!dispose && currentState != State.Ready)
{
if (currentState == State.Finished)
{
Logger.Warn($"Tried to send Message but \"{name}\" is already Finished", "CustomRpcSender.Warn");
return;
}
var errorMsg = $"Tried to send Message but State is not Ready (in: \"{name}\", state: {currentState})";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
}
if (stream.Length > 1200 && !dispose) Logger.Msg($"Large packet \"{name}\" is sending ({stream.Length} bytes)", "CustomRpcSender");
else if (log || stream.Length > 3) Logger.Info($"\"{name}\" is finished (Length: {stream.Length}, dispose: {dispose}, sendOption: {sendOption})", "CustomRpcSender");
if (!dispose)
{
if (doneStreams.Count > 0)
{
var sb = new StringBuilder(" + Lengths: ");
doneStreams.ForEach(x =>
{
if (x.Length > 1200) Logger.Msg($"Large reliable packet \"{name}\" is sending ({x.Length} bytes)", "CustomRpcSender");
else if (log || x.Length > 3) sb.Append($" | {x.Length}");
AmongUsClient.Instance.SendOrDisconnect(x);
x.Recycle();
});
Logger.Info(sb.ToString(), "CustomRpcSender");
doneStreams.Clear();
}
AmongUsClient.Instance.SendOrDisconnect(stream);
onSendDelegate();
}
packed = false;
currentRpcTarget = -2;
messages = 0;
currentState = State.Finished;
stream.Recycle();
}
private CustomRpcSender Write(Action<MessageWriter> action)
{
if (currentState != State.InRpc)
{
var errorMsg = $"Tried to write into RPC, but State is not InRpc (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
action(stream);
return this;
}
private delegate void OnSendDelegateType();
#region Start/End Message
public CustomRpcSender StartMessage(int targetClientId = -1)
{
if (currentState is not State.Ready and not State.InRootPackedMessage)
{
var errorMsg = $"Tried to start Message but State is not Ready or InRootPackedMessage (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
if (currentState == State.InRootPackedMessage && targetClientId < 0)
{
var errorMsg = $"Tried to start Message, but State is InRootPackedMessage and the requested targetClientId is negative. Only GameDataTo messages can be started in this state. (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
if (checkLength && stream.Length > 500)
{
if (currentState == State.InRootPackedMessage)
{
stream.EndMessage();
doneStreams.Add(stream);
stream = MessageWriter.Get(sendOption);
messages = 0;
currentState = State.Ready;
StartPackedMessage();
}
else
{
doneStreams.Add(stream);
stream = MessageWriter.Get(sendOption);
messages = 0;
}
}
if (targetClientId < 0)
{
// RPC for everyone
stream.StartMessage(5);
stream.Write(AmongUsClient.Instance.GameId);
}
else
{
// RPC (Desync) to a specific client
stream.StartMessage(6);
stream.Write(AmongUsClient.Instance.GameId);
stream.WritePacked(targetClientId);
}
currentRpcTarget = targetClientId;
currentState = State.InRootMessage;
return this;
}
public CustomRpcSender StartPackedMessage()
{
if (currentState != State.Ready)
{
var errorMsg = $"Tried to start Packed Message but State is not Ready (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
if (checkLength && stream.Length > 500)
{
doneStreams.Add(stream);
stream = MessageWriter.Get(sendOption);
messages = 0;
}
stream.StartMessage(26);
stream.WritePacked(AmongUsClient.Instance.GameId);
currentRpcTarget = -2;
currentState = State.InRootPackedMessage;
packed = true;
return this;
}
public CustomRpcSender EndMessage(bool startNew = false)
{
if (currentState is not State.InRootMessage and not State.InRootPackedMessage)
{
var errorMsg = $"Tried to end Message but State is not InRootMessage or InRootPackedMessage (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
bool wasPackedContext = packed;
bool closingPackedRoot = currentState == State.InRootPackedMessage;
stream.EndMessage();
if (closingPackedRoot)
packed = false;
if (startNew)
{
if (wasPackedContext && !closingPackedRoot)
{
// Close outer packed root too
stream.EndMessage();
}
doneStreams.Add(stream);
stream = MessageWriter.Get(sendOption);
messages = 0;
currentState = State.Ready;
currentRpcTarget = -2;
if (wasPackedContext)
StartPackedMessage();
return this;
}
currentRpcTarget = -2;
currentState = packed ? State.InRootPackedMessage : State.Ready;
return this;
}
#endregion
#region Start/End Rpc
public CustomRpcSender StartRpc(uint targetNetId, RpcCalls rpcCall)
{
return StartRpc(targetNetId, (byte)rpcCall);
}
public CustomRpcSender StartRpc(
uint targetNetId,
byte callId)
{
if (currentState != State.InRootMessage)
{
var errorMsg = $"Tried to start RPC but State is not InRootMessage (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
if (messages >= AmongUsClient.Instance.GetMaxMessagePackingLimit())
{
int targetClientId = currentRpcTarget;
EndMessage(startNew: true);
StartMessage(targetClientId);
}
messages++;
stream.StartMessage(2);
stream.WritePacked(targetNetId);
stream.Write(callId);
currentState = State.InRpc;
return this;
}
public CustomRpcSender EndRpc()
{
if (currentState != State.InRpc)
{
var errorMsg = $"Tried to end RPC but State is not InRpc (in: \"{name}\")";
if (isUnsafe)
Logger.Warn(errorMsg, "CustomRpcSender.Warn");
else
throw new InvalidOperationException(errorMsg);
}
stream.EndMessage();
currentState = State.InRootMessage;
return this;
}
#endregion
// Write
#region PublicWriteMethods
public CustomRpcSender Write(float val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(string val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(ulong val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(int val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(uint val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(ushort val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(byte val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(sbyte val)
{
return Write(w => w.Write(val));
}
public CustomRpcSender Write(bool val)
{
return Write(w => w.Write(val));
}
/*
public CustomRpcSender Write(byte[] bytes)
{
return Write(w => w.Write(bytes));
}
public CustomRpcSender Write(byte[] bytes, int offset, int length)
{
return Write(w => w.Write(bytes, offset, length));
}
public CustomRpcSender WriteBytesAndSize(byte[] bytes)
{
return Write(w => w.WriteBytesAndSize(bytes));
}
*/
public CustomRpcSender WritePacked(int val)
{
return Write(w => w.WritePacked(val));
}
public CustomRpcSender WritePacked(uint val)
{
return Write(w => w.WritePacked(val));
}
public CustomRpcSender WriteNetObject(InnerNetObject obj)
{
return Write(w => w.WriteNetObject(obj));
}
public CustomRpcSender WriteVector2(Vector2 vector2)
{
return Write(w => NetHelpers.WriteVector2(vector2, w));
}
#endregion
}
public static class CustomRpcSenderExtensions
{
public static void RpcSetName(ref CustomRpcSender sender, PlayerControl player, PlayerControl seer, string name)
{
bool seerIsNull = !seer;
int targetClientId = seerIsNull ? -1 : seer.OwnerId;
name = name.Replace("color=", string.Empty);
switch (seerIsNull)
{
case true when Main.LastNotifyNames.Where(x => x.Key.Item1 == player.PlayerId).All(x => x.Value == name):
case false when Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] == name:
return;
case true:
Main.EnumeratePlayerControls().Do(x => Main.LastNotifyNames[(player.PlayerId, x.PlayerId)] = name);
break;
default:
Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name;
break;
}
sender.checkLength = false;
if (sender.stream.Length + GetSetNameRpcSize(player.NetId, name) > 1180)
{
bool packed = sender.packed;
sender.SendMessage();
sender = CustomRpcSender.Create(sender.name, sender.sendOption);
if (packed) sender.StartPackedMessage();
}
sender.AutoStartRpc(player.NetId, RpcCalls.SetName, targetClientId)
.Write(player.Data.NetId)
.Write(name)
.Write(false)
.EndRpc();
return;
static int GetSetNameRpcSize(uint netId, string playerName)
{
int byteCount = Encoding.UTF8.GetByteCount(playerName);
return 3 + PackedUIntSize(netId) + 1 + 4 + PackedUIntSize((uint)byteCount) + byteCount + 1;
}
static int PackedUIntSize(uint value)
{
return value switch
{
< 0x80 => 1,
< 0x4000 => 2,
< 0x20_0000 => 3,
< 0x1000_0000 => 4,
_ => 5
};
}
}
public static bool Notify(ref CustomRpcSender sender, PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true, bool setName = true)
{
if (!AmongUsClient.Instance.AmHost || !pc) return false;
if (!GameStates.IsInTask) return false;
if (!text.Contains('#') && !text.Contains("<color=")) text = Utils.ColorString(Color.white, text);
if (!text.Contains("<size=")) text = $"<size=1.9>{text}</size>";
long expireTS = Utils.TimeStamp + (long)time;
bool alreadyContainsKey = false;
if (overrideAll || !NameNotifyManager.Notifies.TryGetValue(pc.PlayerId, out Dictionary<string, long> notifies))
NameNotifyManager.Notifies[pc.PlayerId] = new() { { text, expireTS } };
else
{
alreadyContainsKey = notifies.ContainsKey(text);
notifies[text] = expireTS;
}
bool returnValue = pc.IsNonHostModdedClient();
if (returnValue) NameNotifyManager.SendRPC(sender, pc, text, expireTS, overrideAll);
if (alreadyContainsKey)
{
if (log) Logger.Info($"Extended name notify for {pc.GetNameWithRole()}: {text} ({time}s)", "Name Notify");
return returnValue;
}
if (setName) returnValue |= Utils.WriteSetNameRpcsToSender(ref sender, false, false, false, false, false, false, pc, [pc], [], out bool senderWasCleared) && !senderWasCleared;
if (log) Logger.Info($"New name notify for {pc.GetNameWithRole()}: {text} ({time}s)", "Name Notify");
return returnValue;
}
extension(CustomRpcSender sender)
{
public bool RpcSetRole(PlayerControl player, RoleTypes role, int targetClientId = -1, bool noRpcForSelf = true, bool changeRoleMap = false)
{
if (AmongUsClient.Instance.ClientId == targetClientId && noRpcForSelf)
{
player.SetRole(role);
return false;
}
sender.AutoStartRpc(player.NetId, RpcCalls.SetRole, targetClientId)
.Write((ushort)role)
.Write(true)
.EndRpc();
if (changeRoleMap)
{
try
{
if (targetClientId != -1) ChangeRoleMapForClient(Utils.GetClientById(targetClientId).Character.PlayerId);
else Main.PlayerStates.Keys.Do(ChangeRoleMapForClient);
}
catch (Exception e) { Utils.ThrowException(e); }
void ChangeRoleMapForClient(byte id)
{
(byte, byte) key = (id, player.PlayerId);
if (StartGameHostPatch.RpcSetRoleReplacer.RoleMap.TryGetValue(key, out (RoleTypes RoleType, CustomRoles CustomRole) pair))
{
pair.RoleType = role;
StartGameHostPatch.RpcSetRoleReplacer.RoleMap[key] = pair;
}
}
}
return true;
}
public bool RpcGuardAndKill(PlayerControl killer, PlayerControl target = null, bool forObserver = false, bool fromSetKCD = false)
{
if (!AmongUsClient.Instance.AmHost)
{
StackFrame caller = new(1, false);
MethodBase callerMethod = caller.GetMethod();
string callerMethodName = callerMethod?.Name;
string callerClassName = callerMethod?.DeclaringType?.FullName;
Logger.Warn($"Modded non-host client activated RpcGuardAndKill from {callerClassName}.{callerMethodName}", "RpcGuardAndKill");
return false;
}
if (!target) target = killer;
var returnValue = false;
// Check Observer
if (!forObserver && !MeetingStates.FirstMeeting)
{
foreach (PlayerControl x in Main.EnumeratePlayerControls())
{
if (x.Is(CustomRoles.Observer) && killer.PlayerId != x.PlayerId && sender.RpcGuardAndKill(x, target, true))
returnValue = true;
}
}
// Host
if (killer.AmOwner) killer.MurderPlayer(target, MurderResultFlags.FailedProtected);
// Other Clients
else
{
sender.AutoStartRpc(killer.NetId, RpcCalls.MurderPlayer, killer.OwnerId);
sender.WriteNetObject(target);
sender.Write((int)MurderResultFlags.FailedProtected);
sender.EndRpc();
if (Options.CurrentGameMode == CustomGameMode.Standard && !MeetingStates.FirstMeeting && !AntiBlackout.SkipTasks && !ExileController.Instance && GameStates.IsInTask && killer.IsBeginner() && Main.GotShieldAnimationInfoThisGame.Add(killer.PlayerId))
Notify(ref sender, killer, Translator.GetString("PleaseStopBeingDumb"), 10f);
returnValue = true;
}
if (!fromSetKCD) killer.AddKillTimerToDict(true);
return returnValue;
}
public bool SetKillCooldown(PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false)
{
if (!player) return false;
if (!Mathf.Approximately(time, -1f) && Committed.ReduceKCD != null && Committed.ReduceKCD.TryGetValue(player.PlayerId, out float reduction))
time = Math.Max(time - reduction, 0.01f);
Logger.Info($"{player.GetNameWithRole()}'s KCD set to {(Math.Abs(time - -1f) < 0.5f ? Main.AllPlayerKillCooldown[player.PlayerId] : time)}s", "SetKCD");
if (player.GetCustomRole().UsesPetInsteadOfKill())
{
if (Math.Abs(time - -1f) < 0.5f)
player.AddKCDAsAbilityCD();
else
player.AddAbilityCD((int)Math.Round(time));
if (player.GetCustomRole() is not CustomRoles.Necromancer and not CustomRoles.Deathknight and not CustomRoles.Renegade and not CustomRoles.Sidekick) return false;
}
if (!player.CanUseKillButton() && !AntiBlackout.SkipTasks && !IntroCutsceneDestroyPatch.PreventKill) return false;
player.AddKillTimerToDict(cd: time);
if (!target) target = player;
if (time >= 0f)
Main.AllPlayerKillCooldown[player.PlayerId] = time * 2;
else
Main.AllPlayerKillCooldown[player.PlayerId] *= 2;
var returnValue = false;
if (player.Is(CustomRoles.Glitch) && Main.PlayerStates[player.PlayerId].Role is Glitch gc)
{
gc.LastKill = Utils.TimeStamp + ((int)(time / 2) - Glitch.KillCooldown.GetInt());
gc.KCDTimer = (int)(time / 2);
}
else if (forceAnime || !player.IsModdedClient() || !Options.DisableShieldAnimations.GetBool())
{
returnValue |= sender.SyncSettings(player);
returnValue |= sender.RpcGuardAndKill(player, target, fromSetKCD: true);
}
else
{
time = Main.AllPlayerKillCooldown[player.PlayerId] / 2;
if (player.AmOwner)
PlayerControl.LocalPlayer.SetKillTimer(time);
else
{
sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillTimer, player.OwnerId);
sender.Write(time);
sender.EndRpc();
returnValue = true;
}
foreach (PlayerControl x in Main.EnumeratePlayerControls())
{
if (x.Is(CustomRoles.Observer) && target.PlayerId != x.PlayerId && sender.RpcGuardAndKill(x, target, true, true))
returnValue = true;
}
}
if (player.GetCustomRole() is not CustomRoles.Inhibitor and not CustomRoles.Saboteur)
{
player.ResetKillCooldown(sync: false);
LateTask.New(player.SyncSettings, 1f, log: false);
}
return returnValue;
}
public bool RpcResetAbilityCooldown(PlayerControl target)
{
if (!AmongUsClient.Instance.AmHost) return false;
Logger.Info($"Reset Ability Cooldown for {target.name} (ID: {target.PlayerId})", "RpcResetAbilityCooldown");
if (target.Is(CustomRoles.Glitch) && Main.PlayerStates[target.PlayerId].Role is Glitch gc)
{
gc.LastHack = Utils.TimeStamp;
gc.HackCDTimer = 10;
return false;
}
if (target.AmOwner)
{
// If target is host
target.Data.Role.SetCooldown();
return false;
}
if (target.IsModdedClient())
{
sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetAbilityCooldown, target.OwnerId);
sender.EndRpc();
return true;
}
sender.AutoStartRpc(target.NetId, RpcCalls.ProtectPlayer, target.OwnerId);
sender.WriteNetObject(target);
sender.Write(0);
sender.EndRpc();
return true;
}
public void RpcDesyncUpdateSystem(PlayerControl target, SystemTypes systemType, int amount)
{
sender.AutoStartRpc(ShipStatus.Instance.NetId, RpcCalls.UpdateSystem, target.OwnerId);
sender.Write((byte)systemType);
sender.WriteNetObject(target);
sender.Write((byte)amount);
sender.EndRpc();
}
public bool TP(PlayerControl pc, Vector2 location, bool noCheckState = false, bool log = true)
{
if (!AmongUsClient.Instance.AmHost) return false;
CustomNetworkTransform nt = pc.NetTransform;
if (!noCheckState)
{
if (pc.Is(CustomRoles.AntiTP)) return false;
if (pc.inVent || pc.inMovingPlat || pc.onLadder || !pc.IsAlive() || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || pc.MyPhysics.Animations.IsPlayingEnterVentAnimation())
{
if (log) Logger.Warn($"Target ({pc.GetNameWithRole()}) is in an un-teleportable state - Teleporting canceled", "TP");
return false;
}
if (FastVector2.DistanceWithinRange(pc.Pos(), location, 0.5f))
{
if (log) Logger.Warn($"Target ({pc.GetNameWithRole()}) is too close to the destination - Teleporting canceled", "TP");
return false;
}
}
nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328));
nt.SetDirtyBit(uint.MaxValue);
var newSid = (ushort)(nt.lastSequenceId + 8);
sender.AutoStartRpc(nt.NetId, RpcCalls.SnapTo);
sender.WriteVector2(location);
sender.Write(newSid);
sender.EndRpc();
if (log) Logger.Info($"{pc.GetNameWithRole()} => {location}", "TP");
CheckInvalidMovementPatch.LastPosition[pc.PlayerId] = location;
CheckInvalidMovementPatch.ExemptedPlayers.Add(pc.PlayerId);
if (sender.sendOption == SendOption.Reliable) Utils.NumSnapToCallsThisRound++;
return true;
}
public bool NotifyRolesSpecific(PlayerControl seer, PlayerControl target, out CustomRpcSender newSender, out bool senderWasCleared)
{
senderWasCleared = false;
newSender = sender;
if (!AmongUsClient.Instance.AmHost || !seer || seer.Data.Disconnected || seer.IsModdedClient() && (seer.IsHost() || Options.CurrentGameMode == CustomGameMode.Standard) || !SetUpRoleTextPatch.IsInIntro && GameStates.IsLobby) return false;
var hasValue = Utils.WriteSetNameRpcsToSender(ref sender, false, false, false, false, false, false, seer, [seer], [target], out senderWasCleared) && !senderWasCleared;
newSender = sender;
return hasValue;
}
public bool RpcExiled(PlayerControl target, bool autoStartRpc = true, int targetClientId = -1, bool exileForHost = true)
{
if (!AmongUsClient.Instance.AmHost) return false;
if (exileForHost) target.Exiled();
if (autoStartRpc) sender.AutoStartRpc(target.NetId, RpcCalls.Exiled, targetClientId).EndRpc();
else sender.StartRpc(target.NetId, RpcCalls.Exiled).EndRpc();
return true;
}
public bool RpcExileV2(PlayerControl target, bool autoStartRpc = true, int targetClientId = -1, bool exileForHost = true)
{
if (!AmongUsClient.Instance.AmHost) return false;
sender.RpcExiled(target, autoStartRpc, targetClientId, exileForHost);
FixedUpdatePatch.LoversSuicide(target.PlayerId);
return true;
}
public bool SyncSettings(PlayerControl player)
{
player.SyncSettings();
return false;
}
public bool SyncGeneralOptions(PlayerControl player)
{
if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || !Utils.DoRPC) return false;
sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions);
sender.Write(player.PlayerId);
sender.WritePacked((int)player.GetCustomRole());
sender.Write(Main.PlayerStates[player.PlayerId].IsDead);
sender.WritePacked((int)Main.PlayerStates[player.PlayerId].deathReason);
sender.Write(Main.AllPlayerKillCooldown[player.PlayerId]);
sender.Write(Main.AllPlayerSpeed[player.PlayerId]);
sender.EndRpc();
return true;
}
public bool RpcExitVentDesync(PlayerPhysics physics, int ventId, PlayerControl seer)
{
if (!physics) return false;
int clientId = seer.OwnerId;
if (AmongUsClient.Instance.ClientId == clientId)
{
physics.StopAllCoroutines();
physics.StartCoroutine(physics.CoExitVent(ventId));
return false;
}
if (clientId < 0) return false;
sender.AutoStartRpc(physics.NetId, RpcCalls.ExitVent, clientId);
sender.WritePacked(ventId);
sender.EndRpc();
return true;
}
}
}
// Rather not send the packet than get kicked immediately after sending it
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendOrDisconnect))]
static class PreventLargePacketKickPatch
{
public static bool Prefix([HarmonyArgument(0)] MessageWriter msg)
{
if (msg.Length <= 1200) return true;
if (AmongUsClient.Instance.AmHost && GameStates.CurrentServerType is GameStates.ServerType.Niko or GameStates.ServerType.Modded) return true;
Logger.Warn($" Blocked large packet from sending (size: {msg.Length})", nameof(PreventLargePacketKickPatch));
return false;
}
}