mirror of
				https://github.com/DerTyp7/defrain-shooter-unity.git
				synced 2025-10-31 05:27:07 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			397 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			397 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Net;
 | |
| using System.Net.Sockets;
 | |
| using System.Threading.Tasks;
 | |
| using UnityEngine;
 | |
| 
 | |
| // Based on https://github.com/EnlightenedOne/MirrorNetworkDiscovery
 | |
| // forked from https://github.com/in0finite/MirrorNetworkDiscovery
 | |
| // Both are MIT Licensed
 | |
| 
 | |
| namespace Mirror.Discovery
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Base implementation for Network Discovery.  Extend this component
 | |
|     /// to provide custom discovery with game specific data
 | |
|     /// <see cref="NetworkDiscovery">NetworkDiscovery</see> for a sample implementation
 | |
|     /// </summary>
 | |
|     [DisallowMultipleComponent]
 | |
|     [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-discovery")]
 | |
|     public abstract class NetworkDiscoveryBase<Request, Response> : MonoBehaviour
 | |
|         where Request : NetworkMessage
 | |
|         where Response : NetworkMessage
 | |
|     {
 | |
|         public static bool SupportedOnThisPlatform { get { return Application.platform != RuntimePlatform.WebGLPlayer; } }
 | |
| 
 | |
|         // each game should have a random unique handshake,  this way you can tell if this is the same game or not
 | |
|         [HideInInspector]
 | |
|         public long secretHandshake;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The UDP port the server will listen for multi-cast messages")]
 | |
|         protected int serverBroadcastListenPort = 47777;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, broadcasts a discovery request every ActiveDiscoveryInterval seconds")]
 | |
|         public bool enableActiveDiscovery = true;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Time in seconds between multi-cast messages")]
 | |
|         [Range(1, 60)]
 | |
|         float ActiveDiscoveryInterval = 3;
 | |
| 
 | |
|         protected UdpClient serverUdpClient;
 | |
|         protected UdpClient clientUdpClient;
 | |
| 
 | |
| #if UNITY_EDITOR
 | |
|         void OnValidate()
 | |
|         {
 | |
|             if (secretHandshake == 0)
 | |
|             {
 | |
|                 secretHandshake = RandomLong();
 | |
|                 UnityEditor.Undo.RecordObject(this, "Set secret handshake");
 | |
|             }
 | |
|         }
 | |
| #endif
 | |
| 
 | |
|         public static long RandomLong()
 | |
|         {
 | |
|             int value1 = UnityEngine.Random.Range(int.MinValue, int.MaxValue);
 | |
|             int value2 = UnityEngine.Random.Range(int.MinValue, int.MaxValue);
 | |
|             return value1 + ((long)value2 << 32);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// virtual so that inheriting classes' Start() can call base.Start() too
 | |
|         /// </summary>
 | |
|         public virtual void Start()
 | |
|         {
 | |
|             // Server mode? then start advertising
 | |
| #if UNITY_SERVER
 | |
|             AdvertiseServer();
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         // Ensure the ports are cleared no matter when Game/Unity UI exits
 | |
|         void OnApplicationQuit()
 | |
|         {
 | |
|             //Debug.Log("NetworkDiscoveryBase OnApplicationQuit");
 | |
|             Shutdown();
 | |
|         }
 | |
| 
 | |
|         void OnDisable()
 | |
|         {
 | |
|             //Debug.Log("NetworkDiscoveryBase OnDisable");
 | |
|             Shutdown();
 | |
|         }
 | |
| 
 | |
|         void OnDestroy()
 | |
|         {
 | |
|             //Debug.Log("NetworkDiscoveryBase OnDestroy");
 | |
|             Shutdown();
 | |
|         }
 | |
| 
 | |
|         void Shutdown()
 | |
|         {
 | |
|             if (serverUdpClient != null)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     serverUdpClient.Close();
 | |
|                 }
 | |
|                 catch (Exception)
 | |
|                 {
 | |
|                     // it is just close, swallow the error
 | |
|                 }
 | |
| 
 | |
|                 serverUdpClient = null;
 | |
|             }
 | |
| 
 | |
|             if (clientUdpClient != null)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     clientUdpClient.Close();
 | |
|                 }
 | |
|                 catch (Exception)
 | |
|                 {
 | |
|                     // it is just close, swallow the error
 | |
|                 }
 | |
| 
 | |
|                 clientUdpClient = null;
 | |
|             }
 | |
| 
 | |
|             CancelInvoke();
 | |
|         }
 | |
