From c55c669c2a3df7194dc59db107d9c56ba57930ac Mon Sep 17 00:00:00 2001 From: michcio Date: Sun, 10 May 2026 01:44:52 +0200 Subject: [PATCH 1/2] still a wip --- .../Exiled.API/Extensions/MirrorExtensions.cs | 231 ++++++++++++++---- 1 file changed, 184 insertions(+), 47 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index ca7742fda..4c30e6b34 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -13,35 +13,22 @@ namespace Exiled.API.Extensions using System.Linq; using System.Reflection; using System.Reflection.Emit; - 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; @@ -49,13 +36,8 @@ namespace Exiled.API.Extensions using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.Spectating; using PlayerRoles.Voice; - using RelativePositioning; - - using Respawning; - using UnityEngine; - using Utils.Networking; /// @@ -107,8 +89,8 @@ public static ReadOnlyDictionary SyncVarDirtyBits if (SyncVarDirtyBitsValue.Count == 0) { foreach (PropertyInfo property in typeof(ServerConsole).Assembly.GetTypes() - .SelectMany(x => x.GetProperties()) - .Where(m => m.Name.StartsWith("Network"))) + .SelectMany(x => x.GetProperties()) + .Where(m => m.Name.StartsWith("Network"))) { MethodInfo setMethod = property.GetSetMethod(); @@ -141,8 +123,8 @@ public static ReadOnlyDictionary RpcFullNames if (RpcFullNamesValue.Count == 0) { foreach (MethodInfo method in typeof(ServerConsole).Assembly.GetTypes() - .SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) - .Where(m => m.GetCustomAttributes(typeof(ClientRpcAttribute), false).Length > 0 || m.GetCustomAttributes(typeof(TargetRpcAttribute), false).Length > 0)) + .SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + .Where(m => m.GetCustomAttributes(typeof(ClientRpcAttribute), false).Length > 0 || m.GetCustomAttributes(typeof(TargetRpcAttribute), false).Length > 0)) { MethodBody methodBody = method.GetMethodBody(); @@ -306,7 +288,14 @@ public static void PlaceBlood(this Player player, Vector3 position, Vector3 orig /// /// Only this player can see Display Text. /// Text displayed to the player. - public static void SetIntercomDisplayTextForTargetOnly(this Player target, string text) => target.SendFakeSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText), text); + public static void SetIntercomDisplayTextForTargetOnly(this Player target, string text) => target.Connection.SetIntercomDisplayTextForTargetOnly(text); + + /// + /// Sets that only the connection can see. + /// + /// Only this connection can see Display Text. + /// Text displayed to the player. + public static void SetIntercomDisplayTextForTargetOnly(this NetworkConnection connection, string text) => connection.SendFakeSyncVar(IntercomDisplay._singleton.netIdentity, typeof(IntercomDisplay), nameof(IntercomDisplay.Network_overrideText), text); /// /// Resync . @@ -321,6 +310,30 @@ public static void PlaceBlood(this Player player, Vector3 position, Vector3 orig /// Color to set. public static void SetRoomColorForTargetOnly(this Room room, Player target, Color color) => target.SendFakeSyncVar(room.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkOverrideColor), color); + /// + /// Sets of a that only the can see. + /// + /// Room to modify. + /// Only this connection can see room color. + /// Color to set. + public static void SetRoomColorForTargetOnly(this Room room, NetworkConnection connection, Color color) => connection.SendFakeSyncVar(room.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkOverrideColor), color); + + /// + /// Sets of a that only the can see. + /// + /// Only this player can see room color. + /// Room to modify. + /// Color to set. + public static void SetRoomColorForTargetOnly(this Player player, Room targetRoom, Color color) => player.Connection.SetRoomColorForTargetOnly(targetRoom, color); + + /// + /// Sets of a that only the can see. + /// + /// Only this connection can see room color. + /// Room to modify. + /// Color to set. + public static void SetRoomColorForTargetOnly(this NetworkConnection connection, Room targetRoom, Color color) => connection.SendFakeSyncVar(targetRoom.RoomLightControllerNetIdentity, typeof(RoomLightController), nameof(RoomLightController.NetworkOverrideColor), color); + /// /// Sets the lights of a to be either on or off, visible only to the player. /// @@ -335,10 +348,7 @@ public static void PlaceBlood(this Player player, Vector3 position, Vector3 orig /// Only this player can see the name changed. /// Player that will desync the CustomName. /// Nickname to set. - public static void SetName(this Player target, Player player, string name) - { - target.SendFakeSyncVar(player.NetworkIdentity, typeof(NicknameSync), nameof(NicknameSync.Network_displayName), name); - } + public static void SetName(this Player target, Player player, string name) => target.SendFakeSyncVar(player.NetworkIdentity, typeof(NicknameSync), nameof(NicknameSync.Network_displayName), name); /// /// Change character model for appearance. @@ -556,14 +566,21 @@ public static void MessageTranslated(this Player player, string words, string tr /// /// The player to send the Scene. /// The new Scene the client will load. - public static void SendFakeSceneLoading(this Player player, ScenesType newSceneName) + public static void SendFakeSceneLoading(this Player player, ScenesType newSceneName) => player.Connection.SendFakeSceneLoading(newSceneName); + + /// + /// Sends to the player a Fake Change Scene. + /// + /// The to send the Scene. + /// The new Scene the client will load. + public static void SendFakeSceneLoading(this NetworkConnection connection, ScenesType newSceneName) { SceneMessage message = new() { sceneName = newSceneName.ToString(), }; - player.Connection.Send(message); + connection.Send(message); } /// @@ -587,6 +604,13 @@ public static void ChangeSceneToAllClients(ScenesType scene) /// The to spawn. public static void SpawnNetworkIdentity(this Player player, NetworkIdentity identity) => SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection }); + /// + /// Sends a spawn message for the specified to the given . + /// + /// The which should receive the spawn message. + /// The to spawn. + public static void SpawnNetworkIdentity(this NetworkConnection connection, NetworkIdentity identity) => SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, connection }); + /// /// Sends a destroy message for the specified to the given . /// @@ -594,18 +618,32 @@ public static void ChangeSceneToAllClients(ScenesType scene) /// The to destroy. public static void DestroyNetworkIdentity(this Player player, NetworkIdentity identity) => player.DestroyNetworkId(identity.netId); + /// + /// Sends a destroy message for the specified to the given . + /// + /// The which should receive the destroy message. + /// The to destroy. + public static void DestroyNetworkIdentity(this NetworkConnection connection, NetworkIdentity identity) => connection.DestroyNetworkId(identity.netId); + /// /// Sends a destroy message for the specified network ID to the given . /// /// The player who should receive the destroy message. /// The network ID of the object to destroy. - public static void DestroyNetworkId(this Player player, uint netId) => player.Connection.Send(new ObjectDestroyMessage() { netId = netId }); + public static void DestroyNetworkId(this Player player, uint netId) => player.Connection.DestroyNetworkId(netId); + + /// + /// Sends a destroy message for the specified network ID to the given . + /// + /// The which should receive the destroy message. + /// The network ID of the object to destroy. + public static void DestroyNetworkId(this NetworkConnection connection, uint netId) => connection.Send(new ObjectDestroyMessage() { netId = netId }); /// /// Respawns the specified for the given . /// This sends a destroy message followed by a spawn message to the player's client. /// - /// The player who should receive the respawn messages. + /// The who should receive the respawn messages. /// The to respawn. public static void RespawnNetworkIdentity(this Player player, NetworkIdentity identity) { @@ -613,6 +651,18 @@ public static void RespawnNetworkIdentity(this Player player, NetworkIdentity id player.SpawnNetworkIdentity(identity); } + /// + /// Respawns the specified for the given . + /// This sends a destroy message followed by a spawn message to connection. + /// + /// The who should receive the respawn messages. + /// The to respawn. + public static void RespawnNetworkIdentity(this NetworkConnection connection, NetworkIdentity identity) + { + connection.DestroyNetworkIdentity(identity); + connection.SpawnNetworkIdentity(identity); + } + /// /// Moves object for the player. /// @@ -670,14 +720,42 @@ public static void EditNetworkObject(this Player player, NetworkIdentity identit resetAction?.Invoke(identity); } + /// + /// Edit 's parameter and sync. + /// + /// Target to send. + /// Target object. + /// Edit function. + /// Reback function for reset object to original state. + public static void EditNetworkObject(this NetworkConnection connection, NetworkIdentity identity, Action customAction, Action resetAction) + { + if (identity == null) + return; + + customAction?.Invoke(identity); + + connection.RespawnNetworkIdentity(identity); + + resetAction?.Invoke(identity); + } + /// /// Sends a spawn message for the specified to a targeted collection of players. /// /// The to serialize and spawn. /// The collection of who will receive the spawn message. - public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEnumerable players) + public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEnumerable players) => identity.SendSpawnMessageForConnections(players.Where(p => p.IsConnected).Select(p => p.Connection)); + + /// + /// Sends a spawn message for the specified to a targeted collection of s."/>. + /// + /// The to serialize and spawn. + /// The collection of who will receive the spawn message. + public static void SendSpawnMessageForConnections(this NetworkIdentity identity, IEnumerable connections) { - if (identity == null || identity.netId == 0 || !players.Any()) + IEnumerable networkConnections = connections as NetworkConnection[] ?? connections.ToArray(); + + if (identity == null || identity.netId == 0 || !networkConnections.Any()) return; using NetworkWriterPooled ownerWriter = NetworkWriterPool.Get(); @@ -705,25 +783,25 @@ public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEn NetworkMessages.Pack(spawnMessage, prepackedObserverWriter); ArraySegment segment = prepackedObserverWriter.ToArraySegment(); - foreach (Player player in players) + foreach (NetworkConnection connection in networkConnections) { - if (!player.IsConnected) + if (connection == null) continue; - bool isOwner = identity.connectionToClient == player.Connection; + bool isOwner = identity.connectionToClient == connection; if (!isOwner) { - player.Connection.Send(segment); + connection.Send(segment); continue; } SpawnMessage ownerMessage = spawnMessage; ownerMessage.isOwner = true; - ownerMessage.isLocalPlayer = player.NetworkIdentity == identity; + ownerMessage.isLocalPlayer = connection.identity == identity; ownerMessage.payload = ownerPayload; - player.Connection.Send(ownerMessage); + connection.Send(ownerMessage); } } @@ -824,13 +902,29 @@ public static void EditNetworkObject(this NetworkIdentity identity, ActionValue of send to target. public static void SendFakeSyncVar(this Player target, NetworkIdentity behaviorOwner, Type targetType, string propertyName, T value) { - if (!target.IsConnected || behaviorOwner == null) + if (!target.IsConnected) + return; + target.Connection.SendFakeSyncVar(behaviorOwner, targetType, propertyName, value); + } + + /// + /// Send fake values to client's . + /// + /// Target SyncVar property type. + /// to send. + /// of object that owns . + /// 's type. + /// Property name starting with Network. + /// Value of send to connection. + public static void SendFakeSyncVar(this NetworkConnection connection, NetworkIdentity behaviorOwner, Type targetType, string propertyName, T value) + { + if (connection == null || behaviorOwner == null) return; NetworkWriterPooled writer = NetworkWriterPool.Get(); NetworkWriterPooled writer2 = NetworkWriterPool.Get(); MakeCustomSyncWriter(behaviorOwner, targetType, null, CustomSyncVarGenerator, writer, writer2); - target.Connection.Send(new EntityStateMessage + connection.Send(new EntityStateMessage { netId = behaviorOwner.netId, payload = writer.ToArraySegment(), @@ -838,6 +932,7 @@ public static void SendFakeSyncVar(this Player target, NetworkIdentity behavi NetworkWriterPool.Return(writer); NetworkWriterPool.Return(writer2); + void CustomSyncVarGenerator(NetworkWriter targetWriter) { bool isAdminToy = targetType.BaseType == typeof(AdminToyBase); @@ -888,9 +983,25 @@ public static void ResyncSyncVar(NetworkIdentity behaviorOwner, Type targetType, /// 's type. /// Property name starting with Rpc. /// Values of send to target. - public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwner, Type targetType, string rpcName, params object[] values) + public static void SendFakeTargetRpc(this Player target, NetworkIdentity behaviorOwner, Type targetType, string rpcName, params object[] values) { - if (!target.IsConnected || behaviorOwner == null) + if (!target.IsConnected) + return; + + target.Connection.SendFakeTargetRpc(behaviorOwner, targetType, rpcName, values); + } + + /// + /// Send fake values to client's . + /// + /// Connection to send. + /// of object that owns . + /// 's type. + /// Property name starting with Rpc. + /// Values of send to connection. + public static void SendFakeTargetRpc(this NetworkConnection connection, NetworkIdentity behaviorOwner, Type targetType, string rpcName, params object[] values) + { + if (connection == null || behaviorOwner == null) return; NetworkWriterPooled writer = NetworkWriterPool.Get(); @@ -906,7 +1017,7 @@ public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwne payload = writer.ToArraySegment(), }; - target.Connection.Send(msg); + connection.Send(msg); NetworkWriterPool.Return(writer); } @@ -929,15 +1040,41 @@ public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwne /// }); /// /// - public static void SendFakeSyncObject(Player target, NetworkIdentity behaviorOwner, Type targetType, Action customAction) + public static void SendFakeSyncObject(this Player target, NetworkIdentity behaviorOwner, Type targetType, Action customAction) { if (!target.IsConnected) return; + target.Connection.SendFakeSyncObject(behaviorOwner, targetType, customAction); + } + + /// + /// Send fake values to client's . + /// + /// Target to send. + /// of object that owns . + /// 's type. + /// Custom writing action. + /// + /// EffectOnlySCP207. + /// + /// MirrorExtensions.SendFakeSyncObject(player, player.NetworkIdentity, typeof(PlayerEffectsController), (writer) => { + /// writer.WriteULong(1ul); // DirtyObjectsBit + /// writer.WriteUInt(1); // DirtyIndexCount + /// writer.WriteByte((byte)SyncList<byte>.Operation.OP_SET); // Operations + /// writer.WriteUInt(17); // EditIndex + /// }); + /// + /// + public static void SendFakeSyncObject(this NetworkConnection connection, NetworkIdentity behaviorOwner, Type targetType, Action customAction) + { + if (connection == null || behaviorOwner == null) + return; + NetworkWriterPooled writer = NetworkWriterPool.Get(); NetworkWriterPooled writer2 = NetworkWriterPool.Get(); MakeCustomSyncWriter(behaviorOwner, targetType, customAction, null, writer, writer2); - target.Connection.Send(new EntityStateMessage() { netId = behaviorOwner.netId, payload = writer.ToArraySegment() }); + connection.Send(new EntityStateMessage() { netId = behaviorOwner.netId, payload = writer.ToArraySegment() }); NetworkWriterPool.Return(writer); NetworkWriterPool.Return(writer2); } @@ -996,4 +1133,4 @@ private static void MakeCustomSyncWriter(NetworkIdentity behaviorOwner, Type tar observer.WriteBytes(owner.ToArraySegment().Array, position, owner.Position - position); } } -} +} \ No newline at end of file From 1328367f4e88d89252e8bad6159d00a0a69a9270 Mon Sep 17 00:00:00 2001 From: michcio Date: Sun, 10 May 2026 22:58:33 +0200 Subject: [PATCH 2/2] yamatos request --- .../Exiled.API/Extensions/MirrorExtensions.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 4c30e6b34..860fe60d9 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -13,18 +13,23 @@ namespace Exiled.API.Extensions using System.Linq; using System.Reflection; using System.Reflection.Emit; + 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 HarmonyLib; using InventorySystem; using InventorySystem.Items; using InventorySystem.Items.Autosync; + using InventorySystem.Items.Firearms; using InventorySystem.Items.Firearms.Modules; using InventorySystem.Items.Keycards; using MEC; @@ -37,6 +42,8 @@ namespace Exiled.API.Extensions using PlayerRoles.Spectating; using PlayerRoles.Voice; using RelativePositioning; + using Respawning; + using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using Utils.Networking; @@ -163,6 +170,12 @@ public static ReadOnlyDictionary RpcFullNames /// Target to play sound to. public static void PlayBeepSound(this Player player) => SendFakeTargetRpc(player, ReferenceHub._hostHub.networkIdentity, typeof(AmbientSoundPlayer), nameof(AmbientSoundPlayer.RpcPlaySound), 7); + /// + /// Plays a beep sound that only the target can hear. + /// + /// Target to play sound to. + public static void PlayBeepSound(this NetworkConnection connection) => connection.SendFakeTargetRpc(ReferenceHub._hostHub.networkIdentity, typeof(AmbientSoundPlayer), nameof(AmbientSoundPlayer.RpcPlaySound), 7); + /// /// Set on the player that only the can see. /// @@ -176,7 +189,7 @@ public static ReadOnlyDictionary RpcFullNames /// /// Target to play. /// Position to play on. - /// Weapon' sound to play. + /// Weapon's sound to play. /// Sound's volume to set. /// GunAudioMessage's audioClipId to set (default = 0). [Obsolete("This method is not working. Use PlayGunSound(Player, Vector3, FirearmType, float, int, bool) overload instead.")] @@ -1079,7 +1092,7 @@ public static void SendFakeSyncObject(this NetworkConnection connection, Network NetworkWriterPool.Return(writer2); } - // Get components index in identity.(private) + // Get components' index in identity.(private) private static int GetComponentIndex(NetworkIdentity identity, Type type) { return Array.FindIndex(identity.NetworkBehaviours, (x) => x.GetType() == type);