Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 214 additions & 72 deletions EXILED/Exiled.API/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,46 @@ namespace Exiled.API.Extensions
using System.Text;

using AdminToys;

using AudioPooling;

using Cassie;

using CustomPlayerEffects;

using Exiled.API.Enums;
using Exiled.API.Features.Items;
using Exiled.API.Features.Items.Keycards;
using Exiled.API.Features.Pickups.Keycards;

using Features;
using Features.Pools;

using InventorySystem;
using InventorySystem.Items;
using InventorySystem.Items.Autosync;
using InventorySystem.Items.Firearms;
using InventorySystem.Items.Firearms.Modules;
using InventorySystem.Items.Keycards;

using MEC;

using Mirror;

using PlayerRoles;
using PlayerRoles.Blood;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps.Scp049.Zombies;
using PlayerRoles.PlayableScps.Scp1507;
using PlayerRoles.Spectating;
using PlayerRoles.Voice;

using RelativePositioning;

using Respawning;

using UnityEngine;

using Utils.Networking;

/// <summary>
Expand Down Expand Up @@ -148,9 +161,9 @@ public static ReadOnlyDictionary<string, string> RpcFullNames
}

/// <summary>
/// Gets a <see cref="NetworkBehaviour.SetSyncVarDirtyBit(ulong)"/>'s <see cref="MethodInfo"/>.
/// Gets a NetworkIdentity.SerializeServer's <see cref="MethodInfo"/>.
/// </summary>
public static MethodInfo SetDirtyBitsMethodInfo => field ??= typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.SetSyncVarDirtyBit));
public static MethodInfo SerializeServerMethodInfo => field ??= typeof(NetworkIdentity).GetMethod("SerializeServer", BindingFlags.NonPublic | BindingFlags.Instance);

/// <summary>
/// Gets a NetworkServer.SendSpawnMessage's <see cref="MethodInfo"/>.
Expand Down Expand Up @@ -400,7 +413,7 @@ public static void ChangeAppearance(this Player player, RoleTypeId type, IEnumer
if (target != player || !isRisky)
target.Connection.Send(writer.ToArraySegment());
else
Log.Error($"Prevent Seld-Desync of {player.Nickname} with {type}");
Log.Error($"Prevent Self-Desync of {player.Nickname} with {type}");
}

NetworkWriterPool.Return(writer);
Expand Down Expand Up @@ -538,27 +551,6 @@ public static void MessageTranslated(this Player player, string words, string tr
announcement.Payload.SendToHubsConditionally(hub => hub == player.ReferenceHub);
}

/// <summary>
/// Moves object for the player.
/// </summary>
/// <param name="player">Target to send.</param>
/// <param name="identity">The <see cref="Mirror.NetworkIdentity"/> to move.</param>
/// <param name="pos">The position to change.</param>
public static void MoveNetworkIdentityObject(this Player player, NetworkIdentity identity, Vector3 pos)
{
if (identity == null)
return;

identity.gameObject.transform.position = pos;
ObjectDestroyMessage objectDestroyMessage = new()
{
netId = identity.netId,
};

player.Connection.Send(objectDestroyMessage, 0);
SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection });
}

/// <summary>
/// Sends to the player a Fake Change Scene.
/// </summary>
Expand Down Expand Up @@ -588,6 +580,58 @@ public static void ChangeSceneToAllClients(ScenesType scene)
NetworkServer.SendToAll(message);
}

/// <summary>
/// Sends a spawn message for the specified <see cref="NetworkIdentity"/> to the given <see cref="Player"/>.
/// </summary>
/// <param name="player">The player who should receive the spawn message.</param>
/// <param name="identity">The <see cref="NetworkIdentity"/> to spawn.</param>
public static void SpawnNetworkIdentity(this Player player, NetworkIdentity identity) => SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection });

/// <summary>
/// Sends a destroy message for the specified <see cref="NetworkIdentity"/> to the given <see cref="Player"/>.
/// </summary>
/// <param name="player">The player who should receive the destroy message.</param>
/// <param name="identity">The <see cref="NetworkIdentity"/> to destroy.</param>
public static void DestroyNetworkIdentity(this Player player, NetworkIdentity identity) => player.DestroyNetworkId(identity.netId);

/// <summary>
/// Sends a destroy message for the specified network ID to the given <see cref="Player"/>.
/// </summary>
/// <param name="player">The player who should receive the destroy message.</param>
/// <param name="netId">The network ID of the object to destroy.</param>
public static void DestroyNetworkId(this Player player, uint netId) => player.Connection.Send(new ObjectDestroyMessage() { netId = netId });

