diff --git a/Runtime/VelNetBinaryManager.cs b/Runtime/VelNetBinaryManager.cs deleted file mode 100644 index 4671de8..0000000 --- a/Runtime/VelNetBinaryManager.cs +++ /dev/null @@ -1,927 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using UnityEngine; -using System.Net; -using UnityEngine.SceneManagement; -using System.Runtime.Serialization.Formatters.Binary; -using System.Runtime.Serialization; -using System.IO; - -namespace VelNet -{ - [AddComponentMenu("VelNet/VelNet Manager")] - public class VelNetBinaryManager : MonoBehaviour - { - public enum MessageSendType - { - LOGIN = 0, - GET_ROOMS = 1, - JOIN_ROOM = 2, - MESSAGE_OTHERS = 3, - MESSAGE_ALL = 4, - MESSAGE_GROUP = 5, - FORM_GROUP = 6 - }; - public enum MessageReceiveType - { - LOGGED_IN = 0, - ROOM_LIST = 1, - CLIENT_JOINED = 2, - MESSAGE = 3 - }; - - public string host; - public int port; - - public static VelNetBinaryManager instance; - - private TcpClient socketConnection; - private Socket udpSocket; - public bool udpConnected; - private IPEndPoint RemoteEndPoint; - private Thread clientReceiveThread; - private Thread clientReceiveThreadUDP; - public int userid = -1; - public string room; - private int messagesReceived = 0; - - public readonly Dictionary players = new Dictionary(); - - /// - /// We just joined a room - /// string - the room name - /// - public static Action OnJoinedRoom; - - /// - /// We just left a room - /// string - the room name we left - /// - public static Action OnLeftRoom; - - /// - /// Somebody else just joined our room - /// - public static Action OnPlayerJoined; - - /// - /// Somebody else just left our room - /// - public static Action OnPlayerLeft; - - public static Action OnConnectedToServer; - public static Action MessageReceived; - public static Action LoggedIn; - public static Action RoomsReceived; - - public bool connected; - - public List prefabs = new List(); - public NetworkObject[] sceneObjects; - public List deletedSceneObjects = new List(); - - /// - /// Maintains a list of all known objects on the server (ones that have ids) - /// - public readonly Dictionary objects = new Dictionary(); - - /// - /// Maintains a list of all known groups on the server - /// - public readonly Dictionary> groups = new Dictionary>(); - - private VelNetPlayer masterPlayer; - public static VelNetPlayer LocalPlayer => instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault(); - public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; - - - // Use this for initialization - public class Message - { - public MessageReceiveType type; - //public string text; - public byte[] data; - public int sender; - } - - - - public readonly List receivedMessages = new List(); - - private void Awake() - { - if (instance != null) - { - Debug.LogError("Multiple NetworkManagers detected! Bad!", this); - } - - instance = this; - - SceneManager.sceneLoaded += (scene, mode) => - { - // add all local network objects - sceneObjects = FindObjectsOfType().Where(o => o.isSceneObject).ToArray(); - }; - } - - private IEnumerator Start() - { - ConnectToTcpServer(); - yield return null; - - try - { - OnConnectedToServer?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - - private void AddMessage(Message m) - { - lock (receivedMessages) - { - //Debug.Log(messagesReceived++); - receivedMessages.Add(m); - } - } - - private void Update() - { - lock (receivedMessages) - { - //the main thread, which can do Unity stuff - foreach (Message m in receivedMessages) - { - switch (m.type) - { - // when you join the server - case 0: - userid = m.sender; - Debug.Log("joined server " + userid); - - try - { - LoggedIn?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - - //start the udp thread - clientReceiveThreadUDP = new Thread(ListenForDataUDP); - clientReceiveThreadUDP.IsBackground = true; - clientReceiveThreadUDP.Start(); - break; - /* - // if this message is for me, that means I joined a new room... - case 2 when userid == m.sender: - { - string oldRoom = LocalPlayer?.room; - - // we clear the list, but will recreate as we get messages from people in our room - players.Clear(); - masterPlayer = null; - - if (m.text != "") - { - VelNetPlayer player = new VelNetPlayer - { - isLocal = true, - userid = m.sender, - room = m.text - }; - - players.Add(userid, player); - if (m.text != "") - { - try - { - OnJoinedRoom?.Invoke(m.text); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - } - // we just left a room - else - { - // delete all networkobjects that aren't sceneobjects or are null now - objects - .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) - .Select(o => o.Key) - .ToList().ForEach(NetworkDestroy); - - // then remove references to the ones that are left - objects.Clear(); - - // empty all the groups - foreach (string group in instance.groups.Keys) - { - SetupMessageGroup(group, new List()); - } - - instance.groups.Clear(); - - try - { - OnLeftRoom?.Invoke(oldRoom); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - break; - } - // not for me, a player is joining or leaving - case 2: - { - VelNetPlayer me = players[userid]; - - if (me.room != m.text) - { - // we got a left message, kill it - // change ownership of all objects to master - List deleteObjects = new List(); - foreach (KeyValuePair kvp in objects) - { - if (kvp.Value.owner == players[m.sender]) // the owner is the player that left - { - // if this object has locked ownership, delete it - if (kvp.Value.ownershipLocked) - { - deleteObjects.Add(kvp.Value.networkId); - } - // I'm the local master player, so can take ownership immediately - else if (me.isLocal && me == masterPlayer) - { - TakeOwnership(kvp.Key); - } - // the master player left, so everyone should set the owner null (we should get a new master shortly) - else if (players[m.sender] == masterPlayer) - { - kvp.Value.owner = null; - } - } - } - - // TODO this may check for ownership in the future. We don't need ownership here - deleteObjects.ForEach(NetworkDestroy); - - players.Remove(m.sender); - } - else - { - // we got a join message, create it - VelNetPlayer player = new VelNetPlayer - { - isLocal = false, - room = m.text, - userid = m.sender - }; - players.Add(m.sender, player); - try - { - OnPlayerJoined?.Invoke(player); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - break; - } - // generic message - case 3: - if (players.ContainsKey(m.sender)) - { - players[m.sender]?.HandleMessage(m); - } - else - { - Debug.LogError("Received message from player that doesn't exist: " + m.text); - } - - break; - // change master player (this should only happen when the first player joins or if the master player leaves) - case 4: - { - if (masterPlayer == null) - { - masterPlayer = players[m.sender]; - - // no master player yet, add the scene objects - - for (int i = 0; i < sceneObjects.Length; i++) - { - sceneObjects[i].networkId = -1 + "-" + i; - sceneObjects[i].owner = masterPlayer; - sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - objects.Add(sceneObjects[i].networkId, sceneObjects[i]); - } - } - else - { - masterPlayer = players[m.sender]; - } - - masterPlayer.SetAsMasterPlayer(); - - // master player should take over any objects that do not have an owner - foreach (KeyValuePair kvp in objects) - { - kvp.Value.owner ??= masterPlayer; - } - - break; - } - */ - } - - MessageReceived?.Invoke(m); - } - - receivedMessages.Clear(); - } - } - - private void OnApplicationQuit() - { - socketConnection.Close(); - } - - /// - /// Setup socket connection. - /// - private void ConnectToTcpServer() - { - try - { - clientReceiveThread = new Thread(ListenForData); - clientReceiveThread.IsBackground = true; - clientReceiveThread.Start(); - } - catch (Exception e) - { - Debug.Log("On client connect exception " + e); - } - } - - private void HandleMessage(string s) // this parses messages from the server, and adds them to a queue to be processed on the main thread - { - /* - // Debug.Log("Received: " + s); - Message m = new Message(); - string[] sections = s.Split(':'); - if (sections.Length <= 0) return; - - int type = int.Parse(sections[0]); - - switch (type) - { - case 0: // logged in message - { - if (sections.Length > 1) - { - m.type = type; - m.sender = int.Parse(sections[1]); - m.text = ""; - AddMessage(m); - } - - break; - } - case 1: // room info message - { - break; - } - case 2: // joined room message - { - if (sections.Length > 2) - { - m.type = 2; - int user_id = int.Parse(sections[1]); - m.sender = user_id; - string new_room = sections[2]; - m.text = new_room; - - AddMessage(m); - } - - break; - } - case 3: // text message - { - if (sections.Length > 2) - { - m.type = 3; - m.sender = int.Parse(sections[1]); - m.text = sections[2]; - AddMessage(m); - } - - break; - } - case 4: // change master client - { - if (sections.Length > 1) - { - m.type = 4; - m.sender = int.Parse(sections[1]); - AddMessage(m); - } - - break; - } - }*/ - } - - /// - /// Runs in background clientReceiveThread; Listens for incomming data. - /// - /// - private byte[] ReadExact(NetworkStream stream, int N) - { - byte[] toReturn = new byte[N]; - - int numRead = 0; - int numLeft = N; - while (numLeft > 0) - { - numRead += stream.Read(toReturn, numRead, numLeft); - numLeft = N - numRead; - } - return toReturn; - } - - private uint GetUintFromBytes(byte[] bytes) - { - if (BitConverter.IsLittleEndian) - { - return BitConverter.ToUInt32(bytes.Reverse().ToArray(),0); - } - else - { - return BitConverter.ToUInt32(bytes, 0); - } - } - private void ListenForData() - { - connected = true; - try - { - socketConnection = new TcpClient(host, port); - socketConnection.NoDelay = true; - byte[] bytes = new byte[1024]; - - Login("Kyle", "Johnsen"); - //Join("MyRoom"); - //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); - //FormGroup("close", new List { 1 }); - //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); - while (true) - { - - // Get a stream object for reading - using NetworkStream stream = socketConnection.GetStream(); - int length; - - //read a byte - byte type = (byte)stream.ReadByte(); - - if(type == 0) - { - //read the id (this is my network id) - - Message m = new Message(); - m.sender = (int)GetUintFromBytes(ReadExact(stream, 4)); - m.data = new byte[0]; //no data - m.type = 0; - AddMessage(m); - - } - else if(type == 1) - { - //rooms message - }else if(type == 2) - { - //join message - }else if(type == 3) - { - //message from client over tcp - } - - // Read incomming stream into byte arrary. - while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) - { - Debug.Log("read " + length + " bytes!"); - string t = ""; - for(int i = 0; i < length; i++) - { - t = t + "," + bytes[i]; - } - Debug.Log(t); - //byte[] incommingData = new byte[length]; - //Array.Copy(bytes, 0, incommingData, 0, length); - //// Convert byte array to string message. - //string serverMessage = Encoding.ASCII.GetString(incommingData); - //string[] sections = serverMessage.Split('\n'); - //if (sections.Length > 1) - //{ - // lock (receivedMessages) - // { - // for (int i = 0; i < sections.Length - 1; i++) - // { - // if (i == 0) - // { - // HandleMessage(partialMessage + sections[0]); - // partialMessage = ""; - // } - // else - // { - // HandleMessage(sections[i]); - // } - // } - // } - //} - - //partialMessage = partialMessage + sections[sections.Length - 1]; - } - } - } - - - catch (Exception socketException) - { - Debug.Log("Socket exception: " + socketException); - } - - connected = false; - } - - private void ListenForDataUDP() - { - //I don't yet have a UDP connection - try - { - IPAddress[] addresses = Dns.GetHostAddresses(host); - Debug.Assert(addresses.Length > 0); - RemoteEndPoint = new IPEndPoint(addresses[0], port); - - - udpSocket = new Socket(AddressFamily.InterNetwork, - SocketType.Dgram, ProtocolType.Udp); - - - udpConnected = false; - byte[] buffer = new byte[1024]; - while (true) - { - buffer[0] = 0; - Array.Copy(get_be_bytes((uint)userid), 0, buffer, 1, 4); - udpSocket.SendTo(buffer, 5, SocketFlags.None, RemoteEndPoint); - - if (udpSocket.Available == 0) - { - Thread.Sleep(100); - Debug.Log("Waiting for UDP response"); - } - else - { - break; - } - } - - udpConnected = true; - while (true) - { - int numReceived = udpSocket.Receive(buffer); - - - if (buffer[0] == 0) - { - Debug.Log("UDP connected"); - }else if (buffer[0] == 3) - { - //we should get the sender address - byte[] senderBytes = new byte[4]; - Array.Copy(buffer, 1, senderBytes, 0, 4); - uint senderId = GetUintFromBytes(senderBytes); - byte[] messageBytes = new byte[numReceived - 5]; - Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); - Message m = new Message(); - m.sender = (int)senderId; - m.data = messageBytes; - m.type = MessageReceiveType.MESSAGE; - } - - - - - } - } - catch (Exception socketException) - { - Debug.Log("Socket exception: " + socketException); - } - } - - private static void SendUdpMessage(string message) - { - if (instance.udpSocket == null || !instance.udpConnected) - { - return; - } - - byte[] data = Encoding.UTF8.GetBytes(message); - //Debug.Log("Attempting to send: " + message); - instance.udpSocket.SendTo(data, data.Length, SocketFlags.None, instance.RemoteEndPoint); - } - - /// - /// Send message to server using socket connection. - /// - private static void SendNetworkMessage(byte[] message) - { - // Debug.Log("Sent: " + clientMessage); - if (instance.socketConnection == null) - { - return; - } - - try - { - // Get a stream object for writing. - NetworkStream stream = instance.socketConnection.GetStream(); - if (stream.CanWrite) - { - - stream.Write(message,0,message.Length); - } - } - catch (SocketException socketException) - { - Debug.Log("Socket exception: " + socketException); - } - } - - /// - /// Connects to the server with a username - /// - /// - public static byte[] get_be_bytes(uint n) - { - return BitConverter.GetBytes(n).Reverse().ToArray(); - } - public static void Login(string username, string password) - { - - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - byte[] uB = Encoding.UTF8.GetBytes(username); - byte[] pB = Encoding.UTF8.GetBytes(password); - writer.Write((byte)MessageSendType.LOGIN); - writer.Write((byte)uB.Length); - writer.Write(uB); - writer.Write((byte)pB.Length); - writer.Write(pB); - - SendNetworkMessage(stream.ToArray()); - - - } - - /// - /// Joins a room by name - /// - /// The name of the room to join - public static void Join(string roomname) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - byte[] R = Encoding.UTF8.GetBytes(roomname); - writer.Write((byte)MessageSendType.JOIN_ROOM); - writer.Write((byte)R.Length); - writer.Write(R); - SendNetworkMessage(stream.ToArray()); - - - - - } - - public static void FormGroup(string groupname, List client_ids) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - byte[] R = Encoding.UTF8.GetBytes(groupname); - writer.Write((byte)MessageSendType.FORM_GROUP); - writer.Write((byte)R.Length); - writer.Write(R); - writer.Write(get_be_bytes((uint)client_ids.Count*4)); - for(int i = 0; i < client_ids.Count; i++) - { - writer.Write(get_be_bytes(client_ids[i])); - } - SendNetworkMessage(stream.ToArray()); - } - - /// - /// Leaves a room if we're in one - /// - public static void Leave() - { - //if (InRoom) SendNetworkMessage("2:-1"); - } - - public static void SendTo(MessageSendType type, byte[] message, bool reliable = true) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - writer.Write((byte)3); - writer.Write(get_be_bytes((uint)message.Length)); - writer.Write(message); - - if (reliable) - { - SendNetworkMessage(stream.ToArray()); - - //SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); - } - } - - public static void SendTo(MessageSendType type, string message, bool reliable = true) - { - if (reliable) - { - //SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); - } - } - - public static void SendToGroup(string group, byte[] message, bool reliable = true) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - writer.Write((byte)MessageSendType.MESSAGE_GROUP); - writer.Write(get_be_bytes((uint)message.Length)); - writer.Write(message); - writer.Write((byte)group.Length); - writer.Write(Encoding.UTF8.GetBytes(group)); - - if (reliable) - { - SendNetworkMessage(stream.ToArray()); - - //SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - //SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); - } - } - - public static void SendToGroup(string group, string message, bool reliable = true) - { - if (reliable) - { - //SendNetworkMessage("4:" + group + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":4:" + group + ":" + message); - } - } - - /// - /// changes the designated group that sendto(4) will go to - /// - public static void SetupMessageGroup(string groupName, List userIds) - { - if (userIds.Count > 0) - { - instance.groups[groupName] = userIds.ToList(); - } - - //SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); - } - - - public static NetworkObject InstantiateNetworkObject(string prefabName) - { - VelNetPlayer localPlayer = LocalPlayer; - NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); - if (prefab == null) - { - Debug.LogError("Couldn't find a prefab with that name: " + prefabName); - return null; - } - - string networkId = localPlayer.userid + "-" + localPlayer.lastObjectId++; - if (instance.objects.ContainsKey(networkId)) - { - Debug.LogError("Can't instantiate object. Obj with that network ID was already instantiated.", instance.objects[networkId]); - return null; - } - NetworkObject newObject = Instantiate(prefab); - newObject.networkId = networkId; - newObject.prefabName = prefabName; - newObject.owner = localPlayer; - instance.objects.Add(newObject.networkId, newObject); - - // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendTo(MessageSendType.MESSAGE_OTHERS, "7," + newObject.networkId + "," + prefabName); - - return newObject; - } - - public static void SomebodyInstantiatedNetworkObject(string networkId, string prefabName, VelNetPlayer owner) - { - NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); - if (prefab == null) return; - NetworkObject newObject = Instantiate(prefab); - newObject.networkId = networkId; - newObject.prefabName = prefabName; - newObject.owner = owner; - instance.objects.Add(newObject.networkId, newObject); - } - - public static void NetworkDestroy(NetworkObject obj) - { - NetworkDestroy(obj.networkId); - } - - public static void NetworkDestroy(string networkId) - { - if (!instance.objects.ContainsKey(networkId)) return; - NetworkObject obj = instance.objects[networkId]; - if (obj == null) - { - instance.objects.Remove(networkId); - return; - } - if (obj.isSceneObject) - { - instance.deletedSceneObjects.Add(networkId); - } - - Destroy(obj.gameObject); - instance.objects.Remove(networkId); - } - - /// - /// Takes local ownership of an object by id. - /// - /// Network ID of the object to transfer - /// True if successfully transferred, False if transfer message not sent - public static bool TakeOwnership(string networkId) - { - // local player must exist - if (LocalPlayer == null) return false; - - // obj must exist - if (!instance.objects.ContainsKey(networkId)) return false; - - // if the ownership is locked, fail - if (instance.objects[networkId].ownershipLocked) return false; - - // immediately successful - instance.objects[networkId].owner = LocalPlayer; - - // must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome. - SendTo(MessageSendType.MESSAGE_ALL, "6," + networkId); - - return true; - } - } -} \ No newline at end of file diff --git a/Runtime/VelNetBinaryManager.cs.meta b/Runtime/VelNetBinaryManager.cs.meta deleted file mode 100644 index 014ff23..0000000 --- a/Runtime/VelNetBinaryManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 233344de094f11341bdb834d564708dc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 71ea086..546fe36 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -8,18 +8,24 @@ using System.Threading; using UnityEngine; using System.Net; using UnityEngine.SceneManagement; +using System.Runtime.Serialization.Formatters.Binary; +using System.Runtime.Serialization; +using System.IO; namespace VelNet { [AddComponentMenu("VelNet/VelNet Manager")] public class VelNetManager : MonoBehaviour { - public enum MessageType + public enum MessageSendType { - OTHERS = 0, - ALL = 1, - OTHERS_ORDERED = 2, - ALL_ORDERED = 3 + MESSAGE_LOGIN = 0, + MESSAGE_GETROOMS = 1, + MESSAGE_JOINROOM = 2, + MESSAGE_OTHERS = 3, + MESSAGE_ALL = 4, + MESSAGE_GROUP = 5, + MESSAGE_SETGROUP = 6 }; public string host; @@ -62,7 +68,6 @@ namespace VelNet public static Action OnPlayerLeft; public static Action OnConnectedToServer; - public static Action MessageReceived; public static Action LoggedIn; public static Action RoomsReceived; @@ -87,12 +92,40 @@ namespace VelNet public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; + //this is for sending udp packets + static byte[] toSend = new byte[1024]; + // Use this for initialization - public class Message + public abstract class Message { - public int type; - public string text; - public int sender; + + } + public class ListedRoom + { + public string name; + public int numUsers; + } + public class LoginMessage: Message + { + public int userId; + } + public class RoomsMessage: Message + { + public List rooms; + } + public class JoinMessage: Message + { + public int userId; + public string room; + } + public class DataMessage: Message + { + public int senderId; + public byte[] data; + } + public class ChangeMasterMessage: Message + { + public int masterId; } public readonly List receivedMessages = new List(); @@ -109,7 +142,7 @@ namespace VelNet SceneManager.sceneLoaded += (scene, mode) => { // add all local network objects - sceneObjects = FindObjectsOfType().Where(o=>o.isSceneObject).ToArray(); + sceneObjects = FindObjectsOfType().Where(o => o.isSceneObject).ToArray(); }; } @@ -146,200 +179,206 @@ namespace VelNet //the main thread, which can do Unity stuff foreach (Message m in receivedMessages) { - switch (m.type) + switch (m) { - // when you join the server - case 0: - userid = m.sender; - Debug.Log("joined server"); - - try + case LoginMessage lm: { - LoggedIn?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - - //start the udp thread - clientReceiveThreadUDP = new Thread(ListenForDataUDP); - clientReceiveThreadUDP.IsBackground = true; - clientReceiveThreadUDP.Start(); - break; - // if this message is for me, that means I joined a new room... - case 2 when userid == m.sender: - { - string oldRoom = LocalPlayer?.room; - - // we clear the list, but will recreate as we get messages from people in our room - players.Clear(); - masterPlayer = null; - - if (m.text != "") - { - VelNetPlayer player = new VelNetPlayer - { - isLocal = true, - userid = m.sender, - room = m.text - }; - - players.Add(userid, player); - if (m.text != "") - { - try - { - OnJoinedRoom?.Invoke(m.text); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - } - // we just left a room - else - { - // delete all networkobjects that aren't sceneobjects or are null now - objects - .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) - .Select(o => o.Key) - .ToList().ForEach(NetworkDestroy); - - // then remove references to the ones that are left - objects.Clear(); - - // empty all the groups - foreach (string group in instance.groups.Keys) - { - SetupMessageGroup(group, new List()); - } - - instance.groups.Clear(); + userid = lm.userId; + Debug.Log("joined server " + userid); try { - OnLeftRoom?.Invoke(oldRoom); + LoggedIn?.Invoke(); } // prevent errors in subscribers from breaking our code catch (Exception e) { Debug.LogError(e); } + + //start the udp thread + clientReceiveThreadUDP = new Thread(ListenForDataUDP); + clientReceiveThreadUDP.IsBackground = true; + clientReceiveThreadUDP.Start(); + + break; } + case RoomsMessage rm: { + Debug.Log("Got Rooms Message"); + + break; + } + case JoinMessage jm: { + if(userid == jm.userId) //this is us + { + string oldRoom = LocalPlayer?.room; - break; - } - // not for me, a player is joining or leaving - case 2: - { - VelNetPlayer me = players[userid]; + // we clear the list, but will recreate as we get messages from people in our room + players.Clear(); + masterPlayer = null; - if (me.room != m.text) - { - // we got a left message, kill it - // change ownership of all objects to master - List deleteObjects = new List(); + if (jm.room != "") + { + VelNetPlayer player = new VelNetPlayer + { + isLocal = true, + userid = jm.userId, + room = jm.room + }; + + players.Add(userid, player); + + try + { + OnJoinedRoom?.Invoke(jm.room); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + + } + // we just left a room + else + { + // delete all networkobjects that aren't sceneobjects or are null now + objects + .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) + .Select(o => o.Key) + .ToList().ForEach(NetworkDestroy); + + // then remove references to the ones that are left + objects.Clear(); + + // empty all the groups + foreach (string group in instance.groups.Keys) + { + SetupMessageGroup(group, new List()); + } + + instance.groups.Clear(); + + try + { + OnLeftRoom?.Invoke(oldRoom); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + } + else + { + VelNetPlayer me = players[userid]; + + if (me.room != jm.room) + { + // we got a left message, kill it + // change ownership of all objects to master + List deleteObjects = new List(); + foreach (KeyValuePair kvp in objects) + { + if (kvp.Value.owner == players[jm.userId]) // the owner is the player that left + { + // if this object has locked ownership, delete it + if (kvp.Value.ownershipLocked) + { + deleteObjects.Add(kvp.Value.networkId); + } + // I'm the local master player, so can take ownership immediately + else if (me.isLocal && me == masterPlayer) + { + TakeOwnership(kvp.Key); + } + // the master player left, so everyone should set the owner null (we should get a new master shortly) + else if (players[jm.userId] == masterPlayer) + { + kvp.Value.owner = null; + } + } + } + + // TODO this may check for ownership in the future. We don't need ownership here + deleteObjects.ForEach(NetworkDestroy); + + players.Remove(jm.userId); + } + else + { + // we got a join message, create it + VelNetPlayer player = new VelNetPlayer + { + isLocal = false, + room = jm.room, + userid = jm.userId + }; + players.Add(jm.userId, player); + try + { + OnPlayerJoined?.Invoke(player); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + } + break; + + } + case DataMessage dm: { + if (players.ContainsKey(dm.senderId)) + { + players[dm.senderId]?.HandleMessage(dm); //todo + } + else + { + Debug.LogError("Received message from player that doesn't exist "); + } + + break; + + + } + case ChangeMasterMessage cm: { + + if (masterPlayer == null) + { + masterPlayer = players[cm.masterId]; + + // no master player yet, add the scene objects + + for (int i = 0; i < sceneObjects.Length; i++) + { + sceneObjects[i].networkId = -1 + "-" + i; + sceneObjects[i].owner = masterPlayer; + sceneObjects[i].isSceneObject = true; // needed for special handling when deleted + objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + } + } + else + { + masterPlayer = players[cm.masterId]; + } + + masterPlayer.SetAsMasterPlayer(); + + // master player should take over any objects that do not have an owner foreach (KeyValuePair kvp in objects) { - if (kvp.Value.owner == players[m.sender]) // the owner is the player that left - { - // if this object has locked ownership, delete it - if (kvp.Value.ownershipLocked) - { - deleteObjects.Add(kvp.Value.networkId); - } - // I'm the local master player, so can take ownership immediately - else if (me.isLocal && me == masterPlayer) - { - TakeOwnership(kvp.Key); - } - // the master player left, so everyone should set the owner null (we should get a new master shortly) - else if (players[m.sender] == masterPlayer) - { - kvp.Value.owner = null; - } - } + kvp.Value.owner ??= masterPlayer; } - // TODO this may check for ownership in the future. We don't need ownership here - deleteObjects.ForEach(NetworkDestroy); - - players.Remove(m.sender); + break; + } - else - { - // we got a join message, create it - VelNetPlayer player = new VelNetPlayer - { - isLocal = false, - room = m.text, - userid = m.sender - }; - players.Add(m.sender, player); - try - { - OnPlayerJoined?.Invoke(player); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - break; - } - // generic message - case 3: - if (players.ContainsKey(m.sender)) - { - players[m.sender]?.HandleMessage(m); - } - else - { - Debug.LogError("Received message from player that doesn't exist: " + m.text); - } - - break; - // change master player (this should only happen when the first player joins or if the master player leaves) - case 4: - { - if (masterPlayer == null) - { - masterPlayer = players[m.sender]; - - // no master player yet, add the scene objects - - for (int i = 0; i < sceneObjects.Length; i++) - { - sceneObjects[i].networkId = -1 + "-" + i; - sceneObjects[i].owner = masterPlayer; - sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - objects.Add(sceneObjects[i].networkId, sceneObjects[i]); - } - } - else - { - masterPlayer = players[m.sender]; - } - - masterPlayer.SetAsMasterPlayer(); - - // master player should take over any objects that do not have an owner - foreach (KeyValuePair kvp in objects) - { - kvp.Value.owner ??= masterPlayer; - } - - break; - } } - - MessageReceived?.Invoke(m); + + //MessageReceived?.Invoke(m); } receivedMessages.Clear(); @@ -368,77 +407,36 @@ namespace VelNet } } - private void HandleMessage(string s) // this parses messages from the server, and adds them to a queue to be processed on the main thread - { - // Debug.Log("Received: " + s); - Message m = new Message(); - string[] sections = s.Split(':'); - if (sections.Length <= 0) return; - - int type = int.Parse(sections[0]); - - switch (type) - { - case 0: // logged in message - { - if (sections.Length > 1) - { - m.type = type; - m.sender = int.Parse(sections[1]); - m.text = ""; - AddMessage(m); - } - - break; - } - case 1: // room info message - { - break; - } - case 2: // joined room message - { - if (sections.Length > 2) - { - m.type = 2; - int user_id = int.Parse(sections[1]); - m.sender = user_id; - string new_room = sections[2]; - m.text = new_room; - - AddMessage(m); - } - - break; - } - case 3: // text message - { - if (sections.Length > 2) - { - m.type = 3; - m.sender = int.Parse(sections[1]); - m.text = sections[2]; - AddMessage(m); - } - - break; - } - case 4: // change master client - { - if (sections.Length > 1) - { - m.type = 4; - m.sender = int.Parse(sections[1]); - AddMessage(m); - } - - break; - } - } - } /// /// Runs in background clientReceiveThread; Listens for incomming data. /// + /// + private byte[] ReadExact(NetworkStream stream, int N) + { + byte[] toReturn = new byte[N]; + + int numRead = 0; + int numLeft = N; + while (numLeft > 0) + { + numRead += stream.Read(toReturn, numRead, numLeft); + numLeft = N - numRead; + } + return toReturn; + } + + private int GetIntFromBytes(byte[] bytes) + { + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt32(bytes.Reverse().ToArray(),0); + } + else + { + return BitConverter.ToInt32(bytes, 0); + } + } private void ListenForData() { connected = true; @@ -446,41 +444,65 @@ namespace VelNet { socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; - byte[] bytes = new byte[1024]; - string partialMessage = ""; + NetworkStream stream = socketConnection.GetStream(); + //Join("MyRoom"); + //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); + //FormGroup("close", new List { 1 }); + //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); while (true) { + // Get a stream object for reading - using NetworkStream stream = socketConnection.GetStream(); - int length; - // Read incomming stream into byte arrary. - while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) - { - byte[] incommingData = new byte[length]; - Array.Copy(bytes, 0, incommingData, 0, length); - // Convert byte array to string message. - string serverMessage = Encoding.ASCII.GetString(incommingData); - string[] sections = serverMessage.Split('\n'); - if (sections.Length > 1) - { - lock (receivedMessages) - { - for (int i = 0; i < sections.Length - 1; i++) - { - if (i == 0) - { - HandleMessage(partialMessage + sections[0]); - partialMessage = ""; - } - else - { - HandleMessage(sections[i]); - } - } - } - } + - partialMessage = partialMessage + sections[sections.Length - 1]; + //read a byte + byte type = (byte)stream.ReadByte(); + + if (type == 0) //login + { + LoginMessage m = new LoginMessage(); + m.userId = GetIntFromBytes(ReadExact(stream, 4)); //not really the sender... + AddMessage(m); + } + else if(type == 1) //rooms + { + RoomsMessage m = new RoomsMessage(); + m.rooms = new List(); + int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload + byte[] utf8data = ReadExact(stream, N); + string roomMessage = Encoding.UTF8.GetString(utf8data); + string[] sections = roomMessage.Split(','); + foreach(string s in sections) + { + string[] pieces = s.Split(':'); + ListedRoom lr = new ListedRoom(); + lr.name = pieces[0]; + lr.numUsers = int.Parse(pieces[1]); + m.rooms.Add(lr); + } + AddMessage(m); + } + else if(type == 2) //joined + { + JoinMessage m = new JoinMessage(); + m.userId = GetIntFromBytes(ReadExact(stream, 4)); + int N = stream.ReadByte(); + byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 + m.room = Encoding.UTF8.GetString(utf8data); + AddMessage(m); + }else if(type == 3) //data + { + DataMessage m = new DataMessage(); + m.senderId = GetIntFromBytes(ReadExact(stream, 4)); + int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload + m.data = ReadExact(stream, N); //the message + AddMessage(m); + } + else if(type == 4) //new master + { + ChangeMasterMessage m = new ChangeMasterMessage(); + m.masterId = (int)GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master + AddMessage(m); } } } @@ -512,9 +534,9 @@ namespace VelNet byte[] buffer = new byte[1024]; while (true) { - string welcome = userid + ":0:Hello"; - byte[] data = Encoding.ASCII.GetBytes(welcome); - udpSocket.SendTo(data, data.Length, SocketFlags.None, RemoteEndPoint); + buffer[0] = 0; + Array.Copy(get_be_bytes(userid), 0, buffer, 1, 4); + udpSocket.SendTo(buffer, 5, SocketFlags.None, RemoteEndPoint); if (udpSocket.Available == 0) { @@ -531,18 +553,20 @@ namespace VelNet while (true) { int numReceived = udpSocket.Receive(buffer); - - string message = Encoding.UTF8.GetString(buffer, 0, numReceived); - - string[] sections = message.Split(':'); - if (sections[0] == "0") + if (buffer[0] == 0) { Debug.Log("UDP connected"); - } - - if (sections[0] == "3") + }else if (buffer[0] == 3) { - HandleMessage(message); + DataMessage m = new DataMessage(); + //we should get the sender address + byte[] senderBytes = new byte[4]; + Array.Copy(buffer, 1, senderBytes, 0, 4); + m.senderId = GetIntFromBytes(senderBytes); + byte[] messageBytes = new byte[numReceived - 5]; + Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); + m.data = messageBytes; + AddMessage(m); } } } @@ -552,22 +576,20 @@ namespace VelNet } } - private static void SendUdpMessage(string message) + private static void SendUdpMessage(byte[] message, int N) { if (instance.udpSocket == null || !instance.udpConnected) { return; } - byte[] data = Encoding.UTF8.GetBytes(message); - //Debug.Log("Attempting to send: " + message); - instance.udpSocket.SendTo(data, data.Length, SocketFlags.None, instance.RemoteEndPoint); + instance.udpSocket.SendTo(message, N, SocketFlags.None, instance.RemoteEndPoint); } /// /// Send message to server using socket connection. /// - private static void SendNetworkMessage(string clientMessage) + private static void SendTcpMessage(byte[] message) //we can assume that this message is already formatted, so we just send it { // Debug.Log("Sent: " + clientMessage); if (instance.socketConnection == null) @@ -581,11 +603,8 @@ namespace VelNet NetworkStream stream = instance.socketConnection.GetStream(); if (stream.CanWrite) { - // Convert string message to byte array. - clientMessage += "\n"; // append a new line to delineate the message - byte[] clientMessageAsByteArray = Encoding.ASCII.GetBytes(clientMessage); - // Write byte array to socketConnection stream. - stream.Write(clientMessageAsByteArray, 0, clientMessageAsByteArray.Length); + + stream.Write(message,0,message.Length); } } catch (SocketException socketException) @@ -597,9 +616,28 @@ namespace VelNet /// /// Connects to the server with a username /// + /// + public static byte[] get_be_bytes(int n) + { + return BitConverter.GetBytes(n).Reverse().ToArray(); + } public static void Login(string username, string password) { - SendNetworkMessage("0:" + username + ":" + password); + + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + byte[] uB = Encoding.UTF8.GetBytes(username); + byte[] pB = Encoding.UTF8.GetBytes(password); + writer.Write((byte)0); + writer.Write((byte)uB.Length); + writer.Write(uB); + writer.Write((byte)pB.Length); + writer.Write(pB); + + SendTcpMessage(stream.ToArray()); + + } /// @@ -608,52 +646,104 @@ namespace VelNet /// The name of the room to join public static void Join(string roomname) { - SendNetworkMessage("2:" + roomname); + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + byte[] R = Encoding.UTF8.GetBytes(roomname); + writer.Write((byte)2); + writer.Write((byte)R.Length); + writer.Write(R); + SendTcpMessage(stream.ToArray()); + + + + } + + /// /// Leaves a room if we're in one /// public static void Leave() { - if (InRoom) SendNetworkMessage("2:-1"); - } - - public static void SendTo(MessageType type, string message, bool reliable = true) - { - if (reliable) + if (InRoom) { - SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); + Join(""); //super secret way to leave } } - public static void SendToGroup(string group, string message, bool reliable = true) + public static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true) { + byte sendType = (byte)(include_self ? MessageSendType.MESSAGE_ALL : MessageSendType.MESSAGE_OTHERS); if (reliable) { - SendNetworkMessage("4:" + group + ":" + message); + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(sendType); + writer.Write(get_be_bytes(message.Length)); + writer.Write(message); + SendTcpMessage(stream.ToArray()); } else { - SendUdpMessage(instance.userid + ":4:" + group + ":" + message); + //udp message needs the type + toSend[0] = sendType; //we don't + Array.Copy(get_be_bytes(instance.userid), 0, toSend, 1, 4); + Array.Copy(message, 0, toSend, 5, message.Length); + SendUdpMessage(toSend,message.Length+5); //shouldn't be over 1024... + } + } + + + public static void SendToGroup(string group, byte[] message, bool reliable = true) + { + byte[] utf8bytes = Encoding.UTF8.GetBytes(group); + if (reliable) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + writer.Write((byte)MessageSendType.MESSAGE_GROUP); + writer.Write(get_be_bytes(message.Length)); + writer.Write(message); + writer.Write((byte)utf8bytes.Length); + writer.Write(utf8bytes); + SendTcpMessage(stream.ToArray()); + } + else + { + toSend[0] = (byte)MessageSendType.MESSAGE_GROUP; + Array.Copy(get_be_bytes(instance.userid), 0, toSend, 1, 4); + //also need to send the group + toSend[5] = (byte)utf8bytes.Length; + Array.Copy(utf8bytes, 0, toSend, 6, utf8bytes.Length); + Array.Copy(message, 0, toSend, 6+utf8bytes.Length, message.Length); + SendUdpMessage(toSend, 6 + utf8bytes.Length + message.Length); } } /// /// changes the designated group that sendto(4) will go to /// - public static void SetupMessageGroup(string groupName, List userIds) + public static void SetupMessageGroup(string groupname, List client_ids) { - if (userIds.Count > 0) + if (client_ids.Count > 0) { - instance.groups[groupName] = userIds.ToList(); + instance.groups[groupname] = client_ids.ToList(); } - SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + byte[] R = Encoding.UTF8.GetBytes(groupname); + writer.Write((byte)6); + writer.Write((byte)R.Length); + writer.Write(R); + writer.Write(get_be_bytes(client_ids.Count * 4)); + for (int i = 0; i < client_ids.Count; i++) + { + writer.Write(get_be_bytes(client_ids[i])); + } + SendTcpMessage(stream.ToArray()); } @@ -680,7 +770,7 @@ namespace VelNet instance.objects.Add(newObject.networkId, newObject); // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendTo(MessageType.OTHERS, "7," + newObject.networkId + "," + prefabName); + SendToRoom(Encoding.UTF8.GetBytes("7," + newObject.networkId + "," + prefabName),false,true); return newObject; } @@ -718,7 +808,7 @@ namespace VelNet Destroy(obj.gameObject); instance.objects.Remove(networkId); } - + /// /// Takes local ownership of an object by id. /// @@ -728,19 +818,19 @@ namespace VelNet { // local player must exist if (LocalPlayer == null) return false; - + // obj must exist if (!instance.objects.ContainsKey(networkId)) return false; // if the ownership is locked, fail if (instance.objects[networkId].ownershipLocked) return false; - + // immediately successful instance.objects[networkId].owner = LocalPlayer; // must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome. - SendTo(MessageType.ALL_ORDERED, "6," + networkId); - + SendToRoom(Encoding.UTF8.GetBytes("6," + networkId)); + return true; } } diff --git a/Runtime/VelNetManager.cs.meta b/Runtime/VelNetManager.cs.meta index 56f31aa..014ff23 100644 --- a/Runtime/VelNetManager.cs.meta +++ b/Runtime/VelNetManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 03a4d4e1a7fd74c7ab2eccca4ce168db +guid: 233344de094f11341bdb834d564708dc MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs index fae5623..c31ab89 100644 --- a/Runtime/VelNetPlayer.cs +++ b/Runtime/VelNetPlayer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System; +using System.Text; namespace VelNet { @@ -41,7 +42,7 @@ namespace VelNet { if (kvp.Value.owner == this && kvp.Value.prefabName != "") { - VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "7," + kvp.Value.networkId + "," + kvp.Value.prefabName); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("7," + kvp.Value.networkId + "," + kvp.Value.prefabName),false,true); } } @@ -56,12 +57,14 @@ namespace VelNet /// /// These are generally things that come from the "owner" and should be enacted locally, where appropriate /// - public void HandleMessage(VelNetManager.Message m) + public void HandleMessage(VelNetManager.DataMessage m) { - //we need to parse the message + //for now, we can just convert to text...because + + string text = Encoding.UTF8.GetString(m.data); //types of messages - string[] messages = m.text.Split(';'); //messages are split by ; + string[] messages = text.Split(';'); //messages are split by ; foreach (string s in messages) { //individual message parameters separated by comma @@ -138,17 +141,20 @@ namespace VelNet public void SendGroupMessage(NetworkObject obj, string group, string identifier, byte[] data, bool reliable = true) { - VelNetManager.SendToGroup(group, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); + string message = "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data); + VelNetManager.SendToGroup(group, Encoding.UTF8.GetBytes(message), reliable); } public void SendMessage(NetworkObject obj, string identifier, byte[] data, bool reliable = true) { - VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); + string message = "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes(message), false, reliable); } public void SendSceneUpdate() { - VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects)); + string message = "9," + string.Join(",", manager.deletedSceneObjects); + VelNetManager.SendToRoom( Encoding.UTF8.GetBytes(message)); } [Obsolete("Use VelNetManager.NetworkDestroy() instead.")] @@ -158,7 +164,7 @@ namespace VelNet if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; // send to all, which will make me delete as well - VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "8," + networkId); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("8," + networkId), true, true); } /// True if successful, False if failed to transfer ownership @@ -175,7 +181,7 @@ namespace VelNet manager.objects[networkId].owner = this; // must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome. - VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "6," + networkId); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("6," + networkId),true,true); return true; }