using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using HG;
using RoR2.ContentManagement;
using RoR2.ConVar;
using RoR2.Orbs;
using RoR2.UI;
using Unity;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;

namespace RoR2.Networking;

public abstract class NetworkManagerSystem : NetworkManager
{
	public enum CanPlayOnlineState
	{
		Pending,
		Yes,
		No
	}

	private class NetLogLevelConVar : BaseConVar
	{
		private static NetLogLevelConVar cvNetLogLevel = new NetLogLevelConVar("net_loglevel", ConVarFlags.Engine, null, "Network log verbosity.");

		public NetLogLevelConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValue)
		{
			if (TextSerialization.TryParseInvariant(newValue, out int result))
			{
				LogFilter.currentLogLevel = result;
			}
		}

		public override string GetString()
		{
			return TextSerialization.ToStringInvariant(LogFilter.currentLogLevel);
		}
	}

	private class SvListenConVar : BaseConVar
	{
		private static SvListenConVar cvSvListen = new SvListenConVar("sv_listen", ConVarFlags.Engine, null, "Whether or not the server will accept connections from other players.");

		public SvListenConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValue)
		{
			if (!NetworkServer.active && TextSerialization.TryParseInvariant(newValue, out int result))
			{
				NetworkServer.dontListen = result == 0;
			}
		}

		public override string GetString()
		{
			if (!NetworkServer.dontListen)
			{
				return "1";
			}
			return "0";
		}
	}

	public class SvMaxPlayersConVar : BaseConVar
	{
		public static readonly SvMaxPlayersConVar instance = new SvMaxPlayersConVar("sv_maxplayers", ConVarFlags.Engine, null, "Maximum number of players allowed.");

		public int intValue => NetworkManager.singleton.maxConnections;

		public SvMaxPlayersConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValue)
		{
			if (NetworkServer.active)
			{
				throw new ConCommandException("Cannot change this convar while the server is running.");
			}
			if ((bool)NetworkManager.singleton && TextSerialization.TryParseInvariant(newValue, out int result))
			{
				NetworkManager.singleton.maxConnections = Math.Min(Math.Max(result, 1), RoR2Application.hardMaxPlayers);
			}
		}

		public override string GetString()
		{
			if (!NetworkManager.singleton)
			{
				return "1";
			}
			return TextSerialization.ToStringInvariant(NetworkManager.singleton.maxConnections);
		}
	}

	private class KickMessage : MessageBase
	{
		public BaseKickReason kickReason;

		public KickMessage()
		{
		}

		public KickMessage(BaseKickReason kickReason)
		{
			this.kickReason = kickReason;
		}

		public override void Serialize(NetworkWriter writer)
		{
			base.Serialize(writer);
			string text = kickReason?.GetType().FullName ?? string.Empty;
			writer.Write(text);
			if (!(text == string.Empty))
			{
				kickReason.Serialize(writer);
			}
		}

		public override void Deserialize(NetworkReader reader)
		{
			base.Deserialize(reader);
			string typeName = reader.ReadString();
			Type type = null;
			try
			{
				type = Type.GetType(typeName);
			}
			catch
			{
			}
			if (type == null || !typeof(BaseKickReason).IsAssignableFrom(type) || type.IsAbstract)
			{
				kickReason = null;
				return;
			}
			kickReason = (BaseKickReason)Activator.CreateInstance(type);
			kickReason.Deserialize(reader);
		}

		public bool TryGetDisplayTokenAndFormatParams(out string token, out object[] formatArgs)
		{
			if (kickReason == null)
			{
				token = null;
				formatArgs = null;
				return false;
			}
			kickReason.GetDisplayTokenAndFormatParams(out token, out formatArgs);
			return true;
		}
	}

	public abstract class BaseKickReason : MessageBase
	{
		public BaseKickReason()
		{
		}

		public abstract void GetDisplayTokenAndFormatParams(out string token, out object[] formatArgs);
	}

	public class SimpleLocalizedKickReason : BaseKickReason
	{
		public string baseToken;

		public string[] formatArgs;

		public SimpleLocalizedKickReason()
		{
		}

		public SimpleLocalizedKickReason(string baseToken, params string[] formatArgs)
		{
			this.baseToken = baseToken;
			this.formatArgs = formatArgs;
		}

		public override void GetDisplayTokenAndFormatParams(out string token, out object[] formatArgs)
		{
			token = baseToken;
			object[] array = this.formatArgs;
			formatArgs = array;
		}

		public override void Serialize(NetworkWriter writer)
		{
			writer.Write(baseToken);
			GeneratedNetworkCode._WriteArrayString_None(writer, formatArgs);
		}

		public override void Deserialize(NetworkReader reader)
		{
			baseToken = reader.ReadString();
			formatArgs = GeneratedNetworkCode._ReadArrayString_None(reader);
		}
	}

	public class ModMismatchKickReason : BaseKickReason
	{
		public string[] serverModList = Array.Empty<string>();

		public ModMismatchKickReason()
		{
		}

		public ModMismatchKickReason(IEnumerable<string> serverModList)
		{
			this.serverModList = serverModList.ToArray();
		}

		public override void GetDisplayTokenAndFormatParams(out string token, out object[] formatArgs)
		{
			IEnumerable<string> networkModList = NetworkModCompatibilityHelper.networkModList;
			IEnumerable<string> values = networkModList.Except(serverModList);
			IEnumerable<string> values2 = serverModList.Except(networkModList);
			token = "KICK_REASON_MOD_MISMATCH";
			object[] array = new string[2]
			{
				string.Join("\n", values),
				string.Join("\n", values2)
			};
			formatArgs = array;
		}

		public override void Serialize(NetworkWriter writer)
		{
			GeneratedNetworkCode._WriteArrayString_None(writer, serverModList);
		}

		public override void Deserialize(NetworkReader reader)
		{
			serverModList = GeneratedNetworkCode._ReadArrayString_None(reader);
		}
	}

	protected class AddPlayerMessage : MessageBase
	{
		public PlatformID id;

		public byte[] steamAuthTicketData;

		public override void Serialize(NetworkWriter writer)
		{
			GeneratedNetworkCode._WritePlatformID_None(writer, id);
			writer.WriteBytesFull(steamAuthTicketData);
		}

		public override void Deserialize(NetworkReader reader)
		{
			id = GeneratedNetworkCode._ReadPlatformID_None(reader);
			steamAuthTicketData = reader.ReadBytesAndSize();
		}
	}

	public class SvHostNameConVar : BaseConVar
	{
		public static readonly SvHostNameConVar instance = new SvHostNameConVar("sv_hostname", ConVarFlags.None, "", "The public name to use for the server if hosting.");

		private string value = "NAME";

		public event Action<string> onValueChanged;

		public SvHostNameConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValue)
		{
			value = newValue;
			this.onValueChanged?.Invoke(newValue);
		}

		public override string GetString()
		{
			return value;
		}
	}

	public class SvPortConVar : BaseConVar
	{
		public static readonly SvPortConVar instance = new SvPortConVar("sv_port", ConVarFlags.Engine, null, "The port to use for the server if hosting.");

		public ushort value
		{
			get
			{
				if (!singleton)
				{
					return 0;
				}
				return (ushort)singleton.networkPort;
			}
		}

		public SvPortConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValueString)
		{
			if (NetworkServer.active)
			{
				throw new ConCommandException("Cannot change this convar while the server is running.");
			}
			if (TextSerialization.TryParseInvariant(newValueString, out ushort result))
			{
				singleton.networkPort = result;
			}
		}

		public override string GetString()
		{
			return value.ToString();
		}
	}

	public class SvIPConVar : BaseConVar
	{
		public static readonly SvIPConVar instance = new SvIPConVar("sv_ip", ConVarFlags.Engine, null, "The IP for the server to bind to if hosting.");

		public SvIPConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValueString)
		{
			if (NetworkServer.active)
			{
				throw new ConCommandException("Cannot change this convar while the server is running.");
			}
			singleton.serverBindAddress = newValueString;
		}

		public override string GetString()
		{
			if (!singleton)
			{
				return string.Empty;
			}
			return singleton.serverBindAddress;
		}
	}

	public class SvPasswordConVar : BaseConVar
	{
		public static readonly SvPasswordConVar instance = new SvPasswordConVar("sv_password", ConVarFlags.None, "", "The password to use for the server if hosting.");

		public string value { get; private set; }

		public event Action<string> onValueChanged;

		public SvPasswordConVar(string name, ConVarFlags flags, string defaultValue, string helpText)
			: base(name, flags, defaultValue, helpText)
		{
		}

		public override void SetString(string newValue)
		{
			if (newValue == null)
			{
				newValue = "";
			}
			if (!(value == newValue))
			{
				value = newValue;
				this.onValueChanged?.Invoke(value);
			}
		}

		public override string GetString()
		{
			return value;
		}
	}

	protected class AddressablesChangeSceneAsyncOperation : IChangeSceneAsyncOperation
	{
		private static AsyncOperationHandle<SceneInstance>? previousLoadOperation;

		private AsyncOperationHandle<SceneInstance> srcOperation;

		private bool srcOperationIsDone;

		private SceneInstance sceneInstance;

		private bool isActivated;

		private bool _allowSceneActivation;

		public bool isDone { get; private set; }

		public bool allowSceneActivation
		{
			get
			{
				return _allowSceneActivation;
			}
			set
			{
				if (_allowSceneActivation != value)
				{
					_allowSceneActivation = value;
					ActivateIfReady();
				}
			}
		}

		public AddressablesChangeSceneAsyncOperation(object key, LoadSceneMode loadMode, bool activateOnLoad)
		{
			srcOperation = Addressables.LoadSceneAsync(key, loadMode, activateOnLoad);
			previousLoadOperation = srcOperation;
			srcOperation.Completed += delegate(AsyncOperationHandle<SceneInstance> v)
			{
				srcOperationIsDone = true;
				sceneInstance = v.Result;
				ActivateIfReady();
			};
			allowSceneActivation = activateOnLoad;
		}

		private void ActivateIfReady()
		{
			if (srcOperationIsDone && !isActivated)
			{
				sceneInstance.ActivateAsync().completed += delegate
				{
					isDone = true;
				};
			}
		}
	}

	protected static readonly FieldInfo loadingSceneAsyncFieldInfo;

	protected float _unpredictedServerFixedTime;

	protected float _unpredictedServerFixedTimeSmoothed;

	protected float unpredictedServerFixedTimeVelocity;

	protected float _unpredictedServerFrameTime;

	protected float _unpredictedServerFrameTimeSmoothed;

	protected float unpredictedServerFrameTimeVelocity;

	protected static FloatConVar cvNetTimeSmoothRate;

	public float debugServerTime;

	public float debugRTT;

	protected bool isSinglePlayer;

	protected bool actedUponDesiredHost;

	protected float lastDesiredHostSetTime = float.NegativeInfinity;

	protected HostDescription _desiredHost;

	private static bool wasFading;

	protected static readonly string[] sceneWhiteList;

	public static readonly StringConVar cvClPassword;

	protected GameObject serverNetworkSessionInstance;

	protected GameObject serverPauseStopControllerInstance;

	private static NetworkReader DefaultNetworkReader;

	private static readonly FloatConVar svTimeTransmitInterval;

	private float timeTransmitTimer;

	protected bool serverShuttingDown;

	private static readonly Queue<NetworkConnection> clientsReadyDuringLevelTransition;

	public static readonly StringConVar cvSvCustomTags;

	protected static bool isLoadingScene
	{
		get
		{
			IChangeSceneAsyncOperation changeSceneAsyncOperation = (IChangeSceneAsyncOperation)loadingSceneAsyncFieldInfo.GetValue(null);
			if (changeSceneAsyncOperation != null)
			{
				return !changeSceneAsyncOperation.isDone;
			}
			return false;
		}
	}

	public new static NetworkManagerSystem singleton => (NetworkManagerSystem)NetworkManager.singleton;

	public float unpredictedServerFixedTime => _unpredictedServerFixedTime;

	public float unpredictedServerFixedTimeSmoothed => _unpredictedServerFixedTimeSmoothed;

	public float serverFixedTime => unpredictedServerFixedTimeSmoothed + filteredClientRttFixed;

	public float unpredictedServerFrameTime => _unpredictedServerFrameTime;

	public float unpredictedServerFrameTimeSmoothed => _unpredictedServerFrameTimeSmoothed;

	public float serverFrameTime => unpredictedServerFrameTimeSmoothed + filteredClientRttFrame;

	public HostDescription desiredHost
	{
		get
		{
			return _desiredHost;
		}
		set
		{
			if (!_desiredHost.Equals(value))
			{
				_desiredHost = value;
				actedUponDesiredHost = false;
				lastDesiredHostSetTime = Time.unscaledTime;
				Debug.LogFormat("NetworkManagerSystem.desiredHost={0}", _desiredHost.ToString());
			}
		}
	}

	public bool clientHasConfirmedQuit { get; private set; }

	protected bool clientIsConnecting
	{
		get
		{
			if (client?.connection != null)
			{
				return !client.isConnected;
			}
			return false;
		}
	}

	public float clientRttFixed { get; private set; }

	public float clientRttFrame { get; private set; }

	public float filteredClientRttFixed { get; private set; }

	public float filteredClientRttFrame { get; private set; }

	public bool isHost { get; protected set; }

	public PlatformID clientP2PId { get; protected set; } = PlatformID.nil;


	public PlatformID serverP2PId { get; protected set; } = PlatformID.nil;


	public static event Action onStartGlobal;

	public static event Action<NetworkClient> onStartClientGlobal;

	public static event Action onStopClientGlobal;

	public static event Action<NetworkConnection> onClientConnectGlobal;

	public static event Action<NetworkConnection> onClientDisconnectGlobal;

	public static event Action onStartHostGlobal;

	public static event Action onStopHostGlobal;

	public static event Action onStartServerGlobal;

	public static event Action onStopServerGlobal;

	public static event Action<NetworkConnection> onServerConnectGlobal;

	public static event Action<NetworkConnection> onServerDisconnectGlobal;

	public static event Action<string> onServerSceneChangedGlobal;

	static NetworkManagerSystem()
	{
		cvNetTimeSmoothRate = new FloatConVar("net_time_smooth_rate", ConVarFlags.None, "1.05", "The smoothing rate for the network time.");
		wasFading = false;
		sceneWhiteList = new string[3] { "title", "crystalworld", "logbook" };
		cvClPassword = new StringConVar("cl_password", ConVarFlags.None, "", "The password to use when joining a passworded server.");
		DefaultNetworkReader = new NetworkReader();
		svTimeTransmitInterval = new FloatConVar("sv_time_transmit_interval", ConVarFlags.Cheat, (1f / 60f).ToString(), "How long it takes for the server to issue a time update to clients.");
		clientsReadyDuringLevelTransition = new Queue<NetworkConnection>();
		cvSvCustomTags = new StringConVar("sv_custom_tags", ConVarFlags.None, "", "Comma-delimited custom tags to report to the server browser.");
		loadingSceneAsyncFieldInfo = typeof(NetworkManager).GetField("s_LoadingSceneAsync", BindingFlags.Static | BindingFlags.NonPublic);
		if (loadingSceneAsyncFieldInfo == null)
		{
			Debug.LogError("NetworkManager.s_LoadingSceneAsync field could not be found! Make sure to provide a proper implementation for this version of Unity.");
		}
	}

	public virtual void Init(NetworkManagerConfiguration configurationComponent)
	{
		base.dontDestroyOnLoad = configurationComponent.DontDestroyOnLoad;
		base.runInBackground = configurationComponent.RunInBackground;
		base.logLevel = configurationComponent.LogLevel;
		base.offlineScene = configurationComponent.OfflineScene;
		base.onlineScene = configurationComponent.OnlineScene;
		base.playerPrefab = configurationComponent.PlayerPrefab;
		base.autoCreatePlayer = configurationComponent.AutoCreatePlayer;
		base.playerSpawnMethod = configurationComponent.PlayerSpawnMethod;
		base.spawnPrefabs.Clear();
		base.spawnPrefabs.AddRange(configurationComponent.SpawnPrefabs);
		base.customConfig = configurationComponent.CustomConfig;
		base.maxConnections = configurationComponent.MaxConnections;
		base.channels.Clear();
		base.channels.AddRange(configurationComponent.QosChannels);
		Run.onRunStartGlobal += ResetRunTime;
	}

	private void ResetRunTime(Run run)
	{
		if (Run.instance != null)
		{
			Run.instance.NetworkfixedTime = 0f;
			Run.instance.time = 0f;
			Run.FixedTimeStamp.Update();
		}
	}

	protected void InitializeTime()
	{
		_unpredictedServerFixedTime = 0f;
		_unpredictedServerFixedTimeSmoothed = 0f;
		unpredictedServerFixedTimeVelocity = 1f;
		_unpredictedServerFrameTime = 0f;
		_unpredictedServerFrameTimeSmoothed = 0f;
		unpredictedServerFrameTimeVelocity = 1f;
	}

	protected void UpdateTime(ref float targetValue, ref float currentValue, ref float velocity, float deltaTime)
	{
		if (!(deltaTime <= 0f))
		{
			targetValue += deltaTime;
			float num = (targetValue - currentValue) / deltaTime;
			float num2 = 1f;
			if (velocity == 0f || Mathf.Abs(num) > num2 * 3f)
			{
				currentValue = targetValue;
				velocity = num2;
			}
			else
			{
				currentValue += velocity * deltaTime;
				velocity = Mathf.MoveTowards(velocity, num, cvNetTimeSmoothRate.value * deltaTime);
			}
		}
	}

	protected static NetworkUser[] GetConnectionNetworkUsers(NetworkConnection conn)
	{
		List<PlayerController> playerControllers = conn.playerControllers;
		NetworkUser[] array = new NetworkUser[playerControllers.Count];
		for (int i = 0; i < playerControllers.Count; i++)
		{
			array[i] = playerControllers[i].gameObject.GetComponent<NetworkUser>();
		}
		return array;
	}

	protected abstract void Start();

	protected void OnDestroy()
	{
		typeof(NetworkManager).GetMethod("OnDestroy", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(this, null);
	}

	public IEnumerator FireOnStartGlobalEvent()
	{
		while (Console.instance == null)
		{
			yield return null;
		}
		NetworkManagerSystem.onStartGlobal?.Invoke();
	}

	protected void FixedUpdate()
	{
		UpdateTime(ref _unpredictedServerFixedTime, ref _unpredictedServerFixedTimeSmoothed, ref unpredictedServerFixedTimeVelocity, Time.fixedDeltaTime);
		FixedUpdateServer();
		FixedUpdateClient();
		debugServerTime = unpredictedServerFixedTime;
		debugRTT = clientRttFrame;
	}

	protected abstract void Update();

	protected abstract void EnsureDesiredHost();

	public abstract void ForceCloseAllConnections();

	public bool IsSinglePlayer()
	{
		return isSinglePlayer;
	}

	public void SetSinglePlayerNetworkState()
	{
		NetworkServer.dontListen = true;
		desiredHost = default(HostDescription);
		isSinglePlayer = true;
	}

	public void ResetDesiredHost()
	{
		actedUponDesiredHost = false;
		singleton.desiredHost = default(HostDescription);
		isSinglePlayer = false;
	}

	public abstract void CreateLocalLobby();

	public virtual IEnumerator<CanPlayOnlineState> CanPlayOnline()
	{
		yield return CanPlayOnlineState.Yes;
	}

	public override void OnStartClient(NetworkClient newClient)
	{
		base.OnStartClient(newClient);
		InitializeTime();
		RegisterPrefabs(ContentManager.bodyPrefabs);
		RegisterPrefabs(ContentManager.masterPrefabs);
		RegisterPrefabs(ContentManager.projectilePrefabs);
		RegisterPrefabs(ContentManager.networkedObjectPrefabs);
		RegisterPrefabs(ContentManager.gameModePrefabs);
		ClientScene.RegisterPrefab(LegacyResourcesAPI.Load<GameObject>("Prefabs/NetworkSession"));
		ClientScene.RegisterPrefab(LegacyResourcesAPI.Load<GameObject>("Prefabs/Stage"));
		NetworkMessageHandlerAttribute.RegisterClientMessages(newClient);
		NetworkManagerSystem.onStartClientGlobal?.Invoke(newClient);
		static void RegisterPrefabs(GameObject[] prefabs)
		{
			for (int i = 0; i < prefabs.Length; i++)
			{
				ClientScene.RegisterPrefab(prefabs[i]);
			}
		}
	}

	public override void OnStopClient()
	{
		try
		{
			NetworkManagerSystem.onStopClientGlobal?.Invoke();
		}
		catch (Exception message)
		{
			Debug.LogError(message);
		}
		foreach (NetworkClient allClient in NetworkClient.allClients)
		{
			allClient?.connection?.Disconnect();
		}
		ForceCloseAllConnections();
		if (actedUponDesiredHost)
		{
			singleton.desiredHost = HostDescription.none;
		}
		base.OnStopClient();
		OrbStorageUtility.Clear();
	}

	public override void OnClientConnect(NetworkConnection conn)
	{
		base.OnClientConnect(conn);
		clientRttFrame = 0f;
		filteredClientRttFixed = 0f;
		ClientSendAuth(conn);
		ClientSetPlayers(conn);
		NetworkManagerSystem.onClientConnectGlobal?.Invoke(conn);
	}

	public override void OnClientDisconnect(NetworkConnection conn)
	{
		base.OnClientDisconnect(conn);
		NetworkManagerSystem.onClientDisconnectGlobal?.Invoke(conn);
	}

	public void ClientAddPlayer(short playerControllerId, NetworkConnection connection = null)
	{
		foreach (PlayerController localPlayer in ClientScene.localPlayers)
		{
			if (localPlayer.playerControllerId == playerControllerId && localPlayer.IsValid && (bool)localPlayer.gameObject)
			{
				Debug.LogFormat("Player {0} already added, aborting.", playerControllerId);
				return;
			}
		}
		Debug.LogFormat("Adding local player controller {0} on connection {1}", playerControllerId, connection);
		AddPlayerMessage extraMessage = CreateClientAddPlayerMessage();
		ClientScene.AddPlayer(connection, playerControllerId, extraMessage);
	}

	protected abstract AddPlayerMessage CreateClientAddPlayerMessage();

	protected void UpdateClient()
	{
		UpdateCheckInactiveConnections();
		if (client?.connection != null)
		{
			filteredClientRttFrame = RttManager.GetConnectionFrameSmoothedRtt(client.connection);
			clientRttFrame = RttManager.GetConnectionRTT(client.connection);
		}
		bool flag = string.IsNullOrEmpty(NetworkManager.networkSceneName);
		bool flag2 = (client != null && !ClientScene.ready && !flag) || isLoadingScene;
		if (wasFading != flag2)
		{
			if (flag2)
			{
				FadeToBlackManager.fadeCount++;
			}
			else
			{
				FadeToBlackManager.fadeCount--;
			}
			wasFading = flag2;
		}
	}

	protected abstract void UpdateCheckInactiveConnections();

	protected abstract void StartClient(PlatformID serverID);

	public abstract bool IsConnectedToServer(PlatformID serverID);

	private void FixedUpdateClient()
	{
		if (NetworkClient.active && client != null && client?.connection != null && client.connection.isConnected)
		{
			NetworkConnection connection = client.connection;
			filteredClientRttFixed = RttManager.GetConnectionFixedSmoothedRtt(connection);
			clientRttFixed = RttManager.GetConnectionRTT(connection);
			if (!Util.ConnectionIsLocal(connection))
			{
				RttManager.Ping(connection, QosChannelIndex.ping.intVal);
			}
		}
	}

	public override void OnClientSceneChanged(NetworkConnection conn)
	{
		string text = NetworkManager.networkSceneName;
		List<string> list = new List<string>();
		bool flag = false;
		for (int i = 0; i < SceneManager.sceneCount; i++)
		{
			string text2 = SceneManager.GetSceneAt(i).name;
			list.Add(text2);
			if (text2 == text)
			{
				flag = true;
			}
		}
		if (flag)
		{
			base.autoCreatePlayer = false;
			base.OnClientSceneChanged(conn);
			ClientSetPlayers(conn);
			FadeToBlackManager.ForceFullBlack();
		}
	}

	private void ClientSendAuth(NetworkConnection conn)
	{
		ClientAuthData data = new ClientAuthData();
		PlatformAuth(ref data, conn);
		data.password = cvClPassword.value;
		data.version = RoR2Application.GetBuildId();
		data.modHash = NetworkModCompatibilityHelper.networkModHash;
		conn.Send(74, data);
	}

	protected abstract void PlatformAuth(ref ClientAuthData data, NetworkConnection conn);

	protected void ClientSetPlayers(NetworkConnection conn)
	{
		ReadOnlyCollection<LocalUser> readOnlyLocalUsersList = LocalUserManager.readOnlyLocalUsersList;
		for (int i = 0; i < readOnlyLocalUsersList.Count; i++)
		{
			if (i == 0 || RoR2Application.isInLocalMultiPlayer)
			{
				Debug.LogError($"For iteration {i}, out of {readOnlyLocalUsersList.Count}");
				ClientAddPlayer((short)readOnlyLocalUsersList[i].id, conn);
			}
		}
	}

	[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
	private static void ClientInit()
	{
		SceneCatalog.onMostRecentSceneDefChanged += ClientUpdateOfflineScene;
	}

	private static void ClientUpdateOfflineScene(SceneDef sceneDef)
	{
		if ((bool)singleton && sceneDef.isOfflineScene)
		{
			singleton.offlineScene = sceneDef.cachedName;
		}
	}

	protected static void EnsureNetworkManagerNotBusy()
	{
		if (!singleton || (!singleton.serverShuttingDown && !isLoadingScene))
		{
			return;
		}
		throw new ConCommandException("NetworkManager is busy and cannot receive commands.");
	}

	[ConCommand(commandName = "client_set_players", flags = ConVarFlags.None, helpText = "Adds network players for all local players. Debug only.")]
	private static void CCClientSetPlayers(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformClientSetPlayers(args);
		}
	}

	protected abstract void PlatformClientSetPlayers(ConCommandArgs args);

	[ConCommand(commandName = "ping", flags = ConVarFlags.None, helpText = "Prints the current round trip time from this client to the server and back.")]
	private static void CCPing(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			NetworkConnection networkConnection = singleton?.client?.connection;
			if (networkConnection != null)
			{
				Debug.LogFormat("rtt={0}ms smoothedFrame={1} smoothedFixed={2}", RttManager.GetConnectionRTTInMilliseconds(networkConnection), RttManager.GetConnectionFrameSmoothedRtt(networkConnection), RttManager.GetConnectionFixedSmoothedRtt(networkConnection));
			}
		}
	}

	[ConCommand(commandName = "set_scene", flags = ConVarFlags.None, helpText = "Changes to the named scene.")]
	private static void CCSetScene(ConCommandArgs args)
	{
		string argString = args.GetArgString(0);
		if (!singleton)
		{
			throw new ConCommandException("set_scene failed: NetworkManagerSystem is not available.");
		}
		SceneCatalog.GetSceneDefForCurrentScene();
		SceneDef sceneDefFromSceneName = SceneCatalog.GetSceneDefFromSceneName(argString);
		if (!sceneDefFromSceneName)
		{
			throw new ConCommandException("\"" + argString + "\" is not a valid scene.");
		}
		bool boolValue = Console.CheatsConVar.instance.boolValue;
		if (!NetworkManager.singleton)
		{
			_ = 1;
		}
		else
			_ = NetworkManager.singleton.isNetworkActive;
		if (NetworkManager.singleton.isNetworkActive)
		{
			if (sceneDefFromSceneName.isOfflineScene)
			{
				throw new ConCommandException("Cannot switch to scene \"" + argString + "\": Cannot switch to offline-only scene while in a network session.");
			}
			if (!boolValue)
			{
				throw new ConCommandException("Cannot switch to scene \"" + argString + "\": Cheats must be enabled to switch between online-only scenes.");
			}
		}
		else if (!sceneDefFromSceneName.isOfflineScene)
		{
			throw new ConCommandException("Cannot switch to scene \"" + argString + "\": Cannot switch to online-only scene while not in a network session.");
		}
		if ((bool)PauseStopController.instance && PauseStopController.instance.isPaused)
		{
			PauseStopController.instance.Pause(shouldPause: false);
		}
		if (NetworkServer.active)
		{
			Debug.LogFormat("Setting server scene to {0}", argString);
			singleton.ServerChangeScene(argString);
			return;
		}
		if (!NetworkClient.active)
		{
			Debug.LogFormat("Setting offline scene to {0}", argString);
			singleton.ServerChangeScene(argString);
			return;
		}
		throw new ConCommandException("Cannot change scene while connected to a remote server.");
	}

	[ConCommand(commandName = "scene_list", flags = ConVarFlags.None, helpText = "Prints a list of all available scene names.")]
	private static void CCSceneList(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			string[] array = new string[SceneManager.sceneCountInBuildSettings];
			for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
			{
				array[i] = $"[{i}]={SceneUtility.GetScenePathByBuildIndex(i)}";
			}
		}
	}

	[ConCommand(commandName = "dump_network_ids", flags = ConVarFlags.None, helpText = "Lists the network ids of all currently networked game objects.")]
	private static void CCDumpNetworkIDs(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			List<NetworkIdentity> list = new List<NetworkIdentity>(UnityEngine.Object.FindObjectsOfType<NetworkIdentity>());
			list.Sort((NetworkIdentity lhs, NetworkIdentity rhs) => (int)(lhs.netId.Value - rhs.netId.Value));
			for (int i = 0; i < list.Count; i++)
			{
				Debug.LogFormat("{0}={1}", list[i].netId.Value, list[i].gameObject.name);
			}
		}
	}

	[ConCommand(commandName = "disconnect", flags = ConVarFlags.None, helpText = "Disconnect from a server or shut down the current server.")]
	private static void CCDisconnect(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformDisconnect(args);
		}
	}

	protected abstract void PlatformDisconnect(ConCommandArgs args);

	protected void Disconnect()
	{
		if (!serverShuttingDown && singleton.isNetworkActive)
		{
			if (NetworkServer.active)
			{
				singleton.RequestServerShutdown();
			}
			else
			{
				singleton.StopClient();
			}
			OrbStorageUtility.Clear();
		}
	}

	[ConCommand(commandName = "connect", flags = ConVarFlags.None, helpText = "Connect to a server.")]
	private static void CCConnect(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformConnect(args);
		}
	}

	protected abstract void PlatformConnect(ConCommandArgs args);

	private static void CCConnectP2P(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformConnect(args);
		}
	}

	protected abstract void PlatformConnectP2P(ConCommandArgs args);

	[ConCommand(commandName = "host", flags = ConVarFlags.None, helpText = "Host a server. First argument is whether or not to listen for incoming connections.")]
	private static void CCHost(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformHost(args);
		}
	}

	protected abstract void PlatformHost(ConCommandArgs args);

	[ConCommand(commandName = "steam_get_p2p_session_state")]
	private static void CCSGetP2PSessionState(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformGetP2PSessionState(args);
		}
	}

	protected abstract void PlatformGetP2PSessionState(ConCommandArgs args);

	[ConCommand(commandName = "kick_steam", flags = ConVarFlags.SenderMustBeServer, helpText = "Kicks the user with the specified steam id from the server.")]
	private static void CCKickSteam(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformKick(args);
		}
	}

	protected abstract void PlatformKick(ConCommandArgs args);

	[ConCommand(commandName = "ban_steam", flags = ConVarFlags.SenderMustBeServer, helpText = "Bans the user with the specified steam id from the server.")]
	private static void CCBan(ConCommandArgs args)
	{
		if ((bool)singleton)
		{
			singleton.PlatformBan(args);
		}
	}

	protected abstract void PlatformBan(ConCommandArgs args);

	public virtual bool IsHost()
	{
		return isHost;
	}

	public override void OnStartHost()
	{
		base.OnStartHost();
		isHost = true;
		NetworkManagerSystem.onStartHostGlobal?.Invoke();
	}

	public override void OnStopHost()
	{
		NetworkManagerSystem.onStopHostGlobal?.Invoke();
		isHost = false;
		base.OnStopHost();
	}

	[NetworkMessageHandler(client = true, server = false, msgType = 67)]
	private static void HandleKick(NetworkMessage netMsg)
	{
		KickMessage kickMessage = netMsg.ReadMessage<KickMessage>();
		Debug.LogFormat("Received kick message. Reason={0}", kickMessage.kickReason);
		singleton.StopClient();
		kickMessage.TryGetDisplayTokenAndFormatParams(out var token, out var formatArgs);
		DialogBoxManager.DialogBox(new SimpleDialogBox.TokenParamsPair("DISCONNECTED"), new SimpleDialogBox.TokenParamsPair(token ?? string.Empty, formatArgs), CommonLanguageTokens.ok);
	}

	public static void HandleKick(string displayToken)
	{
		singleton.StopClient();
		DialogBoxManager.DialogBox("DISCONNECTED", displayToken, CommonLanguageTokens.ok);
	}

	[NetworkMessageHandler(msgType = 54, client = true)]
	private static void HandleUpdateTime(NetworkMessage netMsg)
	{
		float num = netMsg.reader.ReadSingle();
		singleton._unpredictedServerFixedTime = num;
		float num2 = Time.time - Time.fixedTime;
		singleton._unpredictedServerFrameTime = num + num2;
	}

	[NetworkMessageHandler(msgType = 64, client = true, server = true)]
	private static void HandleTest(NetworkMessage netMsg)
	{
		int num = netMsg.reader.ReadInt32();
		Debug.LogFormat("Received test packet. value={0}", num);
	}

	public abstract NetworkConnection GetClient(PlatformID clientId);

	public virtual void InitPlatformServer()
	{
	}

	public override void OnStartServer()
	{
		base.OnStartServer();
		NetworkMessageHandlerAttribute.RegisterServerMessages();
		InitializeTime();
		serverNetworkSessionInstance = UnityEngine.Object.Instantiate(RoR2Application.instance.networkSessionPrefab);
		serverPauseStopControllerInstance = UnityEngine.Object.Instantiate(RoR2Application.instance.pauseStopController);
		InitPlatformServer();
		NetworkManagerSystem.onStartServerGlobal?.Invoke();
	}

	public void FireStartServerGlobalEvent()
	{
		NetworkManagerSystem.onStartServerGlobal?.Invoke();
	}

	public override void OnStopServer()
	{
		base.OnStopServer();
		FireStopServerGlobalEvent();
		for (int i = 0; i < NetworkServer.connections.Count; i++)
		{
			NetworkConnection networkConnection = NetworkServer.connections[i];
			if (networkConnection != null)
			{
				OnServerDisconnect(networkConnection);
			}
		}
		UnityEngine.Object.Destroy(serverPauseStopControllerInstance);
		serverPauseStopControllerInstance = null;
		UnityEngine.Object.Destroy(serverNetworkSessionInstance);
		serverNetworkSessionInstance = null;
	}

	public void FireStopServerGlobalEvent()
	{
		NetworkManagerSystem.onStopServerGlobal?.Invoke();
	}

	public override void OnServerConnect(NetworkConnection conn)
	{
		base.OnServerConnect(conn);
	}

	public void FireServerConnectGlobalEvent(NetworkConnection conn)
	{
		NetworkManagerSystem.onServerConnectGlobal?.Invoke(conn);
	}

	public override void OnServerDisconnect(NetworkConnection conn)
	{
		base.OnServerDisconnect(conn);
	}

	public void FireServerDisconnectGlobalEvent(NetworkConnection conn)
	{
		NetworkManagerSystem.onServerDisconnectGlobal?.Invoke(conn);
	}

	public abstract void ServerHandleClientDisconnect(NetworkConnection conn);

	public abstract void ServerBanClient(NetworkConnection conn);

	public void ServerKickClient(NetworkConnection conn, BaseKickReason reason)
	{
		Debug.LogFormat("Kicking client on connection {0}: Reason {1}", conn.connectionId, reason);
		conn.SendByChannel(67, new KickMessage(reason), QosChannelIndex.defaultReliable.intVal);
		conn.FlushChannels();
		KickClient(conn, reason);
	}

	protected abstract void KickClient(NetworkConnection conn, BaseKickReason reason);

	public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
	{
		OnServerAddPlayer(conn, playerControllerId, null);
	}

	public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
	{
		OnServerAddPlayerInternal(conn, playerControllerId, extraMessageReader);
	}

	private void OnServerAddPlayerInternal(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
	{
		if (base.playerPrefab == null)
		{
			if (LogFilter.logError)
			{
				Debug.LogError("The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object.");
			}
			return;
		}
		if (base.playerPrefab.GetComponent<NetworkIdentity>() == null)
		{
			if (LogFilter.logError)
			{
				Debug.LogError("The PlayerPrefab does not have a NetworkIdentity. Please add a NetworkIdentity to the player prefab.");
			}
			return;
		}
		if (playerControllerId < conn.playerControllers.Count && conn.playerControllers[playerControllerId].IsValid && conn.playerControllers[playerControllerId].gameObject != null)
		{
			if (LogFilter.logError)
			{
				Debug.LogErrorFormat("There is already a player at that playerControllerId for this connection, conn = {0}, playerControllerId = {1}.", conn, playerControllerId);
			}
			return;
		}
		if (NetworkUser.readOnlyInstancesList.Count >= base.maxConnections)
		{
			if (LogFilter.logError)
			{
				Debug.LogError("Cannot add any more players.)");
			}
			return;
		}
		if (extraMessageReader == null)
		{
			extraMessageReader = DefaultNetworkReader;
		}
		AddPlayerMessage message = extraMessageReader.ReadMessage<AddPlayerMessage>();
		Transform startPosition = GetStartPosition();
		GameObject gameObject = ((!(startPosition != null)) ? UnityEngine.Object.Instantiate(base.playerPrefab, Vector3.zero, Quaternion.identity) : UnityEngine.Object.Instantiate(base.playerPrefab, startPosition.position, startPosition.rotation));
		Debug.LogFormat("NetworkManagerSystem.AddPlayerInternal(conn={0}, playerControllerId={1}, extraMessageReader={2}", conn, playerControllerId, extraMessageReader);
		NetworkUser component = gameObject.GetComponent<NetworkUser>();
		Util.ConnectionIsLocal(conn);
		component.id = AddPlayerIdFromPlatform(conn, message, (byte)playerControllerId);
		Chat.SendPlayerConnectedMessage(component);
		NetworkServer.AddPlayerForConnection(conn, gameObject, playerControllerId);
	}

	protected abstract NetworkUserId AddPlayerIdFromPlatform(NetworkConnection conn, AddPlayerMessage message, byte playerControllerId);

	protected abstract void UpdateServer();

	private void FixedUpdateServer()
	{
		if (!NetworkServer.active)
		{
			return;
		}
		timeTransmitTimer -= Time.fixedDeltaTime;
		if (timeTransmitTimer <= 0f)
		{
			NetworkWriter networkWriter = new NetworkWriter();
			networkWriter.StartMessage(54);
			networkWriter.Write(unpredictedServerFixedTime);
			networkWriter.FinishMessage();
			NetworkServer.SendWriterToReady(null, networkWriter, QosChannelIndex.time.intVal);
			timeTransmitTimer += svTimeTransmitInterval.value;
		}
		foreach (NetworkConnection connection in NetworkServer.connections)
		{
			if (connection != null && !Util.ConnectionIsLocal(connection))
			{
				RttManager.Ping(connection, QosChannelIndex.ping.intVal);
			}
		}
	}

	public override void OnServerSceneChanged(string sceneName)
	{
		base.OnServerSceneChanged(sceneName);
		if ((bool)Run.instance)
		{
			Run.instance.OnServerSceneChanged(sceneName);
		}
		NetworkManagerSystem.onServerSceneChangedGlobal?.Invoke(sceneName);
		while (clientsReadyDuringLevelTransition.Count > 0)
		{
			NetworkConnection networkConnection = clientsReadyDuringLevelTransition.Dequeue();
			try
			{
				if (networkConnection.isConnected)
				{
					OnServerReady(networkConnection);
				}
			}
			catch (Exception ex)
			{
				Debug.LogErrorFormat("OnServerReady could not be called for client: {0}", ex.Message);
			}
		}
	}

	protected bool IsServerAtMaxConnections()
	{
		ReadOnlyCollection<NetworkConnection> connections = NetworkServer.connections;
		if (connections.Count >= base.maxConnections)
		{
			int num = 0;
			for (int i = 0; i < connections.Count; i++)
			{
				if (connections[i] != null)
				{
					num++;
				}
			}
			return num >= base.maxConnections;
		}
		return false;
	}

	private NetworkUser FindNetworkUserForConnectionServer(NetworkConnection connection)
	{
		ReadOnlyCollection<NetworkUser> readOnlyInstancesList = NetworkUser.readOnlyInstancesList;
		int count = readOnlyInstancesList.Count;
		for (int i = 0; i < count; i++)
		{
			NetworkUser networkUser = readOnlyInstancesList[i];
			if (networkUser.connectionToClient == connection)
			{
				return networkUser;
			}
		}
		return null;
	}

	public int GetConnectingClientCount()
	{
		int num = 0;
		ReadOnlyCollection<NetworkConnection> connections = NetworkServer.connections;
		int count = connections.Count;
		for (int i = 0; i < count; i++)
		{
			NetworkConnection networkConnection = connections[i];
			if (networkConnection != null && !FindNetworkUserForConnectionServer(networkConnection))
			{
				num++;
			}
		}
		return num;
	}

	public void RequestServerShutdown()
	{
		if (!serverShuttingDown)
		{
			serverShuttingDown = true;
			StartCoroutine(ServerShutdownCoroutine());
		}
	}

	private IEnumerator ServerShutdownCoroutine()
	{
		ReadOnlyCollection<NetworkConnection> connections = NetworkServer.connections;
		for (int num = connections.Count - 1; num >= 0; num--)
		{
			NetworkConnection networkConnection = connections[num];
			if (networkConnection != null && !Util.ConnectionIsLocal(networkConnection))
			{
				ServerKickClient(networkConnection, new SimpleLocalizedKickReason("KICK_REASON_SERVERSHUTDOWN"));
			}
		}
		float maxWait = 0.2f;
		for (float t = 0f; t < maxWait; t += Time.unscaledDeltaTime)
		{
			if (CheckConnectionsEmpty())
			{
				break;
			}
			yield return new WaitForEndOfFrame();
		}
		if (client != null)
		{
			StopHost();
		}
		else
		{
			StopServer();
		}
		serverShuttingDown = false;
		static bool CheckConnectionsEmpty()
		{
			foreach (NetworkConnection connection in NetworkServer.connections)
			{
				if (connection != null && !Util.ConnectionIsLocal(connection))
				{
					return false;
				}
			}
			return true;
		}
	}

	private static void ServerHandleReady(NetworkMessage netMsg)
	{
		if (isLoadingScene)
		{
			clientsReadyDuringLevelTransition.Enqueue(netMsg.conn);
		}
		else
		{
			singleton.OnServerReady(netMsg.conn);
		}
	}

	private void RegisterServerOverrideMessages()
	{
		NetworkServer.RegisterHandler(35, ServerHandleReady);
	}

	public override void ServerChangeScene(string newSceneName)
	{
		RegisterServerOverrideMessages();
		base.ServerChangeScene(newSceneName);
	}

	private static bool IsAddressablesKeyValid(string key, Type type)
	{
		foreach (IResourceLocator resourceLocator in Addressables.ResourceLocators)
		{
			if (resourceLocator.Locate(key, typeof(SceneInstance), out var locations) && locations.Count > 0)
			{
				return true;
			}
		}
		return false;
	}

	protected override IChangeSceneAsyncOperation ChangeSceneImplementation(string newSceneName)
	{
		IChangeSceneAsyncOperation changeSceneAsyncOperation = null;
		StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder();
		try
		{
			SceneDef sceneDef = SceneCatalog.FindSceneDef(newSceneName);
			if ((bool)sceneDef)
			{
				string text = sceneDef.sceneAddress?.AssetGUID;
				if (!string.IsNullOrEmpty(text))
				{
					if (IsAddressablesKeyValid(text, typeof(SceneInstance)))
					{
						changeSceneAsyncOperation = new AddressablesChangeSceneAsyncOperation(text, LoadSceneMode.Single, activateOnLoad: false);
					}
					else
					{
						stringBuilder.AppendLine("Scene address is invalid. sceneName=\"").Append(newSceneName).Append("\" sceneAddress=\"")
							.Append(text)
							.Append("\"")
							.AppendLine();
					}
				}
			}
			else if (IsAddressablesKeyValid(newSceneName, typeof(SceneInstance)))
			{
				changeSceneAsyncOperation = new AddressablesChangeSceneAsyncOperation(newSceneName, LoadSceneMode.Single, activateOnLoad: false);
			}
			if (changeSceneAsyncOperation == null)
			{
				changeSceneAsyncOperation = base.ChangeSceneImplementation(newSceneName);
				if (changeSceneAsyncOperation == null)
				{
					stringBuilder.Append("SceneManager.LoadSceneAsync(\"").Append(newSceneName).Append("\" failed.")
						.AppendLine();
				}
			}
			return changeSceneAsyncOperation;
		}
		finally
		{
			if (changeSceneAsyncOperation == null)
			{
				Debug.LogError(stringBuilder.ToString());
			}
			HG.StringBuilderPool.ReturnStringBuilder(stringBuilder);
		}
	}
}