/// <summary>
/// Respawns the specified <see cref="NetworkIdentity"/> for the given <see cref="Player"/>.
/// This sends a destroy message followed by a spawn message to the player's client.
/// </summary>
/// <param name="player">The player who should receive the respawn messages.</param>
/// <param name="identity">The <see cref="NetworkIdentity"/> to respawn.</param>
public static void RespawnNetworkIdentity(this Player player, NetworkIdentity identity)
{
player.DestroyNetworkIdentity(identity);
player.SpawnNetworkIdentity(identity);
}

/// <summary>
/// Moves object for the player.
/// </summary>
/// <param name="player">Target to send.</param>
/// <param name="identity">The <see cref="Mirror.NetworkIdentity"/> to move.</param>
/// <param name="pos">The position to change.</param>
public static void MoveNetworkIdentityObject(this Player player, NetworkIdentity identity, Vector3 pos)
{
if (identity == null)
return;

Vector3 originalPosition = identity.transform.position;
identity.transform.position = pos;

player.RespawnNetworkIdentity(identity);

identity.transform.position = originalPosition;
}

/// <summary>
/// Scales an object for the specified player.
/// </summary>
Expand All @@ -599,14 +643,135 @@ public static void ScaleNetworkIdentityObject(this Player player, NetworkIdentit
if (identity == null)
return;

identity.gameObject.transform.localScale = scale;
ObjectDestroyMessage objectDestroyMessage = new()
Vector3 originalScale = identity.transform.localScale;
identity.transform.localScale = scale;

player.RespawnNetworkIdentity(identity);

identity.transform.localScale = originalScale;
}

/// <summary>
/// Edit <see cref="NetworkIdentity"/>'s parameter and sync.
/// </summary>
/// <param name="player">Target to send.</param>
/// <param name="identity">Target object.</param>
/// <param name="customAction">Edit function.</param>
/// <param name="resetAction">Reback function for reset object to original state.</param>
public static void EditNetworkObject(this Player player, NetworkIdentity identity, Action<NetworkIdentity> customAction, Action<NetworkIdentity> resetAction)
{
if (identity == null)
return;

customAction?.Invoke(identity);

player.RespawnNetworkIdentity(identity);

resetAction?.Invoke(identity);
}

/// <summary>
/// Sends a spawn message for the specified <see cref="NetworkIdentity"/> to a targeted collection of players.
/// </summary>
/// <param name="identity">The <see cref="NetworkIdentity"/> to serialize and spawn.</param>
/// <param name="players">The collection of <see cref="Player"/> who will receive the spawn message.</param>
public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEnumerable<Player> players)
{
if (identity == null || identity.netId == 0 || !players.Any())
return;

using NetworkWriterPooled ownerWriter = NetworkWriterPool.Get();
using NetworkWriterPooled observersWriter = NetworkWriterPool.Get();

SerializeServerMethodInfo?.Invoke(identity, new object[] { true, ownerWriter, observersWriter });

ArraySegment<byte> ownerPayload = ownerWriter.ToArraySegment();
ArraySegment<byte> observerPayload = observersWriter.ToArraySegment();

SpawnMessage spawnMessage = new()
{
netId = identity.netId,
isLocalPlayer = false,
isOwner = false,
sceneId = identity.sceneId,
assetId = identity.assetId,
position = identity.transform.localPosition,
rotation = identity.transform.localRotation,
scale = identity.transform.localScale,
payload = observerPayload,
};

player.Connection.Send(objectDestroyMessage, 0);
SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection });
using NetworkWriterPooled prepackedObserverWriter = NetworkWriterPool.Get();
NetworkMessages.Pack(spawnMessage, prepackedObserverWriter);
ArraySegment<byte> segment = prepackedObserverWriter.ToArraySegment();

foreach (Player player in players)
{
if (!player.IsConnected)
continue;

bool isOwner = identity.connectionToClient == player.Connection;

if (!isOwner)
{
player.Connection.Send(segment);
continue;
}

SpawnMessage ownerMessage = spawnMessage;
ownerMessage.isOwner = true;
ownerMessage.isLocalPlayer = player.NetworkIdentity == identity;
ownerMessage.payload = ownerPayload;

player.Connection.Send(ownerMessage);
}
}

/// <summary>
/// Sends a destroy message for the specified <see cref="NetworkIdentity"/> to a targeted collection of players.
/// </summary>
/// <param name="identity">The <see cref="NetworkIdentity"/> to destroy on the clients.</param>
/// <param name="players">The collection of <see cref="Player"/> who will receive the destroy message.</param>
public static void DestroyNetworkIdentityForPlayers(this NetworkIdentity identity, IEnumerable<Player> players)
{
if (identity == null || identity.netId == 0 || !players.Any())
return;

using NetworkWriterPooled writer = NetworkWriterPool.Get();
NetworkMessages.Pack(new ObjectDestroyMessage() { netId = identity.netId }, writer);
ArraySegment<byte> segment = writer.ToArraySegment();

foreach (Player player in players)
{
if (!player.IsConnected)
continue;

player.Connection.Send(segment);
}
}

