context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using kcp2k;
  using UnityEngine;
  using UnityEngine.SceneManagement;
  using UnityEngine.Serialization;
  
  namespace Mirror
  {
      public enum PlayerSpawnMethod { Random, RoundRobin }
      public enum NetworkManagerMode { Offline, ServerOnly, ClientOnly, Host }
  
      [DisallowMultipleComponent]
      [AddComponentMenu("Network/NetworkManager")]
      [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-manager")]
      public class NetworkManager : MonoBehaviour
      {
          /// <summary>Enable to keep NetworkManager alive when changing scenes.</summary>
          // This should be set if your game has a single NetworkManager that exists for the lifetime of the process. If there is a NetworkManager in each scene, then this should not be set.</para>
          [Header("Configuration")]
          [FormerlySerializedAs("m_DontDestroyOnLoad")]
          [Tooltip("Should the Network Manager object be persisted through scene changes?")]
          public bool dontDestroyOnLoad = true;
  
          // Deprecated 2021-03-10
          // Temporary bool to allow Network Manager to persist to offline scene
          // Based on Discord convo, BigBox is invoking StopHost in startup sequence, bouncing the server and clients back to offline scene, which resets Network Manager.
          // Request is for a checkbox to persist Network Manager to offline scene, despite the collision and warning.
          [Obsolete("This was added temporarily and will be removed in a future release.")]
          [Tooltip("Should the Network Manager object be persisted through scene change to the offline scene?")]
          public bool PersistNetworkManagerToOfflineScene;
  
          /// <summary>Multiplayer games should always run in the background so the network doesn't time out.</summary>
          [FormerlySerializedAs("m_RunInBackground")]
          [Tooltip("Multiplayer games should always run in the background so the network doesn't time out.")]
          public bool runInBackground = true;
  
          /// <summary>Should the server auto-start when 'Server Build' is checked in build settings</summary>
          [Tooltip("Should the server auto-start when 'Server Build' is checked in build settings")]
          [FormerlySerializedAs("startOnHeadless")]
          public bool autoStartServerBuild = true;
  
          /// <summary>Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.</summary>
          [Tooltip("Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")]
          public int serverTickRate = 30;
  
          /// <summary>Automatically switch to this scene upon going offline (on start / on disconnect / on shutdown).</summary>
          [Header("Scene Management")]
          [Scene]
          [FormerlySerializedAs("m_OfflineScene")]
          [Tooltip("Scene that Mirror will switch to when the client or server is stopped")]
          public string offlineScene = "";
  
          /// <summary>Automatically switch to this scene upon going online (after connect/startserver).</summary>
          [Scene]
          [FormerlySerializedAs("m_OnlineScene")]
          [Tooltip("Scene that Mirror will switch to when the server is started. Clients will recieve a Scene Message to load the server's current scene when they connect.")]
          public string onlineScene = "";
  
          // transport layer
          [Header("Network Info")]
          [Tooltip("Transport component attached to this object that server and client will use to connect")]
          [SerializeField]
          protected Transport transport;
  
          /// <summary>Server's address for clients to connect to.</summary>
          [FormerlySerializedAs("m_NetworkAddress")]
          [Tooltip("Network Address where the client should connect to the server. Server does not use this for anything.")]
          public string networkAddress = "localhost";
  
          /// <summary>The maximum number of concurrent network connections to support.</summary>
          [FormerlySerializedAs("m_MaxConnections")]
          [Tooltip("Maximum number of concurrent connections.")]
          public int maxConnections = 100;
  
          // Deprecated 2021-05-10
          [Obsolete("Transport is responsible for timeouts.")]
          public bool disconnectInactiveConnections;
  
          // Deprecated 2021-05-10
          [Obsolete("Transport is responsible for timeouts. Configure the Transport's timeout setting instead.")]
          public float disconnectInactiveTimeout = 60f;
  
          [Header("Authentication")]
          [Tooltip("Authentication component attached to this object")]
          public NetworkAuthenticator authenticator;
  
          /// <summary>The default prefab to be used to create player objects on the server.</summary>
          // Player objects are created in the default handler for AddPlayer() on
          // the server. Implementing OnServerAddPlayer overrides this behaviour.
          [Header("Player Object")]
          [FormerlySerializedAs("m_PlayerPrefab")]
          [Tooltip("Prefab of the player object. Prefab must have a Network Identity component. May be an empty game object or a full avatar.")]
          public GameObject playerPrefab;
  
          /// <summary>Enable to automatically create player objects on connect and on scene change.</summary>
          [FormerlySerializedAs("m_AutoCreatePlayer")]
          [Tooltip("Should Mirror automatically spawn the player after scene change?")]
          public bool autoCreatePlayer = true;
  
          /// <summary>Where to spawn players.</summary>
          [FormerlySerializedAs("m_PlayerSpawnMethod")]
          [Tooltip("Round Robin or Random order of Start Position selection")]
          public PlayerSpawnMethod playerSpawnMethod;
  
          /// <summary>Prefabs that can be spawned over the network need to be registered here.</summary>
          [FormerlySerializedAs("m_SpawnPrefabs"), HideInInspector]
          public List<GameObject> spawnPrefabs = new List<GameObject>();
  
          /// <summary>List of transforms populated by NetworkStartPositions</summary>
          public static List<Transform> startPositions = new List<Transform>();
          public static int startPositionIndex;
  
          /// <summary>The one and only NetworkManager</summary>
          public static NetworkManager singleton { get; private set; }
  
          /// <summary>Number of active player objects across all connections on the server.</summary>
          public int numPlayers => NetworkServer.connections.Count(kv => kv.Value.identity != null);
  
          /// <summary>True if the server is running or client is connected/connecting.</summary>
          [NonSerialized]
          public bool isNetworkActive;
  
          // TODO remove this
          static NetworkConnection clientReadyConnection;
  
          /// <summary>True if the client loaded a new scene when connecting to the server.</summary>
          // This is set before OnClientConnect is called, so it can be checked
          // there to perform different logic if a scene load occurred.
          [NonSerialized]
          public bool clientLoadedScene;
  
          // helper enum to know if we started the networkmanager as server/client/host.
          // -> this is necessary because when StartHost changes server scene to
          //    online scene, FinishLoadScene is called and the host client isn't
          //    connected yet (no need to connect it before server was fully set up).
          //    in other words, we need this to know which mode we are running in
          //    during FinishLoadScene.
          public NetworkManagerMode mode { get; private set; }
  
          // virtual so that inheriting classes' OnValidate() can call base.OnValidate() too
          public virtual void OnValidate()
          {
              // add transport if there is none yet. makes upgrading easier.
              if (transport == null)
              {
                  // was a transport added yet? if not, add one
                  transport = GetComponent<Transport>();
                  if (transport == null)
                  {
                      transport = gameObject.AddComponent<KcpTransport>();
                      Debug.Log("NetworkManager: added default Transport because there was none yet.");
                  }
  #if UNITY_EDITOR
                  // For some insane reason, this line fails when building unless wrapped in this define. Stupid but true.
                  // error CS0234: The type or namespace name 'Undo' does not exist in the namespace 'UnityEditor' (are you missing an assembly reference?)
                  UnityEditor.Undo.RecordObject(gameObject, "Added default Transport");
  #endif
              }
  
              // always >= 0
              maxConnections = Mathf.Max(maxConnections, 0);
  
              if (playerPrefab != null && playerPrefab.GetComponent<NetworkIdentity>() == null)
              {
                  Debug.LogError("NetworkManager - Player Prefab must have a NetworkIdentity.");
                  playerPrefab = null;
              }
  
              // This avoids the mysterious "Replacing existing prefab with assetId ... Old prefab 'Player', New prefab 'Player'" warning.
              if (playerPrefab != null && spawnPrefabs.Contains(playerPrefab))
              {
                  Debug.LogWarning("NetworkManager - Player Prefab should not be added to Registered Spawnable Prefabs list...removed it.");
                  spawnPrefabs.Remove(playerPrefab);
              }
          }
  
          // virtual so that inheriting classes' Awake() can call base.Awake() too
          public virtual void Awake()
          {
              // Don't allow collision-destroyed second instance to continue.
              if (!InitializeSingleton()) return;
  
              Debug.Log("Mirror | mirror-networking.com | discord.gg/N9QVxbM");
  
              // Set the networkSceneName to prevent a scene reload
              // if client connection to server fails.
              networkSceneName = offlineScene;
  
              // setup OnSceneLoaded callback
              SceneManager.sceneLoaded += OnSceneLoaded;
          }
  
          // virtual so that inheriting classes' Start() can call base.Start() too
          public virtual void Start()
          {
              // headless mode? then start the server
              // can't do this in Awake because Awake is for initialization.
              // some transports might not be ready until Start.
              //
              // (tick rate is applied in StartServer!)
  #if UNITY_SERVER
              if (autoStartServerBuild)
              {
                  StartServer();
              }
  #endif
          }
  
          // virtual so that inheriting classes' LateUpdate() can call base.LateUpdate() too
          public virtual void LateUpdate()
          {
              UpdateScene();
          }
  
          // keep the online scene change check in a separate function
          bool IsServerOnlineSceneChangeNeeded()
          {
              // Only change scene if the requested online scene is not blank, and is not already loaded
              return !string.IsNullOrEmpty(onlineScene) && !IsSceneActive(onlineScene) && onlineScene != offlineScene;
          }
  
          public static bool IsSceneActive(string scene)
          {
              Scene activeScene = SceneManager.GetActiveScene();
              return activeScene.path == scene || activeScene.name == scene;
          }
  
          // full server setup code, without spawning objects yet
          void SetupServer()
          {
              // Debug.Log("NetworkManager SetupServer");
              InitializeSingleton();
  
              if (runInBackground)
                  Application.runInBackground = true;
  
              if (authenticator != null)
              {
                  authenticator.OnStartServer();
                  authenticator.OnServerAuthenticated.AddListener(OnServerAuthenticated);
              }
  
              ConfigureServerFrameRate();
  
              // Copy auto-disconnect settings to NetworkServer
  #pragma warning disable 618
              NetworkServer.disconnectInactiveTimeout = disconnectInactiveTimeout;
              NetworkServer.disconnectInactiveConnections = disconnectInactiveConnections;
  #pragma warning restore 618
  
              // start listening to network connections
              NetworkServer.Listen(maxConnections);
  
              // call OnStartServer AFTER Listen, so that NetworkServer.active is
              // true and we can call NetworkServer.Spawn in OnStartServer
              // overrides.
              // (useful for loading & spawning stuff from database etc.)
              //
              // note: there is no risk of someone connecting after Listen() and
              //       before OnStartServer() because this all runs in one thread
              //       and we don't start processing connects until Update.
              OnStartServer();
  
              // this must be after Listen(), since that registers the default message handlers
              RegisterServerMessages();
  
              isNetworkActive = true;
          }
  
          /// <summary>Starts the server, listening for incoming connections.</summary>
          public void StartServer()
          {
              if (NetworkServer.active)
              {
                  Debug.LogWarning("Server already started.");
                  return;
              }
  
              mode = NetworkManagerMode.ServerOnly;
  
              // StartServer is inherently ASYNCHRONOUS (=doesn't finish immediately)
              //
              // Here is what it does:
              //   Listen
              //   if onlineScene:
              //       LoadSceneAsync
              //       ...
              //       FinishLoadSceneServerOnly
              //           SpawnObjects
              //   else:
              //       SpawnObjects
              //
              // there is NO WAY to make it synchronous because both LoadSceneAsync
              // and LoadScene do not finish loading immediately. as long as we
              // have the onlineScene feature, it will be asynchronous!
  
              SetupServer();
  
              // scene change needed? then change scene and spawn afterwards.
              if (IsServerOnlineSceneChangeNeeded())
              {
                  ServerChangeScene(onlineScene);
              }
              // otherwise spawn directly
              else
              {
                  NetworkServer.SpawnObjects();
              }
          }
  
          /// <summary>Starts the client, connects it to the server with networkAddress.</summary>
          public void StartClient()
          {
              if (NetworkClient.active)
              {
                  Debug.LogWarning("Client already started.");
                  return;
              }
  
              mode = NetworkManagerMode.ClientOnly;
  
              InitializeSingleton();
  
              if (runInBackground)
                  Application.runInBackground = true;
  
              if (authenticator != null)
              {
                  authenticator.OnStartClient();
                  authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
              }
  
              isNetworkActive = true;
  
              // In case this is a headless client...
              ConfigureServerFrameRate();
  
              RegisterClientMessages();
  
              if (string.IsNullOrEmpty(networkAddress))
              {
                  Debug.LogError("Must set the Network Address field in the manager");
                  return;
              }
              // Debug.Log("NetworkManager StartClient address:" + networkAddress);
  
              NetworkClient.Connect(networkAddress);
  
              OnStartClient();
          }
  
          /// <summary>Starts the client, connects it to the server via Uri</summary>
          public void StartClient(Uri uri)
          {
              if (NetworkClient.active)
              {
                  Debug.LogWarning("Client already started.");
                  return;
              }
  
              mode = NetworkManagerMode.ClientOnly;
  
              InitializeSingleton();
  
              if (runInBackground)
                  Application.runInBackground = true;
  
              if (authenticator != null)
              {
                  authenticator.OnStartClient();
                  authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
              }
  
              isNetworkActive = true;
  
              RegisterClientMessages();
  
              // Debug.Log("NetworkManager StartClient address:" + uri);
              networkAddress = uri.Host;
  
              NetworkClient.Connect(uri);
  
              OnStartClient();
          }
  
          /// <summary>Starts a network "host" - a server and client in the same application.</summary>
          public void StartHost()
          {
              if (NetworkServer.active || NetworkClient.active)
              {
                  Debug.LogWarning("Server or Client already started.");
                  return;
              }
  
              mode = NetworkManagerMode.Host;
  
              // StartHost is inherently ASYNCHRONOUS (=doesn't finish immediately)
              //
              // Here is what it does:
              //   Listen
              //   ConnectHost
              //   if onlineScene:
              //       LoadSceneAsync
              //       ...
              //       FinishLoadSceneHost
              //           FinishStartHost
              //               SpawnObjects
              //               StartHostClient      <= not guaranteed to happen after SpawnObjects if onlineScene is set!
              //                   ClientAuth
              //                       success: server sends changescene msg to client
              //   else:
              //       FinishStartHost
              //
              // there is NO WAY to make it synchronous because both LoadSceneAsync
              // and LoadScene do not finish loading immediately. as long as we
              // have the onlineScene feature, it will be asynchronous!
  
              // setup server first
              SetupServer();
  
              // call OnStartHost AFTER SetupServer. this way we can use
              // NetworkServer.Spawn etc. in there too. just like OnStartServer
              // is called after the server is actually properly started.
              OnStartHost();
  
              // scene change needed? then change scene and spawn afterwards.
              // => BEFORE host client connects. if client auth succeeds then the
              //    server tells it to load 'onlineScene'. we can't do that if
              //    server is still in 'offlineScene'. so load on server first.
              if (IsServerOnlineSceneChangeNeeded())
              {
                  // call FinishStartHost after changing scene.
                  finishStartHostPending = true;
                  ServerChangeScene(onlineScene);
              }
              // otherwise call FinishStartHost directly
              else
              {
                  FinishStartHost();
              }
          }
  
          // This may be set true in StartHost and is evaluated in FinishStartHost
          bool finishStartHostPending;
  
          // FinishStartHost is guaranteed to be called after the host server was
          // fully started and all the asynchronous StartHost magic is finished
          // (= scene loading), or immediately if there was no asynchronous magic.
          //
          // note: we don't really need FinishStartClient/FinishStartServer. the
          //       host version is enough.
          void FinishStartHost()
          {
              // ConnectHost needs to be called BEFORE SpawnObjects:
              // https://github.com/vis2k/Mirror/pull/1249/
              // -> this sets NetworkServer.localConnection.
              // -> localConnection needs to be set before SpawnObjects because:
              //    -> SpawnObjects calls OnStartServer in all NetworkBehaviours
              //       -> OnStartServer might spawn an object and set [SyncVar(hook="OnColorChanged")] object.color = green;
              //          -> this calls SyncVar.set (generated by Weaver), which has
              //             a custom case for host mode (because host mode doesn't
              //             get OnDeserialize calls, where SyncVar hooks are usually
              //             called):
              //
              //               if (!SyncVarEqual(value, ref color))
              //               {
              //                   if (NetworkServer.localClientActive && !getSyncVarHookGuard(1uL))
              //                   {
              //                       setSyncVarHookGuard(1uL, value: true);
              //                       OnColorChangedHook(value);
              //                       setSyncVarHookGuard(1uL, value: false);
              //                   }
              //                   SetSyncVar(value, ref color, 1uL);
              //               }
              //
              //          -> localClientActive needs to be true, otherwise the hook
              //             isn't called in host mode!
              //
              // TODO call this after spawnobjects and worry about the syncvar hook fix later?
              NetworkClient.ConnectHost();
  
              // server scene was loaded. now spawn all the objects
              NetworkServer.SpawnObjects();
  
              // connect client and call OnStartClient AFTER server scene was
              // loaded and all objects were spawned.
              // DO NOT do this earlier. it would cause race conditions where a
              // client will do things before the server is even fully started.
              //Debug.Log("StartHostClient called");
              StartHostClient();
          }
  
          void StartHostClient()
          {
              //Debug.Log("NetworkManager ConnectLocalClient");
  
              if (authenticator != null)
              {
                  authenticator.OnStartClient();
                  authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
              }
  
              networkAddress = "localhost";
              NetworkServer.ActivateHostScene();
              RegisterClientMessages();
  
              // ConnectLocalServer needs to be called AFTER RegisterClientMessages
              // (https://github.com/vis2k/Mirror/pull/1249/)
              NetworkClient.ConnectLocalServer();
  
              OnStartClient();
          }
  
          /// <summary>This stops both the client and the server that the manager is using.</summary>
          public void StopHost()
          {
              OnStopHost();
  
              // calling OnTransportDisconnected was needed to fix
              // https://github.com/vis2k/Mirror/issues/1515
              // so that the host client receives a DisconnectMessage
              // TODO reevaluate if this is still needed after all the disconnect
              //      fixes, and try to put this into LocalConnection.Disconnect!
              NetworkServer.OnTransportDisconnected(NetworkConnection.LocalConnectionId);
  
              StopClient();
              StopServer();
          }
  
          /// <summary>Stops the server from listening and simulating the game.</summary>
          public void StopServer()
          {
              if (!NetworkServer.active)
                  return;
  
              if (authenticator != null)
              {
                  authenticator.OnServerAuthenticated.RemoveListener(OnServerAuthenticated);
                  authenticator.OnStopServer();
              }
  
              // Get Network Manager out of DDOL before going to offline scene
              // to avoid collision and let a fresh Network Manager be created.
              // IMPORTANT: .gameObject can be null if StopClient is called from
              //            OnApplicationQuit or from tests!
  #pragma warning disable 618
              if (gameObject != null && !PersistNetworkManagerToOfflineScene &&
                  gameObject.scene.name == "DontDestroyOnLoad"
                  && !string.IsNullOrEmpty(offlineScene)
                  && SceneManager.GetActiveScene().path != offlineScene)
                  SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
  #pragma warning restore 618
  
              OnStopServer();
  
              //Debug.Log("NetworkManager StopServer");
              isNetworkActive = false;
              NetworkServer.Shutdown();
  
              // set offline mode BEFORE changing scene so that FinishStartScene
              // doesn't think we need initialize anything.
              mode = NetworkManagerMode.Offline;
  
              if (!string.IsNullOrEmpty(offlineScene))
              {
                  ServerChangeScene(offlineScene);
              }
  
              startPositionIndex = 0;
  
              networkSceneName = "";
          }
  
          /// <summary>Stops and disconnects the client.</summary>
          public void StopClient()
          {
              if (authenticator != null)
              {
                  authenticator.OnClientAuthenticated.RemoveListener(OnClientAuthenticated);
                  authenticator.OnStopClient();
              }
  
              // Get Network Manager out of DDOL before going to offline scene
              // to avoid collision and let a fresh Network Manager be created.
              // IMPORTANT: .gameObject can be null if StopClient is called from
              //            OnApplicationQuit or from tests!
  #pragma warning disable 618
              if (gameObject != null && !PersistNetworkManagerToOfflineScene &&
                  gameObject.scene.name == "DontDestroyOnLoad"
                  && !string.IsNullOrEmpty(offlineScene)
                  && SceneManager.GetActiveScene().path != offlineScene)
                  SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
  #pragma warning restore 618
  
              OnStopClient();
  
              //Debug.Log("NetworkManager StopClient");
              isNetworkActive = false;
  
              // shutdown client
              NetworkClient.Disconnect();
              NetworkClient.Shutdown();
  
              // set offline mode BEFORE changing scene so that FinishStartScene
              // doesn't think we need initialize anything.
              mode = NetworkManagerMode.Offline;
  
              // If this is the host player, StopServer will already be changing scenes.
              // Check loadingSceneAsync to ensure we don't double-invoke the scene change.
              // Check if NetworkServer.active because we can get here via Disconnect before server has started to change scenes.
              if (!string.IsNullOrEmpty(offlineScene) && !IsSceneActive(offlineScene) && loadingSceneAsync == null && !NetworkServer.active)
              {
                  ClientChangeScene(offlineScene, SceneOperation.Normal);
              }
  
              networkSceneName = "";
          }
  
          // called when quitting the application by closing the window / pressing
          // stop in the editor. virtual so that inheriting classes'
          // OnApplicationQuit() can call base.OnApplicationQuit() too
          public virtual void OnApplicationQuit()
          {
              // stop client first
              // (we want to send the quit packet to the server instead of waiting
              //  for a timeout)
              if (NetworkClient.isConnected)
              {
                  StopClient();
                  //Debug.Log("OnApplicationQuit: stopped client");
              }
  
              // stop server after stopping client (for proper host mode stopping)
              if (NetworkServer.active)
              {
                  StopServer();
                  //Debug.Log("OnApplicationQuit: stopped server");
              }
          }
  
          /// <summary>Set the frame rate for a headless server. Override to disable or modify.</summary>
          public virtual void ConfigureServerFrameRate()
          {
              // only set framerate for server build
  #if UNITY_SERVER
              Application.targetFrameRate = serverTickRate;
              // Debug.Log("Server Tick Rate set to: " + Application.targetFrameRate + " Hz.");
  #endif
          }
  
          bool InitializeSingleton()
          {
              if (singleton != null && singleton == this)
                  return true;
  
              if (dontDestroyOnLoad)
              {
                  if (singleton != null)
                  {
                      Debug.LogWarning("Multiple NetworkManagers detected in the scene. Only one NetworkManager can exist at a time. The duplicate NetworkManager will be destroyed.");
                      Destroy(gameObject);
  
                      // Return false to not allow collision-destroyed second instance to continue.
                      return false;
                  }
                  Debug.Log("NetworkManager created singleton (DontDestroyOnLoad)");
                  singleton = this;
  
gt:                 if (Application.isPlaying) DontDestroyOnLoad(gameObject);