| 
 | |
|         #region Server
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Advertise this server in the local network
 | |
|         /// </summary>
 | |
|         public void AdvertiseServer()
 | |
|         {
 | |
|             if (!SupportedOnThisPlatform)
 | |
|                 throw new PlatformNotSupportedException("Network discovery not supported in this platform");
 | |
| 
 | |
|             StopDiscovery();
 | |
| 
 | |
|             // Setup port -- may throw exception
 | |
|             serverUdpClient = new UdpClient(serverBroadcastListenPort)
 | |
|             {
 | |
|                 EnableBroadcast = true,
 | |
|                 MulticastLoopback = false
 | |
|             };
 | |
| 
 | |
|             // listen for client pings
 | |
|             _ = ServerListenAsync();
 | |
|         }
 | |
| 
 | |
|         public async Task ServerListenAsync()
 | |
|         {
 | |
|             while (true)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     await ReceiveRequestAsync(serverUdpClient);
 | |
|                 }
 | |
|                 catch (ObjectDisposedException)
 | |
|                 {
 | |
|                     // socket has been closed
 | |
|                     break;
 | |
|                 }
 | |
|                 catch (Exception)
 | |
|                 {
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         async Task ReceiveRequestAsync(UdpClient udpClient)
 | |
|         {
 | |
|             // only proceed if there is available data in network buffer, or otherwise Receive() will block
 | |
|             // average time for UdpClient.Available : 10 us
 | |
| 
 | |
|             UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
 | |
| 
 | |
|             using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
 | |
|             {
 | |
|                 long handshake = networkReader.ReadLong();
 | |
|                 if (handshake != secretHandshake)
 | |
|                 {
 | |
|                     // message is not for us
 | |
|                     throw new ProtocolViolationException("Invalid handshake");
 | |
|                 }
 | |
| 
 | |
|                 Request request = networkReader.Read<Request>();
 | |
| 
 | |
|                 ProcessClientRequest(request, udpReceiveResult.RemoteEndPoint);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Reply to the client to inform it of this server
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// Override if you wish to ignore server requests based on
 | |
|         /// custom criteria such as language, full server game mode or difficulty
 | |
|         /// </remarks>
 | |
|         /// <param name="request">Request coming from client</param>
 | |
|         /// <param name="endpoint">Address of the client that sent the request</param>
 | |
|         protected virtual void ProcessClientRequest(Request request, IPEndPoint endpoint)
 | |
|         {
 | |
|             Response info = ProcessRequest(request, endpoint);
 | |
| 
 | |
|             if (info == null)
 | |
|                 return;
 | |
| 
 | |
|             using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     writer.WriteLong(secretHandshake);
 | |
| 
 | |
|                     writer.Write(info);
 | |
| 
 | |
|                     ArraySegment<byte> data = writer.ToArraySegment();
 | |
|                     // signature matches
 | |
|                     // send response
 | |
|                     serverUdpClient.Send(data.Array, data.Count, endpoint);
 | |
|                 }
 | |
|                 catch (Exception ex)
 | |
|                 {
 | |
|                     Debug.LogException(ex, this);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Process the request from a client
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// Override if you wish to provide more information to the clients
 | |
|         /// such as the name of the host player
 | |
|         /// </remarks>
 | |
|         /// <param name="request">Request coming from client</param>
 | |
|         /// <param name="endpoint">Address of the client that sent the request</param>
 | |
|         /// <returns>The message to be sent back to the client or null</returns>
 | |
|         protected abstract Response ProcessRequest(Request request, IPEndPoint endpoint);
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Client
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Start Active Discovery
 | |
|         /// </summary>
 | |
|         public void StartDiscovery()
 | |
|         {
 | |
|             if (!SupportedOnThisPlatform)
 | |
|                 throw new PlatformNotSupportedException("Network discovery not supported in this platform");
 | |
| 
 | |
|             StopDiscovery();
 | |
| 
 | |
|             try
 | |
|             {
 | |
|                 // Setup port
 | |
|                 clientUdpClient = new UdpClient(0)
 | |
|                 {
 | |
|                     EnableBroadcast = true,
 | |
|                     MulticastLoopback = false
 | |
|                 };
 | |
|             }
 | |
|             catch (Exception)
 | |
|             {
 | |
|                 // Free the port if we took it
 | |
|                 //Debug.LogError("NetworkDiscoveryBase StartDiscovery Exception");
 | |
|                 Shutdown();
 | |
|                 throw;
 | |
|             }
 | |
| 
 | |
|             _ = ClientListenAsync();
 | |
| 
 | |
|             if (enableActiveDiscovery) InvokeRepeating(nameof(BroadcastDiscoveryRequest), 0, ActiveDiscoveryInterval);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Stop Active Discovery
 | |
|         /// </summary>
 | |
|         public void StopDiscovery()
 | |
|         {
 | |
|             //Debug.Log("NetworkDiscoveryBase StopDiscovery");
 | |
|             Shutdown();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Awaits for server response
 | |
|         /// </summary>
 | |
|         /// <returns>ClientListenAsync Task</returns>
 | |
|         public async Task ClientListenAsync()
 | |
|         {
 | |
|             // while clientUpdClient to fix: 
 | |
|             // https://github.com/vis2k/Mirror/pull/2908
 | |
|             //
 | |
|             // If, you cancel discovery the clientUdpClient is set to null.
 | |
|             // However, nothing cancels ClientListenAsync. If we change the if(true)
 | |
|             // to check if the client is null. You can properly cancel the discovery, 
 | |
|             // and kill the listen thread.
 | |
|             //
 | |
|             // Prior to this fix, if you cancel the discovery search. It crashes the 
 | |
|             // thread, and is super noisy in the output. As well as causes issues on 
 | |
|             // the quest.
 | |
|             while (clientUdpClient != null)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     await ReceiveGameBroadcastAsync(clientUdpClient);
 | |
|                 }
 | |
|                 catch (ObjectDisposedException)
 | |
|                 {
 | |
|                     // socket was closed, no problem
 | |
|                     return;
 | |
|                 }
 | |
|                 catch (Exception ex)
 | |
|                 {
 | |
|                     Debug.LogException(ex);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends discovery request from client
 | |
|         /// </summary>
 | |
|         public void BroadcastDiscoveryRequest()
 | |
|         {
 | |
|             if (clientUdpClient == null)
 | |
|                 return;
 | |
| 
 | |
|             if (NetworkClient.isConnected)
 | |
|             {
 | |
|                 StopDiscovery();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort);
 | |
| 
 | |
|             using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
 | |
|             {
 | |
|                 writer.WriteLong(secretHandshake);
 | |
| 
 | |
|                 try
 | |
|                 {
 | |
|                     Request request = GetRequest();
 | |
| 
 | |
|                     writer.Write(request);
 | |
| 
 | |
|                     ArraySegment<byte> data = writer.ToArraySegment();
 | |
| 
 | |
|                     clientUdpClient.SendAsync(data.Array, data.Count, endPoint);
 | |
|                 }
 | |
|                 catch (Exception)
 | |
|                 {
 | |
|                     // It is ok if we can't broadcast to one of the addresses
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Create a message that will be broadcasted on the network to discover servers
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// Override if you wish to include additional data in the discovery message
 | |
|         /// such as desired game mode, language, difficulty, etc... </remarks>
 | |
|         /// <returns>An instance of ServerRequest with data to be broadcasted</returns>
 | |
|         protected virtual Request GetRequest() => default;
 | |
| 
 | |
|         async Task ReceiveGameBroadcastAsync(UdpClient udpClient)
 | |
|         {
 | |
|             // only proceed if there is available data in network buffer, or otherwise Receive() will block
 | |
|             // average time for UdpClient.Available : 10 us
 | |
| 
 | |
|             UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
 | |
| 
 | |
|             using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
 | |
|             {
 | |
|                 if (networkReader.ReadLong() != secretHandshake)
 | |
|                     return;
 | |
| 
 | |
|                 Response response = networkReader.Read<Response>();
 | |
| 
 | |
|                 ProcessResponse(response, udpReceiveResult.RemoteEndPoint);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Process the answer from a server
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// A client receives a reply from a server, this method processes the
 | |
|         /// reply and raises an event
 | |
|         /// </remarks>
 | |
|         /// <param name="response">Response that came from the server</param>
 | |
|         /// <param name="endpoint">Address of the server that replied</param>
 | |
|         protected abstract void ProcessResponse(Response response, IPEndPoint endpoint);
 | |
| 
 | |
|         #endregion
 | |
|     }
 | |
| }
 | 
