using System; 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.IO; namespace VelNet { /// Used to flag methods as remote-callable. public class VelNetRPC : Attribute { } [AddComponentMenu("VelNet/VelNet Manager")] public class VelNetManager : MonoBehaviour { public enum MessageReceivedType { LOGGED_IN = 0, ROOM_LIST = 1, PLAYER_JOINED = 2, DATA_MESSAGE = 3, MASTER_MESSAGE = 4, YOU_JOINED = 5, PLAYER_LEFT = 6, YOU_LEFT = 7, ROOM_DATA = 8 } public enum MessageSendType { MESSAGE_OTHERS_ORDERED = 7, MESSAGE_ALL_ORDERED = 8, MESSAGE_LOGIN = 0, MESSAGE_GETROOMS = 1, MESSAGE_JOINROOM = 2, MESSAGE_OTHERS = 3, MESSAGE_ALL = 4, MESSAGE_GROUP = 5, MESSAGE_SETGROUP = 6, MESSAGE_GETROOMDATA = 9 }; public enum MessageType : byte { ObjectSync, TakeOwnership, Instantiate, Destroy, DeleteSceneObjects, Custom } public string host; public int port; public static VelNetManager instance; private TcpClient socketConnection; private Socket udpSocket; public bool udpConnected; private IPEndPoint RemoteEndPoint; private Thread clientReceiveThread; private Thread clientReceiveThreadUDP; public int userid = -1; [Tooltip("Sends debug messages about connection and join events")] public bool debugMessages; [Tooltip("Automatically logs in with app name and hash of device id after connecting")] public bool autoLogin = true; [Tooltip("Uses the version number in the login to prevent crosstalk between different app versions")] public bool onlyConnectToSameVersion; public readonly Dictionary players = new Dictionary(); #region Callbacks /// /// 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 /// bool is true when this is a join message for someone that was already in the room when we joined it /// public static Action OnPlayerJoined; /// /// Somebody else just left our room /// public static Action OnPlayerLeft; public static Action OnConnectedToServer; public static Action OnLoggedIn; public static Action RoomsReceived; public static Action RoomDataReceived; public static Action MessageReceived; public static Action CustomMessageReceived; #endregion public bool connected; private bool wasConnected; private double lastConnectionCheck; 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 != null ? instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault() : null; public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; public static string Room => LocalPlayer?.room; public static List Players => instance.players.Values.ToList(); /// /// The player count in this room. /// -1 if not in a room. /// public static int PlayerCount => instance.players.Count; public static bool IsConnected => instance != null && instance.connected && instance.udpConnected; // this is for sending udp packets private static readonly byte[] toSend = new byte[1024]; // Use this for initialization public abstract class Message { } public class ListedRoom { public string name; public int numUsers; public override string ToString() { return "Room Name: " + name + "\tUsers: " + numUsers; } } public class LoginMessage : Message { public int userId; } public class RoomsMessage : Message { public List rooms; public override string ToString() { return string.Join("\n", rooms); } } public class RoomDataMessage : Message { public string room; public readonly List<(int, string)> members = new List<(int, string)>(); public override string ToString() { return room + "\n" + string.Join("\n", members.Select(m => $"{m.Item1}\t{m.Item2}")); } } 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 class YouJoinedMessage : Message { public List playerIds; public string room; } public class PlayerLeftMessage : Message { public int userId; public string room; } public class YouLeftMessage : Message { public string room; } public class ConnectedMessage : Message { } private const int maxUnreadMessages = 1000; private readonly List receivedMessages = new List(); private void Awake() { SceneManager.sceneLoaded += (_, _) => { // add all local network objects sceneObjects = FindObjectsOfType().Where(o => o.isSceneObject).ToArray(); }; } private void OnEnable() { if (instance != null) { VelNetLogger.Error("Multiple NetworkManagers detected! Bad!", this); } instance = this; ConnectToServer(); } private void OnDisable() { DisconnectFromServer(); instance = null; } private void AddMessage(Message m) { bool added = false; lock (receivedMessages) { // this is to avoid backups when headset goes to sleep if (receivedMessages.Count < maxUnreadMessages) { receivedMessages.Add(m); added = true; } } try { if (added) MessageReceived?.Invoke(m); } catch (Exception e) { VelNetLogger.Error(e.ToString()); } } private void Update() { lock (receivedMessages) { //the main thread, which can do Unity stuff foreach (Message m in receivedMessages) { switch (m) { case ConnectedMessage msg: { VelNetLogger.Info("Connected to server."); try { OnConnectedToServer?.Invoke(); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString()); } if (autoLogin) { Login( onlyConnectToSameVersion ? $"{Application.productName}_{Application.version}" : $"{Application.productName}", Hash128.Compute(SystemInfo.deviceUniqueIdentifier).ToString() ); } break; } case LoginMessage lm: { if (userid == lm.userId) { VelNetLogger.Error("Received duplicate login message " + userid); } userid = lm.userId; VelNetLogger.Info("Logged in: " + userid); try { OnLoggedIn?.Invoke(); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } //start the udp thread clientReceiveThreadUDP?.Abort(); clientReceiveThreadUDP = new Thread(ListenForDataUDP); clientReceiveThreadUDP.Start(); break; } case RoomsMessage rm: { VelNetLogger.Info($"Received rooms:\n{rm}"); try { RoomsReceived?.Invoke(rm); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } break; } case RoomDataMessage rdm: { VelNetLogger.Info($"Received room data:\n{rdm}"); try { RoomDataReceived?.Invoke(rdm); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } break; } case YouJoinedMessage jm: { VelNetLogger.Info($"Joined Room: {jm.room} \t ({jm.playerIds.Count} players)"); // we clear the list, but will recreate as we get messages from people in our room players.Clear(); masterPlayer = null; foreach (int playerId in jm.playerIds) { VelNetPlayer player = new VelNetPlayer { room = jm.room, userid = playerId, isLocal = playerId == userid, }; players.Add(player.userid, player); } try { OnJoinedRoom?.Invoke(jm.room); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } foreach (KeyValuePair kvp in players) { if (kvp.Key != userid) { try { OnPlayerJoined?.Invoke(kvp.Value, true); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } } } break; } case YouLeftMessage msg: { LeaveRoomInternal(); break; } case PlayerLeftMessage lm: { VelNetLogger.Info($"Player left: {lm.userId}"); VelNetPlayer me = players[userid]; // 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[lm.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[lm.userId] == masterPlayer) { kvp.Value.owner = null; } } } // TODO this may check for ownership in the future. We don't need ownership here deleteObjects.ForEach(NetworkDestroy); VelNetPlayer leftPlayer = players[lm.userId]; players.Remove(lm.userId); try { OnPlayerLeft?.Invoke(leftPlayer); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } break; } case JoinMessage jm: { VelNetLogger.Info($"Player joined: {jm.userId}"); // 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, false); } // prevent errors in subscribers from breaking our code catch (Exception e) { VelNetLogger.Error(e.ToString(), this); } break; } case DataMessage dm: { if (players.ContainsKey(dm.senderId)) { players[dm.senderId]?.HandleMessage(dm); } else { LocalPlayer?.HandleMessage(dm, true); } break; } case ChangeMasterMessage cm: { VelNetLogger.Info($"Master client changed: {cm.masterId}"); if (masterPlayer == null) { if (players.ContainsKey(cm.masterId)) { masterPlayer = players[cm.masterId]; } else { masterPlayer = players.Aggregate((p1, p2) => p1.Value.userid.CompareTo(p2.Value.userid) > 0 ? p1 : p2).Value; VelNetLogger.Error("Got an invalid master client id from the server. Using fallback."); } // no master player yet, add the scene objects for (int i = 0; i < sceneObjects.Length; i++) { if (sceneObjects[i].sceneNetworkId == 0) { VelNetLogger.Error("Scene Network ID is 0. Make sure to assign one first.", sceneObjects[i]); } sceneObjects[i].networkId = -1 + "-" + sceneObjects[i].sceneNetworkId; sceneObjects[i].owner = masterPlayer; sceneObjects[i].isSceneObject = true; // needed for special handling when deleted try { sceneObjects[i].OwnershipChanged?.Invoke(masterPlayer); } catch (Exception e) { VelNetLogger.Error("Error in event handling.\n" + e); } if (objects.ContainsKey(sceneObjects[i].networkId)) { VelNetLogger.Error($"Duplicate NetworkID: {sceneObjects[i].networkId} {sceneObjects[i].name} {objects[sceneObjects[i].networkId]}"); } else { 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) { kvp.Value.owner ??= masterPlayer; } break; } } //MessageReceived?.Invoke(m); } receivedMessages.Clear(); } // reconnection if (Time.timeAsDouble - lastConnectionCheck > 2) { if (!IsConnected && wasConnected) { VelNetLogger.Info("Reconnecting..."); ConnectToServer(); } lastConnectionCheck = Time.timeAsDouble; } } private void LeaveRoomInternal() { VelNetLogger.Info("Leaving Room"); string oldRoom = LocalPlayer?.room; // delete all NetworkObjects that aren't scene objects 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) { VelNetLogger.Error(e.ToString(), this); } foreach (NetworkObject s in sceneObjects) { s.owner = null; } players.Clear(); masterPlayer = null; } private void OnApplicationQuit() { socketConnection?.Close(); clientReceiveThreadUDP?.Abort(); clientReceiveThread?.Abort(); } /// /// Setup socket connection. /// private static void ConnectToServer() { try { instance.clientReceiveThread = new Thread(instance.ListenForData); instance.clientReceiveThread.Start(); } catch (Exception e) { VelNetLogger.Error("On client connect exception " + e); } } private void DisconnectFromServer() { VelNetLogger.Info("Disconnecting from server..."); string oldRoom = LocalPlayer?.room; // delete all NetworkObjects that aren't scene objects or are null now objects .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) .Select(o => o.Key) .ToList().ForEach(SomebodyDestroyedNetworkObject); // then remove references to the ones that are left objects.Clear(); instance.groups.Clear(); foreach (NetworkObject s in sceneObjects) { s.owner = null; } players.Clear(); masterPlayer = null; connected = false; udpConnected = false; socketConnection?.Close(); clientReceiveThreadUDP?.Abort(); clientReceiveThread?.Abort(); clientReceiveThread = null; socketConnection = null; udpSocket = null; } /// /// Runs in background clientReceiveThread; Listens for incoming data. /// private static byte[] ReadExact(Stream 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 static int GetIntFromBytes(byte[] bytes) { return BitConverter.ToInt32(BitConverter.IsLittleEndian ? bytes.Reverse().ToArray() : bytes, 0); } private void ListenForData() { connected = true; try { socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; // Get a stream object for reading NetworkStream stream = socketConnection.GetStream(); using BinaryReader reader = new BinaryReader(stream); //now we are connected, so add a message to the queue AddMessage(new ConnectedMessage()); while (socketConnection.Connected) { //read a byte int b = stream.ReadByte(); MessageReceivedType type = (MessageReceivedType)b; switch (type) { //login case MessageReceivedType.LOGGED_IN: { AddMessage(new LoginMessage { // not really the sender... userId = GetIntFromBytes(ReadExact(stream, 4)) }); break; } //rooms case MessageReceivedType.ROOM_LIST: { 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(':'); if (pieces.Length == 2) { m.rooms.Add(new ListedRoom { name = pieces[0], numUsers = int.Parse(pieces[1]) }); } } AddMessage(m); break; } case MessageReceivedType.ROOM_DATA: { RoomDataMessage rdm = new RoomDataMessage(); int N = stream.ReadByte(); byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 string roomname = Encoding.UTF8.GetString(utf8data); N = GetIntFromBytes(ReadExact(stream, 4)); //the number of client datas to read rdm.room = roomname; for (int i = 0; i < N; i++) { // client id + short string int client_id = GetIntFromBytes(ReadExact(stream, 4)); int s = stream.ReadByte(); //size of string utf8data = ReadExact(stream, s); //the username string username = Encoding.UTF8.GetString(utf8data); rdm.members.Add((client_id, username)); } AddMessage(rdm); break; } //joined case MessageReceivedType.PLAYER_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); break; } //data case MessageReceivedType.DATA_MESSAGE: { 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); break; } // new master case MessageReceivedType.MASTER_MESSAGE: { ChangeMasterMessage m = new ChangeMasterMessage(); m.masterId = GetIntFromBytes(ReadExact(stream, 4)); // sender is the new master AddMessage(m); break; } case MessageReceivedType.YOU_JOINED: { YouJoinedMessage m = new YouJoinedMessage(); int N = GetIntFromBytes(ReadExact(stream, 4)); m.playerIds = new List(); for (int i = 0; i < N; i++) { m.playerIds.Add(GetIntFromBytes(ReadExact(stream, 4))); } N = stream.ReadByte(); byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 m.room = Encoding.UTF8.GetString(utf8data); AddMessage(m); break; } case MessageReceivedType.PLAYER_LEFT: { PlayerLeftMessage m = new PlayerLeftMessage(); 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); break; } case MessageReceivedType.YOU_LEFT: { YouLeftMessage m = new YouLeftMessage(); int N = stream.ReadByte(); byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 m.room = Encoding.UTF8.GetString(utf8data); AddMessage(m); break; } } } } catch (ThreadAbortException) { // pass } catch (SocketException socketException) { VelNetLogger.Error("Socket exception: " + socketException); } catch (Exception ex) { VelNetLogger.Error(ex.ToString()); } 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(userid), 0, buffer, 1, 4); udpSocket.SendTo(buffer, 5, SocketFlags.None, RemoteEndPoint); if (udpSocket.Available == 0) { Thread.Sleep(100); VelNetLogger.Info("Waiting for UDP response"); } else { break; } } udpConnected = true; wasConnected = true; while (true) { int numReceived = udpSocket.Receive(buffer); switch ((MessageReceivedType)buffer[0]) { case MessageReceivedType.LOGGED_IN: VelNetLogger.Info("UDP connected"); break; case MessageReceivedType.DATA_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); break; } } } } catch (ThreadAbortException) { // pass } catch (SocketException socketException) { VelNetLogger.Error("Socket exception: " + socketException); } catch (Exception ex) { VelNetLogger.Error(ex.ToString()); } } private static void SendUdpMessage(byte[] message, int N) { if (instance.udpSocket == null || !instance.udpConnected) { return; } instance.udpSocket.SendTo(message, N, SocketFlags.None, instance.RemoteEndPoint); } /// /// Send message to server using socket connection. /// /// We can assume that this message is already formatted, so we just send it /// True if the message successfully sent. False if it failed and we should quit private static bool SendTcpMessage(byte[] message) { // Logging.Info("Sent: " + clientMessage); if (instance.socketConnection == null) { VelNetLogger.Error("Tried to send message while socket connection was still null.", instance); return false; } try { // check if we have been disconnected, if so shut down velnet if (!instance.socketConnection.Connected) { instance.DisconnectFromServer(); VelNetLogger.Error("Disconnected from server. Most likely due to timeout."); return false; } // Get a stream object for writing. NetworkStream stream = instance.socketConnection.GetStream(); if (stream.CanWrite) { stream.Write(message, 0, message.Length); } } catch (IOException ioException) { instance.DisconnectFromServer(); VelNetLogger.Error("Disconnected from server. Most likely due to timeout.\n" + ioException); return false; } catch (SocketException socketException) { VelNetLogger.Error("Socket exception: " + socketException); } return true; } private static byte[] get_be_bytes(int n) { return BitConverter.GetBytes(n).Reverse().ToArray(); } /// /// Connects to the server for a particular app /// /// A unique name for your app. Communication can only happen between clients with the same app name. /// Should be unique per device that connects. e.g. md5(deviceUniqueIdentifier) public static void Login(string appName, string deviceId) { MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); byte[] id = Encoding.UTF8.GetBytes(deviceId); byte[] app = Encoding.UTF8.GetBytes(appName); writer.Write((byte)MessageSendType.MESSAGE_LOGIN); writer.Write((byte)id.Length); writer.Write(id); writer.Write((byte)app.Length); writer.Write(app); SendTcpMessage(stream.ToArray()); } public static void GetRooms(Action callback = null) { SendTcpMessage(new byte[] { (byte)MessageSendType.MESSAGE_GETROOMS }); // very simple message if (callback != null) { RoomsReceived += RoomsReceivedCallback; } void RoomsReceivedCallback(RoomsMessage msg) { callback(msg); RoomsReceived -= RoomsReceivedCallback; } } public static void GetRoomData(string roomName) { if (string.IsNullOrEmpty(roomName)) { VelNetLogger.Error("Room name is null. Can't get info for this room."); return; } MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); byte[] R = Encoding.UTF8.GetBytes(roomName); writer.Write((byte)MessageSendType.MESSAGE_GETROOMDATA); writer.Write((byte)R.Length); writer.Write(R); SendTcpMessage(stream.ToArray()); } /// /// Joins a room by name /// /// The name of the room to join [Obsolete("Use JoinRoom() instead")] public static void Join(string roomName) { JoinRoom(roomName); } /// /// Joins a room by name /// /// The name of the room to join public static void JoinRoom(string roomName) { if (instance.userid == -1) { Debug.LogError("Joining room before logging in.", instance); return; } MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); byte[] R = Encoding.UTF8.GetBytes(roomName); writer.Write((byte)MessageSendType.MESSAGE_JOINROOM); writer.Write((byte)R.Length); writer.Write(R); SendTcpMessage(stream.ToArray()); } /// /// Leaves a room if we're in one /// [Obsolete("Use LeaveRoom() instead")] public static void Leave() { LeaveRoom(); } /// /// Leaves a room if we're in one /// public static void LeaveRoom() { if (InRoom) { JoinRoom(""); // super secret way to leave } } public static void SendCustomMessage(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) { using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); writer.Write((byte)MessageType.Custom); writer.Write(message.Length); writer.Write(message); SendToRoom(mem.ToArray(), include_self, reliable, ordered); } public static void SendCustomMessageToGroup(string group, byte[] message, bool reliable = true) { using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); writer.Write((byte)MessageType.Custom); writer.Write(message.Length); writer.Write(message); SendToGroup(group, mem.ToArray(), reliable); } internal static bool SendToRoom(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) { byte sendType = (byte)MessageSendType.MESSAGE_OTHERS; if (include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_ALL_ORDERED; if (include_self && !ordered) sendType = (byte)MessageSendType.MESSAGE_ALL; if (!include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_OTHERS_ORDERED; if (reliable) { MemoryStream mem = new MemoryStream(); BinaryWriter writer = new BinaryWriter(mem); writer.Write(sendType); writer.Write(get_be_bytes(message.Length)); writer.Write(message); return SendTcpMessage(mem.ToArray()); } else { //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... return true; } } internal static bool 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); return 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); return true; } } /// /// changes the designated group that SendTo(4) will go to /// public static void SetupMessageGroup(string groupName, List clientIds) { if (clientIds.Count > 0) { instance.groups[groupName] = clientIds.ToList(); } 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(clientIds.Count * 4)); foreach (int c in clientIds) { writer.Write(get_be_bytes(c)); } SendTcpMessage(stream.ToArray()); } [Obsolete("Use NetworkInstantiate instead. This matches the naming convention of NetworkDestroy")] public static NetworkObject InstantiateNetworkObject(string prefabName) { return NetworkInstantiate(prefabName); } /// /// Instantiates a prefab for all players. /// /// This prefab *must* by added to the list of prefabs in the scene's VelNetManager for all players. /// The NetworkObject for the instantiated object. public static NetworkObject NetworkInstantiate(string prefabName) { VelNetPlayer localPlayer = LocalPlayer; NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); if (prefab == null) { VelNetLogger.Error("Couldn't find a prefab with that name: " + prefabName + "\nMake sure to add the prefab to list of prefabs in VelNetManager"); return null; } string networkId = localPlayer.userid + "-" + localPlayer.lastObjectId++; if (instance.objects.ContainsKey(networkId)) { VelNetLogger.Error("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; try { newObject.OwnershipChanged?.Invoke(localPlayer); } catch (Exception e) { VelNetLogger.Error("Error in event handling.\n" + e); } instance.objects.Add(newObject.networkId, newObject); // only sent to others, as I already instantiated this. Nice that it happens immediately. using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); writer.Write((byte)MessageType.Instantiate); writer.Write(newObject.networkId); writer.Write(prefabName); SendToRoom(mem.ToArray(), include_self: false, reliable: true); 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; try { newObject.OwnershipChanged?.Invoke(owner); } catch (Exception e) { VelNetLogger.Error("Error in event handling.\n" + e); } 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]; // clean up if this is null if (obj == null) { instance.objects.Remove(networkId); VelNetLogger.Error("Object to delete was already null"); return; } // Delete locally immediately SomebodyDestroyedNetworkObject(networkId); // Only sent to others, as we already deleted this. using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); writer.Write((byte)MessageType.Destroy); writer.Write(networkId); SendToRoom(mem.ToArray(), include_self: false, reliable: true); } public static void SomebodyDestroyedNetworkObject(string networkId) { if (!instance.objects.ContainsKey(networkId)) return; NetworkObject obj = instance.objects[networkId]; if (obj == null) { instance.objects.Remove(networkId); // VelNetLogger.Error("Object to delete was already null"); 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) { if (!InRoom) { VelNetLogger.Error("Can't take ownership. Not in a room."); return false; } // local player must exist if (LocalPlayer == null) { VelNetLogger.Error("Can't take ownership. No local player."); return false; } // obj must exist if (!instance.objects.ContainsKey(networkId)) { VelNetLogger.Error("Can't take ownership. Object with that network id doesn't exist: " + networkId); return false; } // if the ownership is locked, fail if (instance.objects[networkId].ownershipLocked) { VelNetLogger.Error("Can't take ownership. Ownership for this object is locked."); return false; } // immediately successful instance.objects[networkId].owner = LocalPlayer; try { instance.objects[networkId].OwnershipChanged?.Invoke(LocalPlayer); } catch (Exception e) { VelNetLogger.Error("Error in event handling.\n" + e); } // 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. using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); writer.Write((byte)MessageType.TakeOwnership); writer.Write(networkId); SendToRoom(mem.ToArray(), false, true); return true; } } }