Files
EndlessHostRoles/Modules/CustomRpcSender.cs
T

983 lines
34 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using AmongUs.GameOptions;
using EHR.Modules;
using EHR.Roles;
using HarmonyLib;
using Hazel;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
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
private 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)
{
static int GetPackedIntSize(int value)
{
uint v = (uint)value;
int count = 0;
do
{
v >>= 7;
count++;
}
while (v != 0);
return count;
}
if (3 + GetPackedIntSize(AmongUsClient.Instance.GameId) >= stream.Length)
dispose = true;
else
EndMessage();
}
if (currentState != State.Ready)
{
var errorMsg = $"Tried to send RPC 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 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 RPC, 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 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 exit 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())
{
EndMessage(startNew: true);
StartMessage(currentRpcTarget);
}
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 terminate 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(Il2CppStructArray<byte> bytes)
{
return Write(w => w.Write(bytes));
}
public CustomRpcSender Write(Il2CppStructArray<byte> bytes, int offset, int length)
{
return Write(w => w.Write(bytes, offset, length));
}
public CustomRpcSender WriteBytesAndSize(Il2CppStructArray<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) > 1100)
{
bool packed = sender.packed;
sender.SendMessage();
sender = CustomRpcSender.Create(sender.name, sender.sendOption);
if (packed) sender.StartPackedMessage();
}
sender.AutoStartRpc(player.NetId, RpcCalls.SetName, targetClientId)
.Write(player.Data.NetId)
.Write(name)
.Write(false)
.EndRpc();
return;
static int GetSetNameRpcSize(uint netId, string playerName)
{
int byteCount = System.Text.Encoding.UTF8.GetByteCount(playerName);
return 3 + PackedUIntSize(netId) + 1 + 4 + PackedUIntSize((uint)byteCount) + byteCount + 1;
}
static int PackedUIntSize(uint value)
{
return value switch
{
< 0x80 => 1,
< 0x4000 => 2,
< 0x20_0000 => 3,
< 0x1000_0000 => 4,
_ => 5
};
}
}
extension(CustomRpcSender sender)
{
public bool RpcSetRole(PlayerControl player, RoleTypes role, int targetClientId = -1, bool noRpcForSelf = true, bool changeRoleMap = false)
{
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))
sender.Notify(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;
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 Notify(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("<color=") && !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.PlayerId, text, expireTS, overrideAll);
if (alreadyContainsKey)
{
if (log) Logger.Info($"Extended name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {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().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify");
return returnValue;
}
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().RemoveHtmlTags()}) 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().RemoveHtmlTags()}) 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().RemoveHtmlTags()} => {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 (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)
{
sender.RpcExiled(target, autoStartRpc, targetClientId, exileForHost);
FixedUpdatePatch.LoversSuicide(target.PlayerId);
return true;
}
public bool SyncSettings(PlayerControl player)
{
if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla)
{
player.SyncSettings();
return false;
}
var optionsender = GameOptionsSender.AllSenders.OfType<PlayerGameOptionsSender>().FirstOrDefault(x => x.player.PlayerId == player.PlayerId);
if (optionsender == null) return false;
var options = optionsender.BuildGameOptions();
if (player.AmOwner)
{
foreach (GameLogicComponent com in GameManager.Instance.LogicComponents)
{
if (com.TryCast(out LogicOptions lo))
lo.SetGameOptions(options);
}
GameOptionsManager.Instance.CurrentGameOptions = options;
return false;
}
var logicOptions = GameManager.Instance.LogicOptions;
var id = GameManager.Instance.LogicComponents.IndexOf(logicOptions);
if (sender.CurrentState == CustomRpcSender.State.InRootMessage) sender.EndMessage();
var writer = sender.stream;
writer.StartMessage(6);
{
writer.Write(AmongUsClient.Instance.GameId);
writer.WritePacked(player.OwnerId);
writer.StartMessage(1);
{
writer.WritePacked(GameManager.Instance.NetId);
writer.StartMessage((byte)id);
{
writer.WriteBytesAndSize(logicOptions.gameOptionsFactory.ToBytes(options, AprilFoolsMode.IsAprilFoolsModeToggledOn));
}
writer.EndMessage();
}
writer.EndMessage();
}
writer.EndMessage();
return true;
}
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;
}
}
}
// 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;
var serverType = GameStates.CurrentServerType;
if (serverType is GameStates.ServerType.Local or GameStates.ServerType.Vanilla || (serverType == GameStates.ServerType.Niko && msg.Length > 1400))
{
Logger.Warn($" Blocked large packet from sending (size: {msg.Length})", nameof(PreventLargePacketKickPatch));
return false;
}
return true;
}
}