/// <summary> /// Spawns a object from the pool at a given position and rotation. Can only be called from server. /// </summary> /// <param name="poolName">The name of the pool</param> /// <param name="position">The position to spawn the object at</param> /// <param name="rotation">The rotation to spawn the object at</param> /// <returns></returns> public static GameObject SpawnPoolObject(string poolName, Vector3 position, Quaternion rotation) { if (!NetworkingManager.singleton.isServer) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Object spawning can only occur on server"); } return(null); } GameObject go = Pools[PoolNamesToIndexes[poolName]].SpawnObject(position, rotation); using (BitWriter writer = BitWriter.Get()) { writer.WriteUInt(go.GetComponent <NetworkedObject>().NetworkId); writer.WriteFloat(position.x); writer.WriteFloat(position.y); writer.WriteFloat(position.z); writer.WriteFloat(rotation.eulerAngles.x); writer.WriteFloat(rotation.eulerAngles.y); writer.WriteFloat(rotation.eulerAngles.z); InternalMessageHandler.Send("MLAPI_SPAWN_POOL_OBJECT", "MLAPI_INTERNAL", writer, null); } return(go); }
internal static void PassthroughSend(uint targetId, uint sourceId, ushort messageType, int channelId, BitReader reader, uint?networkId = null, ushort?orderId = null) { if (netManager.isHost && targetId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Host trying to send data to it's own client return; } using (BitWriter writer = BitWriter.Get()) { writer.WriteGenericMessageHeader(messageType, networkId != null, networkId.GetValueOrDefault(), orderId.GetValueOrDefault(), true, null, sourceId); #if !DISABLE_CRYPTOGRAPHY if (netManager.NetworkConfig.EncryptedChannelsHashSet.Contains(MessageManager.reverseChannels[channelId])) { writer.WriteByteArray(CryptographyHelper.Encrypt(reader.ReadByteArray(), netManager.ConnectedClients[targetId].AesKey)); } else #endif writer.WriteByteArray(reader.ReadByteArray()); writer.Finalize(ref FinalMessageBuffer); netManager.NetworkConfig.NetworkTransport.QueueMessageForSending(targetId, ref FinalMessageBuffer, (int)writer.GetFinalizeSize(), channelId, false, out byte error); } }
internal static void OnDestroyObject(uint networkId, bool destroyGameObject) { if (!spawnedObjects.ContainsKey(networkId) || (netManager != null && !netManager.NetworkConfig.HandleObjectSpawning)) { return; } if (spawnedObjects[networkId].OwnerClientId != NetworkingManager.singleton.NetworkConfig.NetworkTransport.InvalidDummyId && !spawnedObjects[networkId].isPlayerObject) { //Someone owns it. NetworkingManager.singleton.connectedClients[spawnedObjects[networkId].OwnerClientId].OwnedObjects.RemoveAll(x => x.NetworkId == networkId); } GameObject go = spawnedObjects[networkId].gameObject; if (netManager != null && netManager.isServer) { releasedNetworkObjectIds.Push(networkId); if (spawnedObjects[networkId] != null) { using (BitWriter writer = BitWriter.Get()) { writer.WriteUInt(networkId); InternalMessageHandler.Send("MLAPI_DESTROY_OBJECT", "MLAPI_INTERNAL", writer, null); } } } if (destroyGameObject && go != null) { MonoBehaviour.Destroy(go); } spawnedObjects.Remove(networkId); }
/// <summary> /// Returns a base64 encoded version of the config /// </summary> /// <returns></returns> public string ToBase64() { NetworkConfig config = this; using (BitWriter writer = BitWriter.Get()) { writer.WriteUShort(config.ProtocolVersion); writer.WriteBits((byte)config.Transport, 5); writer.WriteUShort((ushort)config.Channels.Count); for (int i = 0; i < config.Channels.Count; i++) { writer.WriteString(config.Channels[i].Name); writer.WriteBool(config.Channels[i].Encrypted); writer.WriteBits((byte)config.Channels[i].Type, 5); } writer.WriteUShort((ushort)config.MessageTypes.Count); for (int i = 0; i < config.MessageTypes.Count; i++) { writer.WriteString(config.MessageTypes[i].Name); writer.WriteBool(config.MessageTypes[i].Passthrough); } writer.WriteUShort((ushort)config.RegisteredScenes.Count); for (int i = 0; i < config.RegisteredScenes.Count; i++) { writer.WriteString(config.RegisteredScenes[i]); } writer.WriteUShort((ushort)config.NetworkedPrefabs.Count); for (int i = 0; i < config.NetworkedPrefabs.Count; i++) { writer.WriteBool(config.NetworkedPrefabs[i].playerPrefab); writer.WriteString(config.NetworkedPrefabs[i].name); } writer.WriteInt(config.MessageBufferSize); writer.WriteInt(config.ReceiveTickrate); writer.WriteInt(config.MaxReceiveEventsPerTickRate); writer.WriteInt(config.SendTickrate); writer.WriteInt(config.EventTickrate); writer.WriteInt(config.MaxConnections); writer.WriteInt(config.ConnectPort); writer.WriteString(config.ConnectAddress); writer.WriteInt(config.ClientConnectionBufferTimeout); writer.WriteBool(config.ConnectionApproval); writer.WriteInt(config.SecondsHistory); writer.WriteBool(config.HandleObjectSpawning); writer.WriteBool(config.EnableEncryption); writer.WriteBool(config.SignKeyExchange); writer.WriteBool(config.AllowPassthroughMessages); writer.WriteBool(config.EnableSceneSwitching); writer.WriteBool(config.EnableTimeResync); writer.WriteBits((byte)config.AttributeMessageMode, 3); return(Convert.ToBase64String(writer.Finalize())); } }
/// <summary> /// Gets a SHA256 hash of parts of the NetworkingConfiguration instance /// </summary> /// <param name="cache"></param> /// <returns></returns> public ulong GetConfig(bool cache = true) { if (ConfigHash != null && cache) { return(ConfigHash.Value); } using (BitWriter writer = BitWriter.Get()) { writer.WriteUShort(ProtocolVersion); for (int i = 0; i < Channels.Count; i++) { writer.WriteString(Channels[i].Name); writer.WriteByte((byte)Channels[i].Type); if (EnableEncryption) { writer.WriteBool(Channels[i].Encrypted); } } for (int i = 0; i < MessageTypes.Count; i++) { writer.WriteString(MessageTypes[i].Name); if (AllowPassthroughMessages) { writer.WriteBool(MessageTypes[i].Passthrough); } } if (EnableSceneSwitching) { for (int i = 0; i < RegisteredScenes.Count; i++) { writer.WriteString(RegisteredScenes[i]); } } if (HandleObjectSpawning) { for (int i = 0; i < NetworkedPrefabs.Count; i++) { writer.WriteString(NetworkedPrefabs[i].name); } } writer.WriteBool(HandleObjectSpawning); writer.WriteBool(EnableEncryption); writer.WriteBool(AllowPassthroughMessages); writer.WriteBool(EnableSceneSwitching); writer.WriteBool(SignKeyExchange); writer.WriteBits((byte)AttributeMessageMode, 3); // Returns a 160 bit / 20 byte / 5 int checksum of the config if (cache) { ConfigHash = writer.Finalize().GetStableHash64(); return(ConfigHash.Value); } return(writer.Finalize().GetStableHash64()); } }
internal string ToBase64() { using (BitWriter writer = BitWriter.Get()) { writer.WriteUShort(ProtocolVersion); writer.WriteBits((byte)Transport, 5); writer.WriteUShort((ushort)Channels.Count); for (int i = 0; i < Channels.Count; i++) { writer.WriteString(Channels[i].Name); writer.WriteBool(Channels[i].Encrypted); writer.WriteBits((byte)Channels[i].Type, 5); } writer.WriteUShort((ushort)MessageTypes.Count); for (int i = 0; i < MessageTypes.Count; i++) { writer.WriteString(MessageTypes[i].Name); writer.WriteBool(MessageTypes[i].Passthrough); } writer.WriteUShort((ushort)RegisteredScenes.Count); for (int i = 0; i < RegisteredScenes.Count; i++) { writer.WriteString(RegisteredScenes[i]); } writer.WriteUShort((ushort)NetworkedPrefabs.Count); for (int i = 0; i < NetworkedPrefabs.Count; i++) { writer.WriteBool(NetworkedPrefabs[i].playerPrefab); writer.WriteString(NetworkedPrefabs[i].name); } writer.WriteInt(MessageBufferSize); writer.WriteInt(ReceiveTickrate); writer.WriteInt(MaxReceiveEventsPerTickRate); writer.WriteInt(SendTickrate); writer.WriteInt(EventTickrate); writer.WriteInt(MaxConnections); writer.WriteInt(ConnectPort); writer.WriteString(ConnectAddress); writer.WriteInt(ClientConnectionBufferTimeout); writer.WriteBool(ConnectionApproval); writer.WriteInt(SecondsHistory); writer.WriteBool(HandleObjectSpawning); writer.WriteBool(EnableEncryption); writer.WriteBool(SignKeyExchange); writer.WriteBool(AllowPassthroughMessages); writer.WriteBool(EnableSceneSwitching); writer.WriteBool(EnableTimeResync); writer.WriteBits((byte)AttributeMessageMode, 3); return(Convert.ToBase64String(writer.Finalize())); } }
//RETURNS THE CLIENTIDS WHICH WAS NOT BEING OBSERVED internal static ref List <uint> Send(string messageType, string channelName, BitWriter messageWriter, uint clientIdToIgnore, uint?fromNetId, uint?networkId = null, ushort?orderId = null) { failedObservers.Clear(); if (netManager.NetworkConfig.EncryptedChannels.Contains(channelName)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Cannot send messages over encrypted channel to multiple clients"); } return(ref failedObservers); } using (BitWriter writer = BitWriter.Get()) { writer.WriteGenericMessageHeader(MessageManager.messageTypes[messageType], networkId != null, networkId.GetValueOrDefault(), orderId.GetValueOrDefault(), false, 0, 0); writer.WriteWriter(messageWriter); int channel = MessageManager.channels[channelName]; foreach (KeyValuePair <uint, NetworkedClient> pair in netManager.connectedClients) { if (pair.Key == clientIdToIgnore) { continue; } uint targetClientId = pair.Key; if (netManager.isHost && targetClientId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Don't invoke the message on our own machine. Instant stack overflow. continue; } else if (targetClientId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Client trying to send data to host targetClientId = netManager.NetworkConfig.NetworkTransport.ServerNetId; } //If we respect the observers, and the message is targeted (networkId != null) and the targetedNetworkId isnt observing the receiver. Then we continue if (netManager.isServer && fromNetId != null && !SpawnManager.spawnedObjects[fromNetId.Value].observers.Contains(pair.Key)) { failedObservers.Add(pair.Key); continue; } writer.Finalize(ref FinalMessageBuffer); NetworkProfiler.StartEvent(TickType.Send, (uint)messageWriter.GetFinalizeSize(), channelName, messageType); byte error; int byteCount = (int)writer.GetFinalizeSize(); NetworkProfiler.addBytesSent((uint)byteCount); netManager.NetworkConfig.NetworkTransport.QueueMessageForSending(targetClientId, ref FinalMessageBuffer, byteCount, channel, false, out error); NetworkProfiler.EndEvent(); } return(ref failedObservers); } }
public void TestWritingTrue() { BitWriter bitWriter = BitWriter.Get(); bitWriter.WriteBool(true); byte[] result = bitWriter.Finalize(); Assert.That(result.Length, Is.EqualTo(1)); Assert.That(result[0], Is.EqualTo(1)); }
internal static void FlushSceneObjects() { if (!NetworkingManager.singleton.isServer) { return; } //This loop is bad. For each client, we loop over every object twice. foreach (KeyValuePair <uint, NetworkedClient> client in netManager.connectedClients) { int sceneObjects = 0; foreach (var netObject in SpawnManager.spawnedObjects) { if (netObject.Value.sceneObject == null || netObject.Value.sceneObject == true) { sceneObjects++; } } using (BitWriter writer = BitWriter.Get()) { writer.WriteUShort((ushort)sceneObjects); foreach (var netObject in SpawnManager.spawnedObjects) { if (netObject.Value.sceneObject == null || netObject.Value.sceneObject == true) { writer.WriteBool(false); //isLocalPlayer writer.WriteUInt(netObject.Value.NetworkId); writer.WriteUInt(netObject.Value.OwnerClientId); writer.WriteInt(NetworkingManager.singleton.NetworkConfig.NetworkPrefabIds[netObject.Value.NetworkedPrefabName]); writer.WriteBool(netObject.Value.sceneObject == null ? true : netObject.Value.sceneObject.Value); writer.WriteBool(netObject.Value.observers.Contains(client.Key)); writer.WriteFloat(netObject.Value.transform.position.x); writer.WriteFloat(netObject.Value.transform.position.y); writer.WriteFloat(netObject.Value.transform.position.z); writer.WriteFloat(netObject.Value.transform.rotation.eulerAngles.x); writer.WriteFloat(netObject.Value.transform.rotation.eulerAngles.y); writer.WriteFloat(netObject.Value.transform.rotation.eulerAngles.z); if (netObject.Value.observers.Contains(client.Key)) { netObject.Value.WriteFormattedSyncedVarData(writer); } } } InternalMessageHandler.Send(client.Key, "MLAPI_ADD_OBJECTS", "MLAPI_INTERNAL", writer, null); } } }
internal static void Send(uint[] clientIds, string messageType, string channelName, BitWriter messageWriter, uint?fromNetId, uint?networkId = null, ushort?orderId = null) { if (netManager.NetworkConfig.EncryptedChannelsHashSet.Contains(channelName)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Cannot send messages over encrypted channel to multiple clients"); } return; } using (BitWriter writer = BitWriter.Get()) { writer.WriteGenericMessageHeader(MessageManager.messageTypes[messageType], networkId != null, networkId.GetValueOrDefault(), orderId.GetValueOrDefault(), false, 0, 0); writer.WriteWriter(messageWriter); int channel = MessageManager.channels[channelName]; for (int i = 0; i < clientIds.Length; i++) { uint targetClientId = clientIds[i]; if (netManager.isHost && targetClientId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Don't invoke the message on our own machine. Instant stack overflow. continue; } else if (targetClientId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Client trying to send data to host targetClientId = netManager.NetworkConfig.NetworkTransport.ServerNetId; } //If we respect the observers, and the message is targeted (networkId != null) and the targetedNetworkId isnt observing the receiver. Then we continue if (netManager.isServer && fromNetId != null && !SpawnManager.spawnedObjects[fromNetId.Value].observers.Contains(clientIds[i])) { continue; } writer.Finalize(ref FinalMessageBuffer); NetworkProfiler.StartEvent(TickType.Send, (uint)messageWriter.GetFinalizeSize(), channelName, messageType); byte error; netManager.NetworkConfig.NetworkTransport.QueueMessageForSending(targetClientId, ref FinalMessageBuffer, (int)writer.GetFinalizeSize(), channel, false, out error); NetworkProfiler.EndEvent(); } } }
/// <summary> /// Destroys a NetworkedObject if it's part of a pool. Use this instead of the MonoBehaviour Destroy method. Can only be called from Server. /// </summary> /// <param name="netObject">The NetworkedObject instance to destroy</param> public static void DestroyPoolObject(NetworkedObject netObject) { if (!NetworkingManager.singleton.isServer) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Objects can only be destroyed on the server"); } return; } netObject.gameObject.SetActive(false); using (BitWriter writer = BitWriter.Get()) { writer.WriteUInt(netObject.NetworkId); InternalMessageHandler.Send("MLAPI_DESTROY_POOL_OBJECT", "MLAPI_INTERNAL", writer, null); } }
/// <summary> /// Switches to a scene with a given name. Can only be called from Server /// </summary> /// <param name="sceneName">The name of the scene to switch to</param> public static void SwitchScene(string sceneName) { if (!NetworkingManager.singleton.NetworkConfig.EnableSceneSwitching) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Scene switching is not enabled"); } return; } else if (isSwitching) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Scene switch already in progress"); } return; } else if (!registeredSceneNames.Contains(sceneName)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("The scene " + sceneName + " is not registered as a switchable scene."); } return; } SpawnManager.DestroySceneObjects(); //Destroy current scene objects before switching. CurrentSceneIndex = sceneNameToIndex[sceneName]; isSwitching = true; lastScene = SceneManager.GetActiveScene(); AsyncOperation sceneLoad = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); sceneLoad.completed += OnSceneLoaded; using (BitWriter writer = BitWriter.Get()) { writer.WriteUInt(sceneNameToIndex[sceneName]); InternalMessageHandler.Send("MLAPI_SWITCH_SCENE", "MLAPI_INTERNAL", writer, null); } }
internal static void RemoveOwnership(uint netId) { if (!netManager.isServer) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("You can only remove ownership from Server"); } return; } NetworkedObject netObject = SpawnManager.spawnedObjects[netId]; NetworkingManager.singleton.connectedClients[netObject.OwnerClientId].OwnedObjects.RemoveAll(x => x.NetworkId == netId); netObject.ownerClientId = NetworkingManager.singleton.NetworkConfig.NetworkTransport.InvalidDummyId; using (BitWriter writer = BitWriter.Get()) { writer.WriteUInt(netId); writer.WriteUInt(netObject.ownerClientId); InternalMessageHandler.Send("MLAPI_CHANGE_OWNER", "MLAPI_INTERNAL", writer, null); } }
//RETURNS IF IT SUCCEDED OR FAILED BECAUSE OF NON-OBSERVER. ANY OTHER FAIL WILL RETURN TRUE internal static bool Send(uint clientId, string messageType, string channelName, BitWriter messageWriter, uint?fromNetId, uint?networkId = null, ushort?orderId = null, bool skipQueue = false) { uint targetClientId = clientId; if (netManager.isHost && targetClientId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Don't invoke the message on our own machine. Instant stack overflow. return(true); } else if (targetClientId == netManager.NetworkConfig.NetworkTransport.HostDummyId) { //Client trying to send data to host targetClientId = netManager.NetworkConfig.NetworkTransport.ServerNetId; } //If we respect the observers, and the message is targeted (networkId != null) and the targetedNetworkId isnt observing the receiver. Then we return if (netManager.isServer && fromNetId != null && !SpawnManager.spawnedObjects[fromNetId.Value].observers.Contains(clientId)) { return(false); } bool isPassthrough = (!netManager.isServer && clientId != netManager.NetworkConfig.NetworkTransport.ServerNetId && netManager.NetworkConfig.AllowPassthroughMessages); if (isPassthrough && !netManager.NetworkConfig.PassthroughMessageHashSet.Contains(MessageManager.messageTypes[messageType])) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("The The MessageType " + messageType + " is not registered as an allowed passthrough message type"); } return(true); } using (BitWriter writer = BitWriter.Get()) { writer.WriteGenericMessageHeader(MessageManager.messageTypes[messageType], networkId != null, networkId.GetValueOrDefault(), orderId.GetValueOrDefault(), isPassthrough, clientId, null); #if !DISABLE_CRYPTOGRAPHY if (netManager.NetworkConfig.EncryptedChannelsHashSet.Contains(channelName)) { //This is an encrypted message. byte[] encrypted; if (netManager.isServer) { encrypted = CryptographyHelper.Encrypt(messageWriter.Finalize(), netManager.ConnectedClients[clientId].AesKey); } else { encrypted = CryptographyHelper.Encrypt(messageWriter.Finalize(), netManager.clientAesKey); } writer.WriteByteArray(encrypted); } else #endif writer.WriteWriter(messageWriter); if (isPassthrough) { targetClientId = netManager.NetworkConfig.NetworkTransport.ServerNetId; } writer.Finalize(ref FinalMessageBuffer); NetworkProfiler.StartEvent(TickType.Send, (uint)messageWriter.GetFinalizeSize(), channelName, messageType); byte error; if (skipQueue) { netManager.NetworkConfig.NetworkTransport.QueueMessageForSending(targetClientId, ref FinalMessageBuffer, (int)writer.GetFinalizeSize(), MessageManager.channels[channelName], true, out error); } else { netManager.NetworkConfig.NetworkTransport.QueueMessageForSending(targetClientId, ref FinalMessageBuffer, (int)writer.GetFinalizeSize(), MessageManager.channels[channelName], false, out error); } NetworkProfiler.EndEvent(); return(true); } }
internal void RebuildObservers(uint?clientId = null) { bool initial = clientId != null; if (initial) { bool shouldBeAdded = true; for (int i = 0; i < childNetworkedBehaviours.Count; i++) { bool state = childNetworkedBehaviours[i].OnCheckObserver(clientId.Value); if (state == false) { shouldBeAdded = false; break; } } if (shouldBeAdded) { observers.Add(clientId.Value); } } else { previousObservers.Clear(); foreach (var item in observers) { previousObservers.Add(item); } bool update = false; for (int i = 0; i < childNetworkedBehaviours.Count; i++) { bool changed = childNetworkedBehaviours[i].OnRebuildObservers(observers); if (changed) { update = true; break; } } if (update) { foreach (KeyValuePair <uint, NetworkedClient> pair in NetworkingManager.singleton.connectedClients) { if (pair.Key == NetworkingManager.singleton.NetworkConfig.NetworkTransport.HostDummyId) { continue; } if ((previousObservers.Contains(pair.Key) && !observers.Contains(pair.Key)) || (!previousObservers.Contains(pair.Key) && observers.Contains(pair.Key))) { //Something changed for this client. using (BitWriter writer = BitWriter.Get()) { writer.WriteUInt(networkId); writer.WriteBool(observers.Contains(pair.Key)); if (observers.Contains(pair.Key)) { WriteFormattedSyncedVarData(writer); } InternalMessageHandler.Send(pair.Key, "MLAPI_SET_VISIBILITY", "MLAPI_INTERNAL", writer, null); } } } } else { foreach (var item in previousObservers) { observers.Add(item); } } previousObservers.Clear(); } }
internal static void SpawnObject(NetworkedObject netObject, uint?clientOwnerId = null, BitWriter payload = null) { if (netObject.isSpawned) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Object already spawned"); } return; } else if (!netManager.isServer) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("Only server can spawn objects"); } return; } else if (!netManager.NetworkConfig.NetworkPrefabIds.ContainsKey(netObject.NetworkedPrefabName)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("The prefab name " + netObject.NetworkedPrefabName + " does not exist as a networkedPrefab"); } return; } else if (!netManager.NetworkConfig.HandleObjectSpawning) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) { LogHelper.LogWarning("NetworkConfig is set to not handle object spawning"); } return; } uint netId = GetNetworkObjectId(); netObject.networkId = netId; spawnedObjects.Add(netId, netObject); netObject._isSpawned = true; netObject.sceneObject = false; if (clientOwnerId != null) { netObject.ownerClientId = clientOwnerId.Value; NetworkingManager.singleton.connectedClients[clientOwnerId.Value].OwnedObjects.Add(netObject); } if (payload == null) { netObject.InvokeBehaviourNetworkSpawn(null); } else { using (BitReader payloadReader = BitReader.Get(payload.Finalize())) netObject.InvokeBehaviourNetworkSpawn(payloadReader); } foreach (var client in netManager.connectedClients) { netObject.RebuildObservers(client.Key); using (BitWriter writer = BitWriter.Get()) { writer.WriteBool(false); writer.WriteUInt(netObject.NetworkId); writer.WriteUInt(netObject.OwnerClientId); writer.WriteInt(netManager.NetworkConfig.NetworkPrefabIds[netObject.NetworkedPrefabName]); writer.WriteBool(netObject.sceneObject == null ? true : netObject.sceneObject.Value); writer.WriteBool(netObject.observers.Contains(client.Key)); writer.WriteFloat(netObject.transform.position.x); writer.WriteFloat(netObject.transform.position.y); writer.WriteFloat(netObject.transform.position.z); writer.WriteFloat(netObject.transform.rotation.eulerAngles.x); writer.WriteFloat(netObject.transform.rotation.eulerAngles.y); writer.WriteFloat(netObject.transform.rotation.eulerAngles.z); writer.WriteBool(payload != null); if (netObject.observers.Contains(client.Key)) { netObject.WriteFormattedSyncedVarData(writer); } if (payload != null) { writer.WriteWriter(payload); } InternalMessageHandler.Send(client.Key, "MLAPI_ADD_OBJECT", "MLAPI_INTERNAL", writer, null); } } }