From 83cb6a85f045e41d894a5f55d71db6632b4c34a8 Mon Sep 17 00:00:00 2001 From: MS-crew <100300664+MS-crew@users.noreply.github.com> Date: Sun, 26 Apr 2026 17:16:26 +0300 Subject: [PATCH 1/7] Refactor object spawn and unspawn logic --- .../Exiled.API/Extensions/MirrorExtensions.cs | 36 ++++--------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 20f5b1a37..0ffd6ffd9 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -620,16 +620,8 @@ public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vect 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 }); - } + NetworkServer.UnSpawn(identity.gameObject); + NetworkServer.Spawn(identity.gameObject); } /// @@ -643,16 +635,8 @@ public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vec return; identity.gameObject.transform.localScale = scale; - 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 }); - } + NetworkServer.UnSpawn(identity.gameObject); + NetworkServer.Spawn(identity.gameObject); } /// @@ -788,16 +772,8 @@ public static void EditNetworkObject(NetworkIdentity identity, Action Date: Fri, 8 May 2026 17:50:03 +0300 Subject: [PATCH 2/7] fix not real player specific / added edit network identiyty player specific / code clean --- .../Exiled.API/Extensions/MirrorExtensions.cs | 129 ++++++++++++------ 1 file changed, 85 insertions(+), 44 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 0ffd6ffd9..3abcad3fa 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -400,7 +400,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); @@ -538,27 +538,6 @@ public static void MessageTranslated(this Player player, string words, string tr announcement.Payload.SendToHubsConditionally(hub => hub == player.ReferenceHub); } - /// - /// Moves object for the player. - /// - /// Target to send. - /// The to move. - /// The position to change. - 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 }); - } - /// /// Sends to the player a Fake Change Scene. /// @@ -588,6 +567,47 @@ public static void ChangeSceneToAllClients(ScenesType scene) NetworkServer.SendToAll(message); } + /// + /// Sends a spawn message for the specified to the given . + /// + /// The player who should receive the spawn message. + /// The to spawn. + public static void SpawnNetworkIdentity(this Player player, NetworkIdentity identity) => SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection }); + + /// + /// Sends a destroy message for the specified to the given . + /// + /// The player who should receive the destroy message. + /// The to destroy. + public static void DestroyNetworkIdentity(this Player player, NetworkIdentity identity) => player.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 }); + + /// + /// Moves object for the player. + /// + /// Target to send. + /// The to move. + /// The position to change. + 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.DestroyNetworkIdentity(identity); + player.SpawnNetworkIdentity(identity); + + identity.transform.position = originalPosition; + } + /// /// Scales an object for the specified player. /// @@ -599,14 +619,33 @@ public static void ScaleNetworkIdentityObject(this Player player, NetworkIdentit if (identity == null) return; - identity.gameObject.transform.localScale = scale; - ObjectDestroyMessage objectDestroyMessage = new() - { - netId = identity.netId, - }; + Vector3 originalScale = identity.transform.localScale; + identity.transform.localScale = scale; - player.Connection.Send(objectDestroyMessage, 0); - SendSpawnMessageMethodInfo?.Invoke(null, new object[] { identity, player.Connection }); + player.DestroyNetworkIdentity(identity); + player.SpawnNetworkIdentity(identity); + + identity.transform.localScale = originalScale; + } + + /// + /// 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 Player player, NetworkIdentity identity, Action customAction, Action resetAction) + { + if (identity == null) + return; + + customAction.Invoke(identity); + + player.DestroyNetworkIdentity(identity); + player.SpawnNetworkIdentity(identity); + + resetAction?.Invoke(identity); } /// @@ -619,7 +658,8 @@ public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vect if (identity == null) return; - identity.gameObject.transform.position = pos; + identity.transform.position = pos; + NetworkServer.UnSpawn(identity.gameObject); NetworkServer.Spawn(identity.gameObject); } @@ -634,7 +674,21 @@ public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vec if (identity == null) return; - identity.gameObject.transform.localScale = scale; + identity.transform.localScale = scale; + + NetworkServer.UnSpawn(identity.gameObject); + NetworkServer.Spawn(identity.gameObject); + } + + /// + /// Edit 's parameter and sync. + /// + /// The to edit. + /// Edit function. + public static void EditNetworkObject(this NetworkIdentity identity, Action customAction) + { + customAction.Invoke(identity); + NetworkServer.UnSpawn(identity.gameObject); NetworkServer.Spawn(identity.gameObject); } @@ -763,19 +817,6 @@ public static void SendFakeSyncObject(Player target, NetworkIdentity behaviorOwn NetworkWriterPool.Return(writer2); } - /// - /// Edit 's parameter and sync. - /// - /// Target object. - /// Edit function. - public static void EditNetworkObject(NetworkIdentity identity, Action customAction) - { - customAction.Invoke(identity); - - NetworkServer.UnSpawn(identity.gameObject); - NetworkServer.Spawn(identity.gameObject); - } - // Get components index in identity.(private) private static int GetComponentIndex(NetworkIdentity identity, Type type) { From baf43d2e41f3ca6bba4b4eb03eeb2c5ee761e890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Fri, 8 May 2026 20:35:43 +0300 Subject: [PATCH 3/7] michcios suggestion --- .../Exiled.API/Extensions/MirrorExtensions.cs | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 3abcad3fa..b78c82bf4 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -588,6 +588,18 @@ public static void ChangeSceneToAllClients(ScenesType scene) /// The network ID of the object to destroy. public static void DestroyNetworkId(this Player player, uint netId) => player.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 to respawn. + public static void RespawnNetworkIdentity(this Player player, NetworkIdentity identity) + { + player.DestroyNetworkIdentity(identity); + player.SpawnNetworkIdentity(identity); + } + /// /// Moves object for the player. /// @@ -602,8 +614,7 @@ public static void MoveNetworkIdentityObject(this Player player, NetworkIdentity Vector3 originalPosition = identity.transform.position; identity.transform.position = pos; - player.DestroyNetworkIdentity(identity); - player.SpawnNetworkIdentity(identity); + player.RespawnNetworkIdentity(identity); identity.transform.position = originalPosition; } @@ -622,8 +633,7 @@ public static void ScaleNetworkIdentityObject(this Player player, NetworkIdentit Vector3 originalScale = identity.transform.localScale; identity.transform.localScale = scale; - player.DestroyNetworkIdentity(identity); - player.SpawnNetworkIdentity(identity); + player.RespawnNetworkIdentity(identity); identity.transform.localScale = originalScale; } @@ -640,14 +650,31 @@ public static void EditNetworkObject(this Player player, NetworkIdentity identit if (identity == null) return; - customAction.Invoke(identity); + customAction?.Invoke(identity); - player.DestroyNetworkIdentity(identity); - player.SpawnNetworkIdentity(identity); + player.RespawnNetworkIdentity(identity); resetAction?.Invoke(identity); } + /// + /// Respawns the specified by respawning its underlying on the server. + /// This forces Mirror to reinitialize the network state for the object. + /// + /// The to respawn. + public static void RespawnNetworkIdentity(this NetworkIdentity identity) => RespawnNetworkObject(identity.gameObject); + + /// + /// Respawns a networked on the server by unspawning and respawning it. + /// This forces Mirror to reinitialize the network state for the object. + /// + /// The networked GameObject to respawn. + public static void RespawnNetworkObject(this GameObject gameObject) + { + NetworkServer.UnSpawn(gameObject); + NetworkServer.Spawn(gameObject); + } + /// /// Moves object for all the players. /// @@ -659,9 +686,7 @@ public static void MoveNetworkIdentityObject(this NetworkIdentity identity, Vect return; identity.transform.position = pos; - - NetworkServer.UnSpawn(identity.gameObject); - NetworkServer.Spawn(identity.gameObject); + RespawnNetworkIdentity(identity); } /// @@ -675,9 +700,7 @@ public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vec return; identity.transform.localScale = scale; - - NetworkServer.UnSpawn(identity.gameObject); - NetworkServer.Spawn(identity.gameObject); + RespawnNetworkIdentity(identity); } /// @@ -688,9 +711,7 @@ public static void ScaleNetworkIdentityObject(this NetworkIdentity identity, Vec public static void EditNetworkObject(this NetworkIdentity identity, Action customAction) { customAction.Invoke(identity); - - NetworkServer.UnSpawn(identity.gameObject); - NetworkServer.Spawn(identity.gameObject); + RespawnNetworkIdentity(identity); } /// From 6a5ad1d1999c1a315a6b72c6451192c17c10af68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 9 May 2026 00:28:41 +0300 Subject: [PATCH 4/7] fix --- .../Exiled.API/Extensions/MirrorExtensions.cs | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index b78c82bf4..a13dd2605 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -148,9 +148,9 @@ public static ReadOnlyDictionary RpcFullNames } /// - /// Gets a 's . + /// Gets a NetworkIdentity.SerializeServer's . /// - public static MethodInfo SetDirtyBitsMethodInfo => field ??= typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.SetSyncVarDirtyBit)); + public static MethodInfo SerializeServerMethodInfo => field ??= typeof(NetworkIdentity).GetMethod("SerializeServer", BindingFlags.NonPublic | BindingFlags.Instance); /// /// Gets a NetworkServer.SendSpawnMessage's . @@ -662,7 +662,57 @@ public static void EditNetworkObject(this Player player, NetworkIdentity identit /// This forces Mirror to reinitialize the network state for the object. /// /// The to respawn. - public static void RespawnNetworkIdentity(this NetworkIdentity identity) => RespawnNetworkObject(identity.gameObject); + public static void RespawnNetworkIdentity(this NetworkIdentity identity) + { + ObjectDestroyMessage destroyMessage = new() { netId = identity.netId }; + NetworkServer.SendToReady(destroyMessage); + + using NetworkWriterPooled ownerWriter = NetworkWriterPool.Get(); + using NetworkWriterPooled observersWriter = NetworkWriterPool.Get(); + + SerializeServerMethodInfo?.Invoke(identity, new object[] { true, ownerWriter, observersWriter }); + + ArraySegment ownerPayload = ownerWriter.ToArraySegment(); + ArraySegment 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, + }; + + foreach (Player player in Player.List) + { + if (player.Connection == null || !player.IsConnected) + continue; + + bool isOwner = identity.connectionToClient == player.Connection; + + if (isOwner) + { + spawnMessage.isOwner = true; + spawnMessage.isLocalPlayer = player.NetworkIdentity == identity; + spawnMessage.payload = ownerPayload; + + player.Connection.Send(spawnMessage); + + spawnMessage.isOwner = false; + spawnMessage.isLocalPlayer = false; + spawnMessage.payload = observerPayload; + } + else + { + player.Connection.Send(spawnMessage); + } + } + } /// /// Respawns a networked on the server by unspawning and respawning it. @@ -773,7 +823,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); } /// From a79a6d1994d9bdab03555e624ed66471e76db1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 9 May 2026 00:55:29 +0300 Subject: [PATCH 5/7] better --- .../Exiled.API/Extensions/MirrorExtensions.cs | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index a13dd2605..84b18470d 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -658,14 +658,14 @@ public static void EditNetworkObject(this Player player, NetworkIdentity identit } /// - /// Respawns the specified by respawning its underlying on the server. - /// This forces Mirror to reinitialize the network state for the object. + /// Sends a spawn message for the specified to a targeted collection of players. /// - /// The to respawn. - public static void RespawnNetworkIdentity(this NetworkIdentity identity) + /// The to serialize and spawn. + /// The collection of who will receive the spawn message. + public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEnumerable players) { - ObjectDestroyMessage destroyMessage = new() { netId = identity.netId }; - NetworkServer.SendToReady(destroyMessage); + if (identity == null || identity.netId == 0 || !players.Any()) + return; using NetworkWriterPooled ownerWriter = NetworkWriterPool.Get(); using NetworkWriterPooled observersWriter = NetworkWriterPool.Get(); @@ -688,9 +688,9 @@ public static void RespawnNetworkIdentity(this NetworkIdentity identity) payload = observerPayload, }; - foreach (Player player in Player.List) + foreach (Player player in players) { - if (player.Connection == null || !player.IsConnected) + if (!player.IsConnected) continue; bool isOwner = identity.connectionToClient == player.Connection; @@ -714,6 +714,40 @@ public static void RespawnNetworkIdentity(this NetworkIdentity identity) } } + /// + /// Sends a destroy message for the specified to a targeted collection of players. + /// + /// The to destroy on the clients. + /// The collection of who will receive the destroy message. + public static void DestroyNetworkIdentityForPlayers(this NetworkIdentity identity, IEnumerable players) + { + if (identity == null || identity.netId == 0 || !players.Any()) + return; + + ObjectDestroyMessage destroyMessage = new ObjectDestroyMessage() { netId = identity.netId }; + + foreach (Player player in players) + { + if (!player.IsConnected) + continue; + + player.Connection.Send(destroyMessage); + } + } + + /// + /// Respawns the specified by respawning its underlying on the server. + /// + /// The to respawn. + 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); + } + /// /// Respawns a networked on the server by unspawning and respawning it. /// This forces Mirror to reinitialize the network state for the object. From 58010511dc94f4769886d9a5e47e2af1ade663aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 9 May 2026 01:04:51 +0300 Subject: [PATCH 6/7] more optimize destroy --- EXILED/Exiled.API/Extensions/MirrorExtensions.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 84b18470d..52f5813a3 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -724,14 +724,16 @@ public static void DestroyNetworkIdentityForPlayers(this NetworkIdentity identit if (identity == null || identity.netId == 0 || !players.Any()) return; - ObjectDestroyMessage destroyMessage = new ObjectDestroyMessage() { netId = identity.netId }; + using NetworkWriterPooled writer = NetworkWriterPool.Get(); + NetworkMessages.Pack(new ObjectDestroyMessage() { netId = identity.netId }, writer); + ArraySegment segment = writer.ToArraySegment(); foreach (Player player in players) { if (!player.IsConnected) continue; - player.Connection.Send(destroyMessage); + player.Connection.Send(segment); } } From 1e3b43c75ae5c5800643bae5e42c81fb803f554b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20SAVA=C5=9E?= Date: Sat, 9 May 2026 01:08:58 +0300 Subject: [PATCH 7/7] optimize spawn message --- .../Exiled.API/Extensions/MirrorExtensions.cs | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs index 52f5813a3..ca7742fda 100644 --- a/EXILED/Exiled.API/Extensions/MirrorExtensions.cs +++ b/EXILED/Exiled.API/Extensions/MirrorExtensions.cs @@ -16,23 +16,32 @@ 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; @@ -40,9 +49,13 @@ namespace Exiled.API.Extensions using PlayerRoles.PlayableScps.Scp1507; using PlayerRoles.Spectating; using PlayerRoles.Voice; + using RelativePositioning; + using Respawning; + using UnityEngine; + using Utils.Networking; /// @@ -688,6 +701,10 @@ public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEn payload = observerPayload, }; + using NetworkWriterPooled prepackedObserverWriter = NetworkWriterPool.Get(); + NetworkMessages.Pack(spawnMessage, prepackedObserverWriter); + ArraySegment segment = prepackedObserverWriter.ToArraySegment(); + foreach (Player player in players) { if (!player.IsConnected) @@ -695,22 +712,18 @@ public static void SendSpawnMessageForPlayers(this NetworkIdentity identity, IEn bool isOwner = identity.connectionToClient == player.Connection; - if (isOwner) + if (!isOwner) { - spawnMessage.isOwner = true; - spawnMessage.isLocalPlayer = player.NetworkIdentity == identity; - spawnMessage.payload = ownerPayload; + player.Connection.Send(segment); + continue; + } - player.Connection.Send(spawnMessage); + SpawnMessage ownerMessage = spawnMessage; + ownerMessage.isOwner = true; + ownerMessage.isLocalPlayer = player.NetworkIdentity == identity; + ownerMessage.payload = ownerPayload; - spawnMessage.isOwner = false; - spawnMessage.isLocalPlayer = false; - spawnMessage.payload = observerPayload; - } - else - { - player.Connection.Send(spawnMessage); - } + player.Connection.Send(ownerMessage); } } @@ -924,7 +937,7 @@ 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); }