mirror of
https://github.com/Gurge44/EndlessHostRoles.git
synced 2026-10-20 01:46:25 +00:00
980 lines
34 KiB
C#
980 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);
|
|
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;
|
|
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;
|
|
|
|
if (GameStates.CurrentServerType is GameStates.ServerType.Local or GameStates.ServerType.Vanilla)
|
|
{
|
|
Logger.Warn($" Blocked large packet from sending (size: {msg.Length})", nameof(PreventLargePacketKickPatch));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} |