/// <summary>
/// Respawns the specified <see cref="NetworkIdentity"/> by respawning its underlying <see cref="GameObject"/> on the server.
/// </summary>
/// <param name="identity">The <see cref="NetworkIdentity"/> to respawn.</param>
public static void RespawnNetworkIdentity(this NetworkIdentity identity)
{
if (identity == null || identity.netId == 0)
return;

NetworkServer.SendToReady(new ObjectDestroyMessage() { netId = identity.netId });
identity.SendSpawnMessageForPlayers(Player.List);
}

/// <summary>
/// Respawns a networked <see cref="GameObject"/> on the server by unspawning and respawning it.
/// This forces Mirror to reinitialize the network state for the object.
/// </summary>
/// <param name="gameObject">The networked GameObject to respawn.</param>
public static void RespawnNetworkObject(this GameObject gameObject)
{
NetworkServer.UnSpawn(gameObject);
NetworkServer.Spawn(gameObject);
}

/// <summary>
Expand All @@ -619,17 +784,8 @@ public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vect
if (identity == null)
return;

identity.gameObject.transform.position = pos;
ObjectDestroyMessage objectDestroyMessage = new()
{
netId = identity.netId,
};

foreach (Player ply in Player.List)
{
ply.Connection.Send(objectDestroyMessage, 0);
SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, ply.Connection });
}
identity.transform.position = pos;
RespawnNetworkIdentity(identity);
}

/// <summary>
Expand All @@ -642,17 +798,19 @@ public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vec
if (identity == null)
return;

identity.gameObject.transform.localScale = scale;
ObjectDestroyMessage objectDestroyMessage = new()
{
netId = identity.netId,
};
identity.transform.localScale = scale;
RespawnNetworkIdentity(identity);
}

foreach (Player ply in Player.List)
{
ply.Connection.Send(objectDestroyMessage, 0);
SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, ply.Connection });
}
/// <summary>
/// Edit <see cref="NetworkIdentity"/>'s parameter and sync.
/// </summary>
/// <param name="identity">The <see cref="NetworkIdentity"/> to edit.</param>
/// <param name="customAction">Edit function.</param>
public static void EditNetworkObject(this NetworkIdentity identity, Action<NetworkIdentity> customAction)
{
customAction.Invoke(identity);
RespawnNetworkIdentity(identity);
}

/// <summary>
Expand Down Expand Up @@ -714,7 +872,12 @@ public static void ResyncSyncVar(NetworkIdentity behaviorOwner, Type targetType,
{
if (behaviorOwner == null)
return;
SetDirtyBitsMethodInfo.Invoke(behaviorOwner.gameObject.GetComponent(targetType), new object[] { SyncVarDirtyBits[$"{targetType.Name}.{propertyName}"] });

if (behaviorOwner.gameObject.GetComponent(targetType) is not NetworkBehaviour behaviour)
return;

ulong dirtyBit = SyncVarDirtyBits[$"{targetType.Name}.{propertyName}"];
behaviour.SetSyncVarDirtyBit(dirtyBit);
}

/// <summary>
Expand Down Expand Up @@ -774,32 +937,11 @@ public static void SendFakeSyncObject(Player target, NetworkIdentity behaviorOwn
NetworkWriterPooled writer = NetworkWriterPool.Get();
NetworkWriterPooled writer2 = NetworkWriterPool.Get();
MakeCustomSyncWriter(behaviorOwner, targetType, customAction, null, writer, writer2);
target.ReferenceHub.networkIdentity.connectionToClient.Send(new EntityStateMessage() { netId = behaviorOwner.netId, payload = writer.ToArraySegment() });
target.Connection.Send(new EntityStateMessage() { netId = behaviorOwner.netId, payload = writer.ToArraySegment() });
NetworkWriterPool.Return(writer);
NetworkWriterPool.Return(writer2);
}

/// <summary>
/// Edit <see cref="NetworkIdentity"/>'s parameter and sync.
/// </summary>
/// <param name="identity">Target object.</param>
/// <param name="customAction">Edit function.</param>
public static void EditNetworkObject(NetworkIdentity identity, Action<NetworkIdentity> customAction)
{
customAction.Invoke(identity);

ObjectDestroyMessage objectDestroyMessage = new()
{
netId = identity.netId,
};

foreach (Player player in Player.List)
{
player.Connection.Send(objectDestroyMessage, 0);
SendSpawnMessageMethodInfo.Invoke(null, new object[] { identity, player.Connection });
}
}

// Get components index in identity.(private)
private static int GetComponentIndex(NetworkIdentity identity, Type type)
{
Expand Down
Loading