using System; using System.Linq; using FishNet.Managing; using FishNet.Object; using FishNet.Transporting; using FishNet.Transporting.Tugboat; using FishySteamworks; using Michsky.LSS; using Michsky.UI.Heat; using Obvious.Soap; using QFSW.QC; using UnityEngine; using UnityEngine.SceneManagement; public class MultiplayerManager : MonoBehaviour { public enum State { Offline = 0, EnteringMap = 1, EnteringSession = 2, InSession = 3, HostChange = 4, LeavingSession = 5 } [SerializeField] private ScriptableLobbyDataVariable scriptable_variable_LobbyData; [SerializeField] private ScriptableEventNoParam onLobbyReadyClick; [SerializeField] private ScriptableEventNoParam onServerDisconnectClick; [SerializeField] private ScriptableEventNoParam OnConnectionLost; [SerializeField] private ScriptableEventNoParam OnConnectionTimeout; [SerializeField] private ScriptableEventNoParam OnLeavingServer; [Tooltip("Time (in seconds) allowed for connection attempts before showing a warning popup")] [SerializeField] private float connectAttemptWarning = 10f; [Tooltip("Time (in seconds) allowed for connection attempts before closing multiplayer and kicking the player")] [SerializeField] private float connectAttemptKick = 15f; private State state; private LocalConnectionState serverConnectionState; private LocalConnectionState clientConnectionState; private float connectAttemptTimer; private bool connectAttemptWarningDisplayed; private bool isDebugHostRunning; private bool isDebugClientRunning; private NetworkManager NetworkManager => NetworkManager.Instances[0]; public static event Action OnOffline; public static event Action OnEnteringMap; public static event Action OnEnteringSession; public static event Action OnInSession; public static event Action OnHostChange; public static event Action OnLeaveingSession; private void OnLobbyReadyClick_OnRaised() { if (state != State.Offline || !scriptable_variable_LobbyData.Value.IsValid) { return; } ChapterManager chapterManager = UnityEngine.Object.FindFirstObjectByType(FindObjectsInactive.Include); if (!chapterManager) { return; } if (scriptable_variable_LobbyData.Value.GetMetadata().TryGetValue("Map", out var mapName) && !string.IsNullOrEmpty(mapName)) { ChangeState(State.EnteringMap); chapterManager.chapters.FirstOrDefault((ChapterManager.ChapterItem c) => c.chapterID == mapName)?.onPlay?.Invoke(); } } private void OnServerDisconnectClick_OnRaised() { if (state != State.Offline && state != State.LeavingSession) { if (scriptable_variable_LobbyData.Value.IsValid) { scriptable_variable_LobbyData.Value.Leave(); } OnLeavingServer.Raise(); ChangeState(State.LeavingSession); } } private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs state) { serverConnectionState = state.ConnectionState; } private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs state) { clientConnectionState = state.ConnectionState; } private void OnEnable() { onLobbyReadyClick.OnRaised += OnLobbyReadyClick_OnRaised; onServerDisconnectClick.OnRaised += OnServerDisconnectClick_OnRaised; NetworkManager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState; NetworkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; } private void OnDisable() { onLobbyReadyClick.OnRaised -= OnLobbyReadyClick_OnRaised; onServerDisconnectClick.OnRaised -= OnServerDisconnectClick_OnRaised; NetworkManager.ServerManager.OnServerConnectionState -= ServerManager_OnServerConnectionState; NetworkManager.ClientManager.OnClientConnectionState -= ClientManager_OnClientConnectionState; } private void Update() { TransitionTo_Offline(); TransitionTo_EnteringSession(); SetHostAndClientConnection(); TransitionTo_InSession(); TransitionTo_HostChange(); TransitionTo_LeavingSession(); } [Command("start-host", "Used to test multiplayer in two editor instances on the same machine.", Platform.AllPlatforms, MonoTargetType.Single)] public void StartHost() { if (state == State.Offline && IsMapSceneLoaded() && IsServerFullyDisconnected() && IsClientFullyDisconnected() && !isDebugHostRunning && !isDebugClientRunning) { isDebugHostRunning = true; global::FishySteamworks.FishySteamworks component = NetworkManager.GetComponent(); Tugboat component2 = NetworkManager.GetComponent(); component.enabled = false; component2.enabled = true; NetworkManager.TransportManager.Transport = component2; NetworkManager.ServerManager.StartConnection(); NetworkManager.ClientManager.StartConnection(); ChangeState(State.EnteringSession); } } [Command("start-client", "Used to test multiplayer in two editor instances on the same machine.", Platform.AllPlatforms, MonoTargetType.Single)] public void StartClient() { if (state == State.Offline && IsMapSceneLoaded() && IsServerFullyDisconnected() && IsClientFullyDisconnected() && !isDebugHostRunning && !isDebugClientRunning) { isDebugClientRunning = true; global::FishySteamworks.FishySteamworks component = NetworkManager.GetComponent(); Tugboat component2 = NetworkManager.GetComponent(); component.enabled = false; component2.enabled = true; NetworkManager.TransportManager.Transport = component2; NetworkManager.ClientManager.StartConnection(); ChangeState(State.EnteringSession); } } [Command("stop-host-client", "Used to test multiplayer in two editor instances on the same machine.", Platform.AllPlatforms, MonoTargetType.Single)] public void StopHostClient() { if (state == State.InSession && (isDebugHostRunning || isDebugClientRunning)) { isDebugHostRunning = false; isDebugClientRunning = false; if (NetworkManager.IsServerStarted) { NetworkManager.ServerManager.StopConnection(sendDisconnectMessage: true); } if (NetworkManager.IsClientStarted) { NetworkManager.ClientManager.StopConnection(); } ChangeState(State.Offline); } } private void TransitionTo_Offline() { if (state == State.LeavingSession) { StopFishNet(); if (scriptable_variable_LobbyData.Value.IsValid) { scriptable_variable_LobbyData.Value.Leave(); } connectAttemptTimer = 0f; connectAttemptWarningDisplayed = false; ChangeState(State.Offline); } } private void TransitionTo_EnteringSession() { if (state == State.EnteringMap && IsMapSceneLoaded()) { ChangeState(State.EnteringSession); } } private void SetHostAndClientConnection() { if ((state == State.EnteringSession || state == State.HostChange) && IsMapSceneLoaded() && scriptable_variable_LobbyData.Value.IsValid && IsServerFullyDisconnected() && IsClientFullyDisconnected()) { NetworkManager.GetComponent().SetClientAddress(scriptable_variable_LobbyData.Value.Owner.user.id.ToString()); Debug.Log("(MultiplayerManager) Starting connection to host ID: " + scriptable_variable_LobbyData.Value.Owner.user.id.ToString()); if (scriptable_variable_LobbyData.Value.IsOwner) { NetworkManager.ServerManager.StartConnection(); NetworkManager.ClientManager.StartConnection(); } else { NetworkManager.ClientManager.StartConnection(); } } } private void TransitionTo_InSession() { if ((state != State.EnteringSession && state != State.HostChange) || !IsMapSceneLoaded()) { return; } bool num = NetworkManager.IsClientStarted && IsClientFullyConnected(); bool flag = !NetworkManager.IsServerStarted || IsServerFullyConnected(); if (num && flag) { NetworkObject firstObject = NetworkManager.ClientManager.Connection.FirstObject; if (firstObject != null && firstObject.TryGetComponent(out var component) && component.ClientStarted) { ChangeState(State.InSession); } } } private void TransitionTo_HostChange() { if (state == State.InSession && !IsClientFullyConnected()) { StopFishNet(); ChangeState(State.HostChange); } } private void TransitionTo_LeavingSession() { if (state == State.EnteringSession || state == State.HostChange) { connectAttemptTimer += Time.deltaTime; } else { connectAttemptTimer = 0f; } if (state == State.EnteringSession || state == State.InSession || state == State.HostChange) { if (connectAttemptTimer >= connectAttemptWarning && !connectAttemptWarningDisplayed) { OnConnectionTimeout.Raise(); connectAttemptWarningDisplayed = true; } else if (connectAttemptTimer >= connectAttemptKick) { OnConnectionLost.Raise(); ChangeState(State.LeavingSession); } else if (!IsMapSceneLoaded() || IsLobbyInvalid()) { OnConnectionLost.Raise(); ChangeState(State.LeavingSession); } } bool IsLobbyInvalid() { if (!scriptable_variable_LobbyData.Value.IsValid && !isDebugHostRunning) { return !isDebugClientRunning; } return false; } } private bool IsMapSceneLoaded() { bool result = false; Scene activeScene = SceneManager.GetActiveScene(); if (LSS_LoadingScreen.instance == null && activeScene.isLoaded && !activeScene.name.Equals("Main Menu") && !activeScene.name.Equals("Gameplay")) { result = true; } return result; } private bool IsServerFullyDisconnected() { if (!NetworkManager.IsServerStarted) { if (serverConnectionState != 0) { return serverConnectionState == LocalConnectionState.Stopped; } return true; } return false; } private bool IsServerFullyConnected() { if (NetworkManager.IsServerStarted) { return serverConnectionState == LocalConnectionState.Started; } return false; } private bool IsClientFullyDisconnected() { if (!NetworkManager.IsClientStarted) { if (clientConnectionState != 0) { return clientConnectionState == LocalConnectionState.Stopped; } return true; } return false; } private bool IsClientFullyConnected() { if (NetworkManager.IsClientStarted) { return clientConnectionState == LocalConnectionState.Started; } return false; } private void StopFishNet() { if (NetworkManager.IsClientStarted) { NetworkManager.ClientManager.StopConnection(); } if (NetworkManager.IsServerStarted) { NetworkManager.ServerManager.StopConnection(sendDisconnectMessage: true); } } private void ChangeState(State newState) { Debug.Log($"(MultiplayerManager) Change state to {newState}"); state = newState; switch (state) { case State.Offline: MultiplayerManager.OnOffline?.Invoke(); break; case State.EnteringMap: MultiplayerManager.OnEnteringMap?.Invoke(); break; case State.EnteringSession: MultiplayerManager.OnEnteringSession?.Invoke(); break; case State.InSession: MultiplayerManager.OnInSession?.Invoke(); break; case State.HostChange: MultiplayerManager.OnHostChange?.Invoke(); break; case State.LeavingSession: MultiplayerManager.OnLeaveingSession?.Invoke(); break; } } }