using System; using System.Collections.Generic; using Unity.Collections; using Unity.Netcode; using UnityEngine; using VContainer; namespace Unity.Multiplayer.Samples.BossRoom { public enum ConnectStatus { Undefined, Success, //client successfully connected. This may also be a successful reconnect. ServerFull, //can't join, server is already at capacity. LoggedInAgain, //logged in on a separate client, causing this one to be kicked out. UserRequestedDisconnect, //Intentional Disconnect triggered by the user. GenericDisconnect, //server disconnected, but no specific reason given. Reconnecting, //client lost connection and is attempting to reconnect. IncompatibleBuildType, //client build type is incompatible with server. HostEndedSession, //host intentionally ended the session. StartHostFailed, // server failed to bind StartClientFailed // failed to connect to server and/or invalid network endpoint } public struct ReconnectMessage { public int CurrentAttempt; public int MaxAttempt; public ReconnectMessage(int currentAttempt, int maxAttempt) { CurrentAttempt = currentAttempt; MaxAttempt = maxAttempt; } } public struct ConnectionEventMessage : INetworkSerializeByMemcpy { public ConnectStatus ConnectStatus; public FixedPlayerName PlayerName; } [Serializable] public class ConnectionPayload { public string playerId; public string playerName; public bool isDebug; } /// /// This state machine handles connection through the NetworkManager. It is responsible for listening to /// NetworkManger callbacks and other outside calls and redirecting them to the current ConnectionState object. /// public class ConnectionManager : MonoBehaviour { public enum ServerType : byte { Undefined = 0, DedicatedServer, ClientHostedServer } ConnectionState m_CurrentState; [SerializeField] NetworkManager m_NetworkManager; public NetworkManager NetworkManager => m_NetworkManager; [SerializeField] NetworkObject m_GameState; public NetworkObject GameState => m_GameState; [Inject] IObjectResolver m_Resolver; public int MaxConnectedPlayers = 8; internal readonly OfflineState m_Offline = new OfflineState(); internal readonly ClientConnectingState m_ClientConnecting = new ClientConnectingState(); internal readonly ClientConnectedState m_ClientConnected = new ClientConnectedState(); internal readonly ClientReconnectingState m_ClientReconnecting = new ClientReconnectingState(); internal readonly DisconnectingWithReasonState m_DisconnectingWithReason = new DisconnectingWithReasonState(); internal readonly ServerStartingState m_ServerStarting = new ServerStartingState(); internal readonly ServerListeningState m_ServerListening = new ServerListeningState(); internal readonly HostStartingState m_HostStarting = new HostStartingState(); internal readonly HostListeningState m_HostListening = new HostListeningState(); public ServerType IsConnectedToHost { get; set; } void Awake() { DontDestroyOnLoad(gameObject); } void Start() { List states = new() { m_Offline, m_ClientConnecting, m_ClientConnected, m_ClientReconnecting, m_DisconnectingWithReason, m_HostStarting, m_HostListening, m_ServerListening, m_ServerStarting }; foreach (var connectionState in states) { m_Resolver.Inject(connectionState); } m_CurrentState = m_Offline; NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; NetworkManager.OnServerStarted += OnServerStarted; NetworkManager.ConnectionApprovalCallback += ApprovalCheck; } void OnDestroy() { NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback; NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; NetworkManager.OnServerStarted -= OnServerStarted; NetworkManager.ConnectionApprovalCallback -= ApprovalCheck; } internal void ChangeState(ConnectionState nextState) { Debug.Log($"Changed connection state from {m_CurrentState.GetType().Name} to {nextState.GetType().Name}."); if (m_CurrentState != null) { m_CurrentState.Exit(); } m_CurrentState = nextState; m_CurrentState.Enter(); } void OnClientDisconnectCallback(ulong clientId) { m_CurrentState.OnClientDisconnect(clientId); } void OnClientConnectedCallback(ulong clientId) { m_CurrentState.OnClientConnected(clientId); } void OnServerStarted() { m_CurrentState.OnServerStarted(); } void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) { m_CurrentState.ApprovalCheck(request, response); } public void StartClientLobby(string playerName) { m_CurrentState.StartClientLobby(playerName); } public void StartClientIp(string playerName, string ipaddress, int port) { m_CurrentState.StartClientIP(playerName, ipaddress, port); } public void StartHostLobby(string playerName) { m_CurrentState.StartHostLobby(playerName); } public void StartHostIp(string playerName, string ipaddress, int port) { m_CurrentState.StartHostIP(playerName, ipaddress, port); } public void StartServerIP(string ipaddress, int port) { m_CurrentState.StartServerIP(ipaddress, port); } public void RequestShutdown() { m_CurrentState.OnUserRequestedShutdown(); } /// /// Registers the message handler for custom named messages. This should only be done once StartClient has been /// called (start client will initialize NetworkSceneManager and CustomMessagingManager) /// This will override the message handler each time and will use this instance's method. /// public void RegisterCustomMessages() { NetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(nameof(ReceiveServerToClientSetDisconnectReason_CustomMessage), ReceiveServerToClientSetDisconnectReason_CustomMessage); NetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(nameof(ReceiveServertoClientSuccessPayload_CustomMessage), ReceiveServertoClientSuccessPayload_CustomMessage); } void ReceiveServerToClientSetDisconnectReason_CustomMessage(ulong clientID, FastBufferReader reader) { reader.ReadValueSafe(out ConnectStatus status); m_CurrentState.OnDisconnectReasonReceived(status); } /// /// Sends a DisconnectReason to all connected clients. This should only be done on the server, prior to disconnecting the clients. /// /// The reason for the upcoming disconnect. public static void SendServerToAllClientsSetDisconnectReason(ConnectStatus status) { var writer = new FastBufferWriter(sizeof(ConnectStatus), Allocator.Temp); writer.WriteValueSafe(status); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessageToAll(nameof(ReceiveServerToClientSetDisconnectReason_CustomMessage), writer); } /// /// Sends a DisconnectReason to the indicated client. This should only be done on the server, prior to disconnecting the client. /// /// id of the client to send to /// The reason for the upcoming disconnect. public static void SendServerToClientSetDisconnectReason(ulong clientID, ConnectStatus status) { var writer = new FastBufferWriter(sizeof(ConnectStatus), Allocator.Temp); writer.WriteValueSafe(status); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage(nameof(ReceiveServerToClientSetDisconnectReason_CustomMessage), clientID, writer); } internal void ReceiveServertoClientSuccessPayload_CustomMessage(ulong clientID, FastBufferReader reader) { reader.ReadValueSafe(out ServerType isHost); IsConnectedToHost = isHost; } internal static void SendServertoClientSuccessPayload(ulong clientID, ServerType isHost) { var writer = new FastBufferWriter(sizeof(bool), Allocator.Temp); writer.WriteValueSafe(isHost); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage(nameof(ReceiveServertoClientSuccessPayload_CustomMessage), clientID, writer); } } }