#if ENABLE_UNET using System; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine.Networking.Match; using UnityEngine.Networking.NetworkSystem; using UnityEngine.Networking.Types; using UnityEngineInternal; namespace UnityEngine.Networking { public sealed class NetworkServer { static bool s_Active; static volatile NetworkServer s_Instance; static object s_Sync = new Object(); static bool m_DontListen; bool m_LocalClientActive; // only used for localConnection accessor List m_LocalConnectionsFakeList = new List(); ULocalConnectionToClient m_LocalConnection = null; NetworkScene m_NetworkScene; HashSet m_ExternalConnections; ServerSimpleWrapper m_SimpleServerSimple; float m_MaxDelay = 0.1f; HashSet m_RemoveList; int m_RemoveListCount; const int k_RemoveListInterval = 100; // this is cached here for easy access when checking the size of state update packets in NetworkIdentity static internal ushort maxPacketSize; // static message objects to avoid runtime-allocations static RemovePlayerMessage s_RemovePlayerMessage = new RemovePlayerMessage(); static public List localConnections { get { return instance.m_LocalConnectionsFakeList; } } static public int listenPort { get { return instance.m_SimpleServerSimple.listenPort; } } static public int serverHostId { get { return instance.m_SimpleServerSimple.serverHostId; } } static public ReadOnlyCollection connections { get { return instance.m_SimpleServerSimple.connections; } } static public Dictionary handlers { get { return instance.m_SimpleServerSimple.handlers; } } static public HostTopology hostTopology { get { return instance.m_SimpleServerSimple.hostTopology; }} public static Dictionary objects { get { return instance.m_NetworkScene.localObjects; } } #if ENABLE_UNET_HOST_MIGRATION [Obsolete("Moved to NetworkMigrationManager")] public static bool sendPeerInfo { get { return false; } set {} } #else [Obsolete("Removed")] public static bool sendPeerInfo { get { return false; } set {} } #endif public static bool dontListen { get { return m_DontListen; } set { m_DontListen = value; } } public static bool useWebSockets { get { return instance.m_SimpleServerSimple.useWebSockets; } set { instance.m_SimpleServerSimple.useWebSockets = value; } } internal static NetworkServer instance { get { if (s_Instance == null) { lock (s_Sync) { if (s_Instance == null) { s_Instance = new NetworkServer(); } } } return s_Instance; } } public static bool active { get { return s_Active; } } public static bool localClientActive { get { return instance.m_LocalClientActive; } } public static int numChannels { get { return instance.m_SimpleServerSimple.hostTopology.DefaultConfig.ChannelCount; } } public static float maxDelay { get { return instance.m_MaxDelay; } set { instance.InternalSetMaxDelay(value); } } static public Type networkConnectionClass { get { return instance.m_SimpleServerSimple.networkConnectionClass; } } static public void SetNetworkConnectionClass() where T : NetworkConnection { instance.m_SimpleServerSimple.SetNetworkConnectionClass(); } NetworkServer() { NetworkTransport.Init(); if (LogFilter.logDev) { Debug.Log("NetworkServer Created version " + Version.Current); } m_RemoveList = new HashSet(); m_ExternalConnections = new HashSet(); m_NetworkScene = new NetworkScene(); m_SimpleServerSimple = new ServerSimpleWrapper(this); } static public bool Configure(ConnectionConfig config, int maxConnections) { return instance.m_SimpleServerSimple.Configure(config, maxConnections); } static public bool Configure(HostTopology topology) { return instance.m_SimpleServerSimple.Configure(topology); } public static void Reset() { #if UNITY_EDITOR UnityEditor.NetworkDetailStats.ResetAll(); #endif NetworkTransport.Shutdown(); NetworkTransport.Init(); s_Instance = null; s_Active = false; } public static void Shutdown() { if (s_Instance != null) { s_Instance.InternalDisconnectAll(); if (m_DontListen) { // was never started, so dont stop } else { s_Instance.m_SimpleServerSimple.Stop(); } s_Instance = null; } m_DontListen = false; s_Active = false; } static public bool Listen(MatchInfo matchInfo, int listenPort) { if (!matchInfo.usingRelay) return instance.InternalListen(null, listenPort); instance.InternalListenRelay(matchInfo.address, matchInfo.port, matchInfo.networkId, Utility.GetSourceID(), matchInfo.nodeId); return true; } internal void RegisterMessageHandlers() { m_SimpleServerSimple.RegisterHandlerSafe(MsgType.Ready, OnClientReadyMessage); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.Command, OnCommandMessage); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.LocalPlayerTransform, NetworkTransform.HandleTransform); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.LocalChildTransform, NetworkTransformChild.HandleChildTransform); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.RemovePlayer, OnRemovePlayerMessage); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.Animation, NetworkAnimator.OnAnimationServerMessage); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.AnimationParameters, NetworkAnimator.OnAnimationParametersServerMessage); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.AnimationTrigger, NetworkAnimator.OnAnimationTriggerServerMessage); m_SimpleServerSimple.RegisterHandlerSafe(MsgType.Fragment, NetworkConnection.OnFragment); // also setup max packet size. maxPacketSize = hostTopology.DefaultConfig.PacketSize; } static public void ListenRelay(string relayIp, int relayPort, NetworkID netGuid, SourceID sourceId, NodeID nodeId) { instance.InternalListenRelay(relayIp, relayPort, netGuid, sourceId, nodeId); } void InternalListenRelay(string relayIp, int relayPort, NetworkID netGuid, SourceID sourceId, NodeID nodeId) { m_SimpleServerSimple.ListenRelay(relayIp, relayPort, netGuid, sourceId, nodeId); s_Active = true; RegisterMessageHandlers(); } static public bool Listen(int serverPort) { return instance.InternalListen(null, serverPort); } static public bool Listen(string ipAddress, int serverPort) { return instance.InternalListen(ipAddress, serverPort); } internal bool InternalListen(string ipAddress, int serverPort) { if (m_DontListen) { // dont start simpleServer - this mode uses external connections instead m_SimpleServerSimple.Initialize(); } else { if (!m_SimpleServerSimple.Listen(ipAddress, serverPort)) return false; } maxPacketSize = hostTopology.DefaultConfig.PacketSize; s_Active = true; RegisterMessageHandlers(); return true; } #if ENABLE_UNET_HOST_MIGRATION static public NetworkClient BecomeHost(NetworkClient oldClient, int port, MatchInfo matchInfo, int oldConnectionId, PeerInfoMessage[] peers) { return instance.BecomeHostInternal(oldClient, port, matchInfo, oldConnectionId, peers); } internal NetworkClient BecomeHostInternal(NetworkClient oldClient, int port, MatchInfo matchInfo, int oldConnectionId, PeerInfoMessage[] peers) { if (s_Active) { if (LogFilter.logError) { Debug.LogError("BecomeHost already a server."); } return null; } if (!NetworkClient.active) { if (LogFilter.logError) { Debug.LogError("BecomeHost NetworkClient not active."); } return null; } // setup a server NetworkServer.Configure(hostTopology); if (matchInfo == null) { if (LogFilter.logDev) { Debug.Log("BecomeHost Listen on " + port); } if (!NetworkServer.Listen(port)) { if (LogFilter.logError) { Debug.LogError("BecomeHost bind failed."); } return null; } } else { if (LogFilter.logDev) { Debug.Log("BecomeHost match:" + matchInfo.networkId); } NetworkServer.ListenRelay(matchInfo.address, matchInfo.port, matchInfo.networkId, Utility.GetSourceID(), matchInfo.nodeId); } // setup server objects foreach (var uv in ClientScene.objects.Values) { if (uv == null || uv.gameObject == null) continue; NetworkIdentity.AddNetworkId(uv.netId.Value); //NOTE: have to pass false to isServer here so that onStartServer sets object up properly. m_NetworkScene.SetLocalObject(uv.netId, uv.gameObject, false, false); uv.OnStartServer(true); } // reset the client peer info(?) if (LogFilter.logDev) { Debug.Log("NetworkServer BecomeHost done. oldConnectionId:" + oldConnectionId); } RegisterMessageHandlers(); if (!NetworkClient.RemoveClient(oldClient)) { if (LogFilter.logError) { Debug.LogError("BecomeHost failed to remove client"); } } if (LogFilter.logDev) { Debug.Log("BecomeHost localClient ready"); } // make a localclient for me var newLocalClient = ClientScene.ReconnectLocalServer(); ClientScene.Ready(newLocalClient.connection); // cause local players and objects to be reconnected ClientScene.SetReconnectId(oldConnectionId, peers); ClientScene.AddPlayer(ClientScene.readyConnection, 0); return newLocalClient; } #endif void InternalSetMaxDelay(float seconds) { // set on existing connections for (int i = 0; i < connections.Count; i++) { NetworkConnection conn = connections[i]; if (conn != null) conn.SetMaxDelay(seconds); } // save for future connections m_MaxDelay = seconds; } // called by LocalClient to add itself. dont call directly. internal int AddLocalClient(LocalClient localClient) { if (m_LocalConnectionsFakeList.Count != 0) { Debug.LogError("Local Connection already exists"); return -1; } m_LocalConnection = new ULocalConnectionToClient(localClient); m_LocalConnection.connectionId = 0; m_SimpleServerSimple.SetConnectionAtIndex(m_LocalConnection); // this is for backwards compatibility with localConnections property m_LocalConnectionsFakeList.Add(m_LocalConnection); m_LocalConnection.InvokeHandlerNoData(MsgType.Connect); return 0; } internal void RemoveLocalClient(NetworkConnection localClientConnection) { for (int i = 0; i < m_LocalConnectionsFakeList.Count; ++i) { if (m_LocalConnectionsFakeList[i].connectionId == localClientConnection.connectionId) { m_LocalConnectionsFakeList.RemoveAt(i); break; } } if (m_LocalConnection != null) { m_LocalConnection.Disconnect(); m_LocalConnection.Dispose(); m_LocalConnection = null; } m_LocalClientActive = false; m_SimpleServerSimple.RemoveConnectionAtIndex(0); } internal void SetLocalObjectOnServer(NetworkInstanceId netId, GameObject obj) { if (LogFilter.logDev) { Debug.Log("SetLocalObjectOnServer " + netId + " " + obj); } m_NetworkScene.SetLocalObject(netId, obj, false, true); } internal void ActivateLocalClientScene() { if (m_LocalClientActive) return; // ClientScene for a local connection is becoming active. any spawned objects need to be started as client objects m_LocalClientActive = true; foreach (var uv in objects.Values) { if (!uv.isClient) { if (LogFilter.logDev) { Debug.Log("ActivateClientScene " + uv.netId + " " + uv.gameObject); } ClientScene.SetLocalObject(uv.netId, uv.gameObject); uv.OnStartClient(); } } } static public bool SendToAll(short msgType, MessageBase msg) { if (LogFilter.logDev) { Debug.Log("Server.SendToAll msgType:" + msgType); } bool result = true; // remote connections for (int i = 0; i < connections.Count; i++) { NetworkConnection conn = connections[i]; if (conn != null) result &= conn.Send(msgType, msg); } return result; } // this is like SendToReady - but it doesn't check the ready flag on the connection. // this is used for ObjectDestroy messages. static bool SendToObservers(GameObject contextObj, short msgType, MessageBase msg) { if (LogFilter.logDev) { Debug.Log("Server.SendToObservers id:" + msgType); } bool result = true; var uv = contextObj.GetComponent(); if (uv == null || uv.observers == null) return false; int count = uv.observers.Count; for (int i = 0; i < count; i++) { var conn = uv.observers[i]; result &= conn.Send(msgType, msg); } return result; } static public bool SendToReady(GameObject contextObj, short msgType, MessageBase msg) { if (LogFilter.logDev) { Debug.Log("Server.SendToReady id:" + msgType); } if (contextObj == null) { for (int i = 0; i < connections.Count; i++) { NetworkConnection conn = connections[i]; if (conn != null && conn.isReady) { conn.Send(msgType, msg); } } return true; } bool result = true; var uv = contextObj.GetComponent(); if (uv == null || uv.observers == null) return false; int count = uv.observers.Count; for (int i = 0; i < count; i++) { var conn = uv.observers[i]; if (!conn.isReady) continue; result &= conn.Send(msgType, msg); } return result; } static public void SendWriterToReady(GameObject contextObj, NetworkWriter writer, int channelId) { if (writer.AsArraySegment().Count > short.MaxValue) { throw new UnityException("NetworkWriter used buffer is too big!"); } SendBytesToReady(contextObj, writer.AsArraySegment().Array, writer.AsArraySegment().Count, channelId); } static public void SendBytesToReady(GameObject contextObj, byte[] buffer, int numBytes, int channelId) { if (contextObj == null) { // no context.. send to all ready connections bool success = true; for (int i = 0; i < connections.Count; i++) { NetworkConnection conn = connections[i]; if (conn != null && conn.isReady) { if (!conn.SendBytes(buffer, numBytes, channelId)) { success = false; } } } if (!success) { if (LogFilter.logWarn) { Debug.LogWarning("SendBytesToReady failed"); } } return; } var uv = contextObj.GetComponent(); try { bool success = true; int count = uv.observers.Count; for (int i = 0; i < count; i++) { var conn = uv.observers[i]; if (!conn.isReady) continue; if (!conn.SendBytes(buffer, numBytes, channelId)) { success = false; } } if (!success) { if (LogFilter.logWarn) { Debug.LogWarning("SendBytesToReady failed for " + contextObj); } } } catch (NullReferenceException) { // observers may be null if object has not been spawned if (LogFilter.logWarn) { Debug.LogWarning("SendBytesToReady object " + contextObj + " has not been spawned"); } } } public static void SendBytesToPlayer(GameObject player, byte[] buffer, int numBytes, int channelId) { for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn == null) continue; for (int j = 0; j < conn.playerControllers.Count; j++) { if (conn.playerControllers[j].IsValid && conn.playerControllers[j].gameObject == player) { conn.SendBytes(buffer, numBytes, channelId); break; } } } } static public bool SendUnreliableToAll(short msgType, MessageBase msg) { if (LogFilter.logDev) { Debug.Log("Server.SendUnreliableToAll msgType:" + msgType); } bool result = true; for (int i = 0; i < connections.Count; i++) { NetworkConnection conn = connections[i]; if (conn != null) result &= conn.SendUnreliable(msgType, msg); } return result; } static public bool SendUnreliableToReady(GameObject contextObj, short msgType, MessageBase msg) { if (LogFilter.logDev) { Debug.Log("Server.SendUnreliableToReady id:" + msgType); } if (contextObj == null) { // no context.. send to all ready connections for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null && conn.isReady) { conn.SendUnreliable(msgType, msg); } } return true; } bool result = true; var uv = contextObj.GetComponent(); int count = uv.observers.Count; for (int i = 0; i < count; i++) { var conn = uv.observers[i]; if (!conn.isReady) continue; result &= conn.SendUnreliable(msgType, msg); } return result; } static public bool SendByChannelToAll(short msgType, MessageBase msg, int channelId) { if (LogFilter.logDev) { Debug.Log("Server.SendByChannelToAll id:" + msgType); } bool result = true; for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) result &= conn.SendByChannel(msgType, msg, channelId); } return result; } static public bool SendByChannelToReady(GameObject contextObj, short msgType, MessageBase msg, int channelId) { if (LogFilter.logDev) { Debug.Log("Server.SendByChannelToReady msgType:" + msgType); } if (contextObj == null) { // no context.. send to all ready connections for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null && conn.isReady) { conn.SendByChannel(msgType, msg, channelId); } } return true; } bool result = true; var uv = contextObj.GetComponent(); int count = uv.observers.Count; for (int i = 0; i < count; i++) { var conn = uv.observers[i]; if (!conn.isReady) continue; result &= conn.SendByChannel(msgType, msg, channelId); } return result; } static public void DisconnectAll() { instance.InternalDisconnectAll(); } internal void InternalDisconnectAll() { m_SimpleServerSimple.DisconnectAllConnections(); if (m_LocalConnection != null) { m_LocalConnection.Disconnect(); m_LocalConnection.Dispose(); m_LocalConnection = null; } m_LocalClientActive = false; } // The user should never need to pump the update loop manually internal static void Update() { if (s_Instance != null) s_Instance.InternalUpdate(); } void UpdateServerObjects() { foreach (var uv in objects.Values) { try { uv.UNetUpdate(); } catch (NullReferenceException) { //ignore nulls here.. they will be cleaned up by CheckForNullObjects below } catch (MissingReferenceException) { //ignore missing ref here.. they will be cleaned up by CheckForNullObjects below } } // check for nulls in this list every N updates. doing it every frame is expensive and unneccessary if (m_RemoveListCount++ % k_RemoveListInterval == 0) CheckForNullObjects(); } void CheckForNullObjects() { // cant iterate through Values here, since we need the keys of null objects to add to remove list. foreach (var k in objects.Keys) { var uv = objects[k]; if (uv == null || uv.gameObject == null) { m_RemoveList.Add(k); } } if (m_RemoveList.Count > 0) { foreach (var remove in m_RemoveList) { objects.Remove(remove); } m_RemoveList.Clear(); } } internal void InternalUpdate() { m_SimpleServerSimple.Update(); if (m_DontListen) { m_SimpleServerSimple.UpdateConnections(); } UpdateServerObjects(); } void OnConnected(NetworkConnection conn) { if (LogFilter.logDebug) { Debug.Log("Server accepted client:" + conn.connectionId); } // add player info conn.SetMaxDelay(m_MaxDelay); conn.InvokeHandlerNoData(MsgType.Connect); SendCrc(conn); } void OnDisconnected(NetworkConnection conn) { conn.InvokeHandlerNoData(MsgType.Disconnect); for (int i = 0; i < conn.playerControllers.Count; i++) { if (conn.playerControllers[i].gameObject != null) { //NOTE: should there be default behaviour here to destroy the associated player? if (LogFilter.logWarn) { Debug.LogWarning("Player not destroyed when connection disconnected."); } } } if (LogFilter.logDebug) { Debug.Log("Server lost client:" + conn.connectionId); } conn.RemoveObservers(); conn.Dispose(); } void OnData(NetworkConnection conn, int receivedSize, int channelId) { #if UNITY_EDITOR UnityEditor.NetworkDetailStats.IncrementStat( UnityEditor.NetworkDetailStats.NetworkDirection.Incoming, MsgType.LLAPIMsg, "msg", 1); #endif conn.TransportReceive(m_SimpleServerSimple.messageBuffer, receivedSize, channelId); } private void GenerateConnectError(int error) { if (LogFilter.logError) { Debug.LogError("UNet Server Connect Error: " + error); } GenerateError(null, error); } private void GenerateDataError(NetworkConnection conn, int error) { NetworkError dataError = (NetworkError)error; if (LogFilter.logError) { Debug.LogError("UNet Server Data Error: " + dataError); } GenerateError(conn, error); } private void GenerateDisconnectError(NetworkConnection conn, int error) { NetworkError disconnectError = (NetworkError)error; if (LogFilter.logError) { Debug.LogError("UNet Server Disconnect Error: " + disconnectError + " conn:[" + conn + "]:" + conn.connectionId); } GenerateError(conn, error); } private void GenerateError(NetworkConnection conn, int error) { if (handlers.ContainsKey(MsgType.Error)) { ErrorMessage msg = new ErrorMessage(); msg.errorCode = error; // write the message to a local buffer NetworkWriter writer = new NetworkWriter(); msg.Serialize(writer); // pass a reader (attached to local buffer) to handler NetworkReader reader = new NetworkReader(writer); conn.InvokeHandler(MsgType.Error, reader, 0); } } static public void RegisterHandler(short msgType, NetworkMessageDelegate handler) { instance.m_SimpleServerSimple.RegisterHandler(msgType, handler); } static public void UnregisterHandler(short msgType) { instance.m_SimpleServerSimple.UnregisterHandler(msgType); } static public void ClearHandlers() { instance.m_SimpleServerSimple.ClearHandlers(); } static public void ClearSpawners() { NetworkScene.ClearSpawners(); } static public void GetStatsOut(out int numMsgs, out int numBufferedMsgs, out int numBytes, out int lastBufferedPerSecond) { numMsgs = 0; numBufferedMsgs = 0; numBytes = 0; lastBufferedPerSecond = 0; for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) { int snumMsgs; int snumBufferedMsgs; int snumBytes; int slastBufferedPerSecond; conn.GetStatsOut(out snumMsgs, out snumBufferedMsgs, out snumBytes, out slastBufferedPerSecond); numMsgs += snumMsgs; numBufferedMsgs += snumBufferedMsgs; numBytes += snumBytes; lastBufferedPerSecond += slastBufferedPerSecond; } } } static public void GetStatsIn(out int numMsgs, out int numBytes) { numMsgs = 0; numBytes = 0; for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) { int cnumMsgs; int cnumBytes; conn.GetStatsIn(out cnumMsgs, out cnumBytes); numMsgs += cnumMsgs; numBytes += cnumBytes; } } } // send this message to the player only static public void SendToClientOfPlayer(GameObject player, short msgType, MessageBase msg) { for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) { for (int j = 0; j < conn.playerControllers.Count; j++) { if (conn.playerControllers[j].IsValid && conn.playerControllers[j].gameObject == player) { conn.Send(msgType, msg); return; } } } } if (LogFilter.logError) { Debug.LogError("Failed to send message to player object '" + player.name + ", not found in connection list"); } } static public void SendToClient(int connectionId, short msgType, MessageBase msg) { if (connectionId < connections.Count) { var conn = connections[connectionId]; if (conn != null) { conn.Send(msgType, msg); return; } } if (LogFilter.logError) { Debug.LogError("Failed to send message to connection ID '" + connectionId + ", not found in connection list"); } } static public bool ReplacePlayerForConnection(NetworkConnection conn, GameObject player, short playerControllerId, NetworkHash128 assetId) { NetworkIdentity id; if (GetNetworkIdentity(player, out id)) { id.SetDynamicAssetId(assetId); } return instance.InternalReplacePlayerForConnection(conn, player, playerControllerId); } static public bool ReplacePlayerForConnection(NetworkConnection conn, GameObject player, short playerControllerId) { return instance.InternalReplacePlayerForConnection(conn, player, playerControllerId); } static public bool AddPlayerForConnection(NetworkConnection conn, GameObject player, short playerControllerId, NetworkHash128 assetId) { NetworkIdentity id; if (GetNetworkIdentity(player, out id)) { id.SetDynamicAssetId(assetId); } return instance.InternalAddPlayerForConnection(conn, player, playerControllerId); } static public bool AddPlayerForConnection(NetworkConnection conn, GameObject player, short playerControllerId) { return instance.InternalAddPlayerForConnection(conn, player, playerControllerId); } internal bool InternalAddPlayerForConnection(NetworkConnection conn, GameObject playerGameObject, short playerControllerId) { NetworkIdentity playerNetworkIdentity; if (!GetNetworkIdentity(playerGameObject, out playerNetworkIdentity)) { if (LogFilter.logError) { Debug.Log("AddPlayer: playerGameObject has no NetworkIdentity. Please add a NetworkIdentity to " + playerGameObject); } return false; } playerNetworkIdentity.Reset(); if (!CheckPlayerControllerIdForConnection(conn, playerControllerId)) return false; // cannot have a player object in "Add" version PlayerController oldController = null; GameObject oldPlayer = null; if (conn.GetPlayerController(playerControllerId, out oldController)) { oldPlayer = oldController.gameObject; } if (oldPlayer != null) { if (LogFilter.logError) { Debug.Log("AddPlayer: player object already exists for playerControllerId of " + playerControllerId); } return false; } PlayerController newPlayerController = new PlayerController(playerGameObject, playerControllerId); conn.SetPlayerController(newPlayerController); // Set the playerControllerId on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients and that sets the playerControllerId there) playerNetworkIdentity.SetConnectionToClient(conn, newPlayerController.playerControllerId); SetClientReady(conn); if (SetupLocalPlayerForConnection(conn, playerNetworkIdentity, newPlayerController)) { return true; } if (LogFilter.logDebug) { Debug.Log("Adding new playerGameObject object netId: " + playerGameObject.GetComponent().netId + " asset ID " + playerGameObject.GetComponent().assetId); } FinishPlayerForConnection(conn, playerNetworkIdentity, playerGameObject); if (playerNetworkIdentity.localPlayerAuthority) { playerNetworkIdentity.SetClientOwner(conn); } return true; } static bool CheckPlayerControllerIdForConnection(NetworkConnection conn, short playerControllerId) { if (playerControllerId < 0) { if (LogFilter.logError) { Debug.LogError("AddPlayer: playerControllerId of " + playerControllerId + " is negative"); } return false; } if (playerControllerId > PlayerController.MaxPlayersPerClient) { if (LogFilter.logError) { Debug.Log("AddPlayer: playerControllerId of " + playerControllerId + " is too high. max is " + PlayerController.MaxPlayersPerClient); } return false; } if (playerControllerId > PlayerController.MaxPlayersPerClient / 2) { if (LogFilter.logWarn) { Debug.LogWarning("AddPlayer: playerControllerId of " + playerControllerId + " is unusually high"); } } return true; } bool SetupLocalPlayerForConnection(NetworkConnection conn, NetworkIdentity uv, PlayerController newPlayerController) { if (LogFilter.logDev) { Debug.Log("NetworkServer SetupLocalPlayerForConnection netID:" + uv.netId); } var localConnection = conn as ULocalConnectionToClient; if (localConnection != null) { if (LogFilter.logDev) { Debug.Log("NetworkServer AddPlayer handling ULocalConnectionToClient"); } // Spawn this player for other players, instead of SpawnObject: if (uv.netId.IsEmpty()) { // it is allowed to provide an already spawned object as the new player object. // so dont spawn it again. uv.OnStartServer(true); } uv.RebuildObservers(true); SendSpawnMessage(uv, null); // Set up local player instance on the client instance and update local object map localConnection.localClient.AddLocalPlayer(newPlayerController); uv.SetClientOwner(conn); // Trigger OnAuthority uv.ForceAuthority(true); // Trigger OnStartLocalPlayer uv.SetLocalPlayer(newPlayerController.playerControllerId); return true; } return false; } static void FinishPlayerForConnection(NetworkConnection conn, NetworkIdentity uv, GameObject playerGameObject) { if (uv.netId.IsEmpty()) { // it is allowed to provide an already spawned object as the new player object. // so dont spawn it again. Spawn(playerGameObject); } OwnerMessage owner = new OwnerMessage(); owner.netId = uv.netId; owner.playerControllerId = uv.playerControllerId; conn.Send(MsgType.Owner, owner); } internal bool InternalReplacePlayerForConnection(NetworkConnection conn, GameObject playerGameObject, short playerControllerId) { NetworkIdentity playerNetworkIdentity; if (!GetNetworkIdentity(playerGameObject, out playerNetworkIdentity)) { if (LogFilter.logError) { Debug.LogError("ReplacePlayer: playerGameObject has no NetworkIdentity. Please add a NetworkIdentity to " + playerGameObject); } return false; } if (!CheckPlayerControllerIdForConnection(conn, playerControllerId)) return false; //NOTE: there can be an existing player if (LogFilter.logDev) { Debug.Log("NetworkServer ReplacePlayer"); } // is there already an owner that is a different object?? PlayerController oldOwner; if (conn.GetPlayerController(playerControllerId, out oldOwner)) { oldOwner.unetView.SetNotLocalPlayer(); oldOwner.unetView.ClearClientOwner(); } PlayerController newPlayerController = new PlayerController(playerGameObject, playerControllerId); conn.SetPlayerController(newPlayerController); // Set the playerControllerId on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients and that sets the playerControllerId there) playerNetworkIdentity.SetConnectionToClient(conn, newPlayerController.playerControllerId); //NOTE: DONT set connection ready. if (LogFilter.logDev) { Debug.Log("NetworkServer ReplacePlayer setup local"); } if (SetupLocalPlayerForConnection(conn, playerNetworkIdentity, newPlayerController)) { return true; } if (LogFilter.logDebug) { Debug.Log("Replacing playerGameObject object netId: " + playerGameObject.GetComponent().netId + " asset ID " + playerGameObject.GetComponent().assetId); } FinishPlayerForConnection(conn, playerNetworkIdentity, playerGameObject); if (playerNetworkIdentity.localPlayerAuthority) { playerNetworkIdentity.SetClientOwner(conn); } return true; } static bool GetNetworkIdentity(GameObject go, out NetworkIdentity view) { view = go.GetComponent(); if (view == null) { if (LogFilter.logError) { Debug.LogError("UNET failure. GameObject doesn't have NetworkIdentity."); } return false; } return true; } static public void SetClientReady(NetworkConnection conn) { instance.SetClientReadyInternal(conn); } internal void SetClientReadyInternal(NetworkConnection conn) { if (LogFilter.logDebug) { Debug.Log("SetClientReadyInternal for conn:" + conn.connectionId); } if (conn.isReady) { if (LogFilter.logDebug) { Debug.Log("SetClientReady conn " + conn.connectionId + " already ready"); } return; } if (conn.playerControllers.Count == 0) { // this is now allowed if (LogFilter.logDebug) { Debug.LogWarning("Ready with no player object"); } } conn.isReady = true; var localConnection = conn as ULocalConnectionToClient; if (localConnection != null) { if (LogFilter.logDev) { Debug.Log("NetworkServer Ready handling ULocalConnectionToClient"); } // Setup spawned objects for local player // Only handle the local objects for the first player (no need to redo it when doing more local players) // and don't handle player objects here, they were done above foreach (NetworkIdentity uv in objects.Values) { // Need to call OnStartClient directly here, as it's already been added to the local object dictionary // in the above SetLocalPlayer call if (uv != null && uv.gameObject != null) { var vis = uv.OnCheckObserver(conn); if (vis) { uv.AddObserver(conn); } if (!uv.isClient) { if (LogFilter.logDev) { Debug.Log("LocalClient.SetSpawnObject calling OnStartClient"); } uv.OnStartClient(); } } } return; } // Spawn/update all current server objects if (LogFilter.logDebug) { Debug.Log("Spawning " + objects.Count + " objects for conn " + conn.connectionId); } ObjectSpawnFinishedMessage msg = new ObjectSpawnFinishedMessage(); msg.state = 0; conn.Send(MsgType.SpawnFinished, msg); foreach (NetworkIdentity uv in objects.Values) { if (uv == null) { if (LogFilter.logWarn) { Debug.LogWarning("Invalid object found in server local object list (null NetworkIdentity)."); } continue; } if (!uv.gameObject.activeSelf) { continue; } if (LogFilter.logDebug) { Debug.Log("Sending spawn message for current server objects name='" + uv.gameObject.name + "' netId=" + uv.netId); } var vis = uv.OnCheckObserver(conn); if (vis) { uv.AddObserver(conn); } } msg.state = 1; conn.Send(MsgType.SpawnFinished, msg); } static internal void ShowForConnection(NetworkIdentity uv, NetworkConnection conn) { if (conn.isReady) instance.SendSpawnMessage(uv, conn); } static internal void HideForConnection(NetworkIdentity uv, NetworkConnection conn) { ObjectDestroyMessage msg = new ObjectDestroyMessage(); msg.netId = uv.netId; conn.Send(MsgType.ObjectHide, msg); } // call this to make all the clients not ready, such as when changing levels. static public void SetAllClientsNotReady() { for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) { SetClientNotReady(conn); } } } static public void SetClientNotReady(NetworkConnection conn) { instance.InternalSetClientNotReady(conn); } internal void InternalSetClientNotReady(NetworkConnection conn) { if (conn.isReady) { if (LogFilter.logDebug) { Debug.Log("PlayerNotReady " + conn); } conn.isReady = false; conn.RemoveObservers(); NotReadyMessage msg = new NotReadyMessage(); conn.Send(MsgType.NotReady, msg); } } // default ready handler. static void OnClientReadyMessage(NetworkMessage netMsg) { if (LogFilter.logDebug) { Debug.Log("Default handler for ready message from " + netMsg.conn); } SetClientReady(netMsg.conn); } // default remove player handler static void OnRemovePlayerMessage(NetworkMessage netMsg) { netMsg.ReadMessage(s_RemovePlayerMessage); PlayerController player = null; netMsg.conn.GetPlayerController(s_RemovePlayerMessage.playerControllerId, out player); if (player != null) { netMsg.conn.RemovePlayerController(s_RemovePlayerMessage.playerControllerId); Destroy(player.gameObject); } else { if (LogFilter.logError) { Debug.LogError("Received remove player message but could not find the player ID: " + s_RemovePlayerMessage.playerControllerId); } } } // Handle command from specific player, this could be one of multiple players on a single client static void OnCommandMessage(NetworkMessage netMsg) { int cmdHash = (int)netMsg.reader.ReadPackedUInt32(); var netId = netMsg.reader.ReadNetworkId(); var cmdObject = FindLocalObject(netId); if (cmdObject == null) { if (LogFilter.logWarn) { Debug.LogWarning("Instance not found when handling Command message [netId=" + netId + "]"); } return; } var uv = cmdObject.GetComponent(); if (uv == null) { if (LogFilter.logWarn) { Debug.LogWarning("NetworkIdentity deleted when handling Command message [netId=" + netId + "]"); } return; } // Commands can be for player objects, OR other objects with client-authority bool foundOwner = false; for (int i = 0; i < netMsg.conn.playerControllers.Count; i++) { var p = netMsg.conn.playerControllers[i]; if (p.gameObject != null && p.gameObject.GetComponent().netId == uv.netId) { foundOwner = true; break; } } if (!foundOwner) { if (uv.clientAuthorityOwner != netMsg.conn) { if (LogFilter.logWarn) { Debug.LogWarning("Command for object without authority [netId=" + netId + "]"); } return; } } if (LogFilter.logDev) { Debug.Log("OnCommandMessage for netId=" + netId + " conn=" + netMsg.conn); } uv.HandleCommand(cmdHash, netMsg.reader); } internal void SpawnObject(GameObject obj) { if (!NetworkServer.active) { if (LogFilter.logError) { Debug.LogError("SpawnObject for " + obj + ", NetworkServer is not active. Cannot spawn objects without an active server."); } return; } NetworkIdentity objNetworkIdentity; if (!GetNetworkIdentity(obj, out objNetworkIdentity)) { if (LogFilter.logError) { Debug.LogError("SpawnObject " + obj + " has no NetworkIdentity. Please add a NetworkIdentity to " + obj); } return; } objNetworkIdentity.Reset(); objNetworkIdentity.OnStartServer(false); if (LogFilter.logDebug) { Debug.Log("SpawnObject instance ID " + objNetworkIdentity.netId + " asset ID " + objNetworkIdentity.assetId); } objNetworkIdentity.RebuildObservers(true); //SendSpawnMessage(objNetworkIdentity, null); } internal void SendSpawnMessage(NetworkIdentity uv, NetworkConnection conn) { if (uv.serverOnly) return; if (uv.sceneId.IsEmpty()) { ObjectSpawnMessage msg = new ObjectSpawnMessage(); msg.netId = uv.netId; msg.assetId = uv.assetId; msg.position = uv.transform.position; msg.rotation = uv.transform.rotation; // include synch data NetworkWriter writer = new NetworkWriter(); uv.UNetSerializeAllVars(writer); if (writer.Position > 0) { msg.payload = writer.ToArray(); } if (conn != null) { conn.Send(MsgType.ObjectSpawn, msg); } else { SendToReady(uv.gameObject, MsgType.ObjectSpawn, msg); } #if UNITY_EDITOR UnityEditor.NetworkDetailStats.IncrementStat( UnityEditor.NetworkDetailStats.NetworkDirection.Outgoing, MsgType.ObjectSpawn, uv.assetId.ToString(), 1); #endif } else { ObjectSpawnSceneMessage msg = new ObjectSpawnSceneMessage(); msg.netId = uv.netId; msg.sceneId = uv.sceneId; msg.position = uv.transform.position; // include synch data NetworkWriter writer = new NetworkWriter(); uv.UNetSerializeAllVars(writer); if (writer.Position > 0) { msg.payload = writer.ToArray(); } if (conn != null) { conn.Send(MsgType.ObjectSpawnScene, msg); } else { SendToReady(uv.gameObject, MsgType.ObjectSpawn, msg); } #if UNITY_EDITOR UnityEditor.NetworkDetailStats.IncrementStat( UnityEditor.NetworkDetailStats.NetworkDirection.Outgoing, MsgType.ObjectSpawnScene, "sceneId", 1); #endif } } static public void DestroyPlayersForConnection(NetworkConnection conn) { if (conn.playerControllers.Count == 0) { if (LogFilter.logWarn) { Debug.LogWarning("Empty player list given to NetworkServer.Destroy(), nothing to do."); } return; } if (conn.clientOwnedObjects != null) { var tmp = new HashSet(conn.clientOwnedObjects); foreach (var netId in tmp) { var obj = FindLocalObject(netId); if (obj != null) { DestroyObject(obj); } } } for (int i = 0; i < conn.playerControllers.Count; i++) { var player = conn.playerControllers[i]; if (player.IsValid) { if (player.unetView == null) { // the playerController's object has been destroyed, but RemovePlayerForConnection was never called. // this is ok, just dont double destroy it. } else { DestroyObject(player.unetView, true); } player.gameObject = null; } } conn.playerControllers.Clear(); } static void UnSpawnObject(GameObject obj) { if (obj == null) { if (LogFilter.logDev) { Debug.Log("NetworkServer UnspawnObject is null"); } return; } NetworkIdentity objNetworkIdentity; if (!GetNetworkIdentity(obj, out objNetworkIdentity)) return; UnSpawnObject(objNetworkIdentity); } static void UnSpawnObject(NetworkIdentity uv) { DestroyObject(uv, false); } static void DestroyObject(GameObject obj) { if (obj == null) { if (LogFilter.logDev) { Debug.Log("NetworkServer DestroyObject is null"); } return; } NetworkIdentity objNetworkIdentity; if (!GetNetworkIdentity(obj, out objNetworkIdentity)) return; DestroyObject(objNetworkIdentity, true); } static void DestroyObject(NetworkIdentity uv, bool destroyServerObject) { if (LogFilter.logDebug) { Debug.Log("DestroyObject instance:" + uv.netId); } if (objects.ContainsKey(uv.netId)) { objects.Remove(uv.netId); } if (uv.clientAuthorityOwner != null) { uv.clientAuthorityOwner.RemoveOwnedObject(uv); } #if UNITY_EDITOR UnityEditor.NetworkDetailStats.IncrementStat( UnityEditor.NetworkDetailStats.NetworkDirection.Outgoing, MsgType.ObjectDestroy, uv.assetId.ToString(), 1); #endif ObjectDestroyMessage msg = new ObjectDestroyMessage(); msg.netId = uv.netId; SendToObservers(uv.gameObject, MsgType.ObjectDestroy, msg); uv.ClearObservers(); if (NetworkClient.active && instance.m_LocalClientActive) { uv.OnNetworkDestroy(); ClientScene.SetLocalObject(msg.netId, null); } // when unspawning, dont destroy the server's object if (destroyServerObject) { Object.Destroy(uv.gameObject); } uv.MarkForReset(); } static public void ClearLocalObjects() { objects.Clear(); } static public void Spawn(GameObject obj) { if (!VerifyCanSpawn(obj)) { return; } instance.SpawnObject(obj); } static bool CheckForPrefab(GameObject obj) { #if UNITY_EDITOR return (UnityEditor.PrefabUtility.GetPrefabParent(obj) == null) && (UnityEditor.PrefabUtility.GetPrefabObject(obj) != null); #else return false; #endif } static bool VerifyCanSpawn(GameObject obj) { if (CheckForPrefab(obj)) { Debug.LogErrorFormat("GameObject {0} is a prefab, it can't be spawned. This will cause errors in builds.", obj.name); return false; } return true; } static public Boolean SpawnWithClientAuthority(GameObject obj, GameObject player) { var uv = player.GetComponent(); if (uv == null) { Debug.LogError("SpawnWithClientAuthority player object has no NetworkIdentity"); return false; } if (uv.connectionToClient == null) { Debug.LogError("SpawnWithClientAuthority player object is not a player."); return false; } return SpawnWithClientAuthority(obj, uv.connectionToClient); } static public bool SpawnWithClientAuthority(GameObject obj, NetworkConnection conn) { if (!conn.isReady) { Debug.LogError("SpawnWithClientAuthority NetworkConnection is not ready!"); return false; } Spawn(obj); var uv = obj.GetComponent(); if (uv == null || !uv.isServer) { // spawning the object failed. return false; } return uv.AssignClientAuthority(conn); } static public bool SpawnWithClientAuthority(GameObject obj, NetworkHash128 assetId, NetworkConnection conn) { Spawn(obj, assetId); var uv = obj.GetComponent(); if (uv == null || !uv.isServer) { // spawning the object failed. return false; } return uv.AssignClientAuthority(conn); } static public void Spawn(GameObject obj, NetworkHash128 assetId) { if (!VerifyCanSpawn(obj)) { return; } NetworkIdentity id; if (GetNetworkIdentity(obj, out id)) { id.SetDynamicAssetId(assetId); } instance.SpawnObject(obj); } static public void Destroy(GameObject obj) { DestroyObject(obj); } static public void UnSpawn(GameObject obj) { UnSpawnObject(obj); } internal bool InvokeBytes(ULocalConnectionToServer conn, byte[] buffer, int numBytes, int channelId) { NetworkReader reader = new NetworkReader(buffer); reader.ReadInt16(); // size short msgType = reader.ReadInt16(); if (handlers.ContainsKey(msgType) && m_LocalConnection != null) { // this must be invoked with the connection to the client, not the client's connection to the server m_LocalConnection.InvokeHandler(msgType, reader, channelId); return true; } return false; } // invoked for local clients internal bool InvokeHandlerOnServer(ULocalConnectionToServer conn, short msgType, MessageBase msg, int channelId) { if (handlers.ContainsKey(msgType) && m_LocalConnection != null) { // write the message to a local buffer NetworkWriter writer = new NetworkWriter(); msg.Serialize(writer); // pass a reader (attached to local buffer) to handler NetworkReader reader = new NetworkReader(writer); // this must be invoked with the connection to the client, not the client's connection to the server m_LocalConnection.InvokeHandler(msgType, reader, channelId); return true; } if (LogFilter.logError) { Debug.LogError("Local invoke: Failed to find local connection to invoke handler on [connectionId=" + conn.connectionId + "] for MsgId:" + msgType); } return false; } static public GameObject FindLocalObject(NetworkInstanceId netId) { return instance.m_NetworkScene.FindLocalObject(netId); } static public Dictionary GetConnectionStats() { Dictionary stats = new Dictionary(); for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) { foreach (short k in conn.packetStats.Keys) { if (stats.ContainsKey(k)) { NetworkConnection.PacketStat s = stats[k]; s.count += conn.packetStats[k].count; s.bytes += conn.packetStats[k].bytes; stats[k] = s; } else { stats[k] = new NetworkConnection.PacketStat(conn.packetStats[k]); } } } } return stats; } static public void ResetConnectionStats() { for (int i = 0; i < connections.Count; i++) { var conn = connections[i]; if (conn != null) { conn.ResetStats(); } } } static public bool AddExternalConnection(NetworkConnection conn) { return instance.AddExternalConnectionInternal(conn); } bool AddExternalConnectionInternal(NetworkConnection conn) { if (conn.connectionId < 0) return false; if (conn.connectionId < connections.Count && connections[conn.connectionId] != null) { if (LogFilter.logError) { Debug.LogError("AddExternalConnection failed, already connection for id:" + conn.connectionId); } return false; } if (LogFilter.logDebug) { Debug.Log("AddExternalConnection external connection " + conn.connectionId); } m_SimpleServerSimple.SetConnectionAtIndex(conn); m_ExternalConnections.Add(conn.connectionId); conn.InvokeHandlerNoData(MsgType.Connect); return true; } static public void RemoveExternalConnection(int connectionId) { instance.RemoveExternalConnectionInternal(connectionId); } bool RemoveExternalConnectionInternal(int connectionId) { if (!m_ExternalConnections.Contains(connectionId)) { if (LogFilter.logError) { Debug.LogError("RemoveExternalConnection failed, no connection for id:" + connectionId); } return false; } if (LogFilter.logDebug) { Debug.Log("RemoveExternalConnection external connection " + connectionId); } var conn = m_SimpleServerSimple.FindConnection(connectionId); if (conn != null) { conn.RemoveObservers(); } m_SimpleServerSimple.RemoveConnectionAtIndex(connectionId); return true; } static bool ValidateSceneObject(NetworkIdentity netId) { if (netId.gameObject.hideFlags == HideFlags.NotEditable || netId.gameObject.hideFlags == HideFlags.HideAndDontSave) return false; #if UNITY_EDITOR if (UnityEditor.EditorUtility.IsPersistent(netId.gameObject)) return false; #endif // If not a scene object if (netId.sceneId.IsEmpty()) return false; return true; } static public bool SpawnObjects() { if (!active) return true; NetworkIdentity[] netIds = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < netIds.Length; i++) { var netId = netIds[i]; if (!ValidateSceneObject(netId)) continue; if (LogFilter.logDebug) { Debug.Log("SpawnObjects sceneId:" + netId.sceneId + " name:" + netId.gameObject.name); } netId.Reset(); netId.gameObject.SetActive(true); } for (int i = 0; i < netIds.Length; i++) { var netId = netIds[i]; if (!ValidateSceneObject(netId)) continue; Spawn(netId.gameObject); // these objects are server authority - even if "localPlayerAuthority" is set on them netId.ForceAuthority(true); } return true; } static void SendCrc(NetworkConnection targetConnection) { if (NetworkCRC.singleton == null) return; if (NetworkCRC.scriptCRCCheck == false) return; CRCMessage crcMsg = new CRCMessage(); // build entries List entries = new List(); foreach (var name in NetworkCRC.singleton.scripts.Keys) { CRCMessageEntry entry = new CRCMessageEntry(); entry.name = name; entry.channel = (byte)NetworkCRC.singleton.scripts[name]; entries.Add(entry); } crcMsg.scripts = entries.ToArray(); targetConnection.Send(MsgType.CRC, crcMsg); } #if ENABLE_UNET_HOST_MIGRATION [Obsolete("moved to NetworkMigrationManager")] #else [Obsolete("Removed")] #endif public void SendNetworkInfo(NetworkConnection targetConnection) { } class ServerSimpleWrapper : NetworkServerSimple { NetworkServer m_Server; public ServerSimpleWrapper(NetworkServer server) { m_Server = server; } public override void OnConnectError(int connectionId, byte error) { m_Server.GenerateConnectError(error); } public override void OnDataError(NetworkConnection conn, byte error) { m_Server.GenerateDataError(conn, error); } public override void OnDisconnectError(NetworkConnection conn, byte error) { m_Server.GenerateDisconnectError(conn, error); } public override void OnConnected(NetworkConnection conn) { m_Server.OnConnected(conn); } public override void OnDisconnected(NetworkConnection conn) { m_Server.OnDisconnected(conn); } public override void OnData(NetworkConnection conn, int receivedSize, int channelId) { m_Server.OnData(conn, receivedSize, channelId); } } }; } #endif //ENABLE_UNET