using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using HG;
using JetBrains.Annotations;
using RoR2.ConVar;
using RoR2.ExpansionManagement;
using RoR2.Navigation;
using RoR2.Networking;
using RoR2.Stats;
using Unity;
using UnityEngine;
using UnityEngine.Networking;

namespace RoR2;

[DisallowMultipleComponent]
[RequireComponent(typeof(NetworkRuleBook))]
[RequireComponent(typeof(RunArtifactManager))]
public class Run : NetworkBehaviour
{
	[Serializable]
	public struct RunStopwatch : IEquatable<RunStopwatch>
	{
		public float offsetFromFixedTime;

		public bool isPaused;

		public bool Equals(RunStopwatch other)
		{
			if (offsetFromFixedTime.Equals(other.offsetFromFixedTime))
			{
				return isPaused == other.isPaused;
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj == null)
			{
				return false;
			}
			if (obj is RunStopwatch other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (offsetFromFixedTime.GetHashCode() * 397) ^ isPaused.GetHashCode();
		}
	}

	[Serializable]
	public struct TimeStamp : IEquatable<TimeStamp>, IComparable<TimeStamp>
	{
		public readonly float t;

		private static float tNow;

		public static readonly TimeStamp zero;

		public static readonly TimeStamp positiveInfinity;

		public static readonly TimeStamp negativeInfinity;

		public float timeUntil => t - tNow;

		public float timeSince => tNow - t;

		public float timeUntilClamped => Mathf.Max(timeUntil, 0f);

		public float timeSinceClamped => Mathf.Max(timeSince, 0f);

		public bool hasPassed => t <= tNow;

		public bool isInfinity => float.IsInfinity(t);

		public bool isPositiveInfinity => float.IsPositiveInfinity(t);

		public bool isNegativeInfinity => float.IsNegativeInfinity(t);

		public static TimeStamp now => new TimeStamp(tNow);

		public override int GetHashCode()
		{
			return t.GetHashCode();
		}

		public static void Update()
		{
			tNow = instance.time;
		}

		private TimeStamp(float t)
		{
			this.t = t;
		}

		public bool Equals(TimeStamp other)
		{
			return t.Equals(other.t);
		}

		public override bool Equals(object obj)
		{
			if (obj is TimeStamp)
			{
				return Equals((TimeStamp)obj);
			}
			return false;
		}

		public int CompareTo(TimeStamp other)
		{
			return t.CompareTo(other.t);
		}

		public static TimeStamp operator +(TimeStamp a, float b)
		{
			return new TimeStamp(a.t + b);
		}

		public static TimeStamp operator -(TimeStamp a, float b)
		{
			return new TimeStamp(a.t - b);
		}

		public static float operator -(TimeStamp a, TimeStamp b)
		{
			return a.t - b.t;
		}

		public static bool operator <(TimeStamp a, TimeStamp b)
		{
			return a.t < b.t;
		}

		public static bool operator >(TimeStamp a, TimeStamp b)
		{
			return a.t > b.t;
		}

		public static bool operator <=(TimeStamp a, TimeStamp b)
		{
			return a.t <= b.t;
		}

		public static bool operator >=(TimeStamp a, TimeStamp b)
		{
			return a.t >= b.t;
		}

		public static bool operator ==(TimeStamp a, TimeStamp b)
		{
			return a.Equals(b);
		}

		public static bool operator !=(TimeStamp a, TimeStamp b)
		{
			return !a.Equals(b);
		}

		public static float operator -(TimeStamp a, FixedTimeStamp b)
		{
			return a.t - b.t;
		}

		public static TimeStamp Deserialize(NetworkReader reader)
		{
			return new TimeStamp(reader.ReadSingle());
		}

		public static void Serialize(NetworkWriter writer, TimeStamp timeStamp)
		{
			writer.Write(timeStamp.t);
		}

		public static void ToXml(XElement element, TimeStamp src)
		{
			element.Value = TextSerialization.ToStringInvariant(src.t);
		}

		public static bool FromXml(XElement element, ref TimeStamp dest)
		{
			if (TextSerialization.TryParseInvariant(element.Value, out float result))
			{
				dest = new TimeStamp(result);
				return true;
			}
			return false;
		}

		static TimeStamp()
		{
			zero = new TimeStamp(0f);
			positiveInfinity = new TimeStamp(float.PositiveInfinity);
			negativeInfinity = new TimeStamp(float.NegativeInfinity);
			HGXml.Register<TimeStamp>(ToXml, FromXml);
		}
	}

	[Serializable]
	public struct FixedTimeStamp : IEquatable<FixedTimeStamp>, IComparable<FixedTimeStamp>
	{
		public readonly float t;

		private static float tNow;

		public static readonly FixedTimeStamp zero;

		public static readonly FixedTimeStamp positiveInfinity;

		public static readonly FixedTimeStamp negativeInfinity;

		public float timeUntil => t - tNow;

		public float timeSince => tNow - t;

		public float timeUntilClamped => Mathf.Max(timeUntil, 0f);

		public float timeSinceClamped => Mathf.Max(timeSince, 0f);

		public bool hasPassed => t <= tNow;

		public bool isInfinity => float.IsInfinity(t);

		public bool isPositiveInfinity => float.IsPositiveInfinity(t);

		public bool isNegativeInfinity => float.IsNegativeInfinity(t);

		public static FixedTimeStamp now => new FixedTimeStamp(tNow);

		public override int GetHashCode()
		{
			return t.GetHashCode();
		}

		public static void Update()
		{
			tNow = instance.fixedTime;
		}

		private FixedTimeStamp(float t)
		{
			this.t = t;
		}

		public bool Equals(FixedTimeStamp other)
		{
			return t.Equals(other.t);
		}

		public override bool Equals(object obj)
		{
			if (obj is FixedTimeStamp)
			{
				return Equals((FixedTimeStamp)obj);
			}
			return false;
		}

		public int CompareTo(FixedTimeStamp other)
		{
			return t.CompareTo(other.t);
		}

		public static FixedTimeStamp operator +(FixedTimeStamp a, float b)
		{
			return new FixedTimeStamp(a.t + b);
		}

		public static FixedTimeStamp operator -(FixedTimeStamp a, float b)
		{
			return new FixedTimeStamp(a.t - b);
		}

		public static float operator -(FixedTimeStamp a, FixedTimeStamp b)
		{
			return a.t - b.t;
		}

		public static bool operator <(FixedTimeStamp a, FixedTimeStamp b)
		{
			return a.t < b.t;
		}

		public static bool operator >(FixedTimeStamp a, FixedTimeStamp b)
		{
			return a.t > b.t;
		}

		public static bool operator <=(FixedTimeStamp a, FixedTimeStamp b)
		{
			return a.t <= b.t;
		}

		public static bool operator >=(FixedTimeStamp a, FixedTimeStamp b)
		{
			return a.t >= b.t;
		}

		public static bool operator ==(FixedTimeStamp a, FixedTimeStamp b)
		{
			return a.Equals(b);
		}

		public static bool operator !=(FixedTimeStamp a, FixedTimeStamp b)
		{
			return !a.Equals(b);
		}

		public static FixedTimeStamp Deserialize(NetworkReader reader)
		{
			return new FixedTimeStamp(reader.ReadSingle());
		}

		public static void Serialize(NetworkWriter writer, FixedTimeStamp timeStamp)
		{
			writer.Write(timeStamp.t);
		}

		public static void ToXml(XElement element, FixedTimeStamp src)
		{
			element.Value = TextSerialization.ToStringInvariant(src.t);
		}

		public static bool FromXml(XElement element, ref FixedTimeStamp dest)
		{
			if (TextSerialization.TryParseInvariant(element.Value, out float result))
			{
				dest = new FixedTimeStamp(result);
				return true;
			}
			return false;
		}

		static FixedTimeStamp()
		{
			zero = new FixedTimeStamp(0f);
			positiveInfinity = new FixedTimeStamp(float.PositiveInfinity);
			negativeInfinity = new FixedTimeStamp(float.NegativeInfinity);
			HGXml.Register<FixedTimeStamp>(ToXml, FromXml);
		}
	}

	private NetworkRuleBook networkRuleBookComponent;

	[Tooltip("This is assigned to the prefab automatically by GameModeCatalog at runtime. Do not set this value manually.")]
	[HideInInspector]
	public GameModeIndex gameModeIndex = GameModeIndex.Invalid;

	public string nameToken = "";

	[Tooltip("Whether or not the user can select this game mode for play in the game mode selector UI.")]
	public bool userPickable = true;

	[Tooltip("Dictates items given when rebirth is active when the user doesn't have a stored item.")]
	public PickupDropTable rebirthDropTable;

	public static int stagesPerLoop = 5;

	public static float baseGravity = -30f;

	[SyncVar]
	private NetworkGuid _uniqueId;

	[SyncVar]
	private NetworkDateTime startTimeUtc;

	[ShowFieldObsolete]
	[Obsolete("Use startingSceneGroup instead.")]
	[Tooltip("The pool of scenes to select the first scene of the run from.")]
	public SceneDef[] startingScenes = Array.Empty<SceneDef>();

	[Tooltip("The pool of scenes to select the first scene of the run from.")]
	public SceneCollection startingSceneGroup;

	public ItemMask availableItems;

	public ItemMask expansionLockedItems;

	public EquipmentMask availableEquipment;

	public EquipmentMask expansionLockedEquipment;

	public bool isRunWeekly;

	[SyncVar]
	public float fixedTime;

	public float time;

	[HideInInspector]
	public bool isRunning;

	[SyncVar]
	private RunStopwatch runStopwatch;

	[SyncVar]
	public int stageClearCount;

	public SceneDef nextStageScene;

	public SceneDef[] blacklistedScenesForFirstScene = Array.Empty<SceneDef>();

	public GameObject gameOverPrefab;

	public GameObject lobbyBackgroundPrefab;

	public GameObject uiPrefab;

	private ulong _seed;

	public Xoroshiro128Plus runRNG;

	public Xoroshiro128Plus nextStageRng;

	public Xoroshiro128Plus stageRngGenerator;

	public Xoroshiro128Plus stageRng;

	public Xoroshiro128Plus bossRewardRng;

	public Xoroshiro128Plus treasureRng;

	public Xoroshiro128Plus spawnRng;

	public Xoroshiro128Plus randomSurvivorOnRespawnRng;

	public float difficultyCoefficient = 1f;

	public float compensatedDifficultyCoefficient = 1f;

	public float oneOverCompensatedDifficultyCoefficientSquared = 1f;

	[SyncVar]
	private int selectedDifficultyInternal = 1;

	public int shopPortalCount;

	private static int ambientLevelCap = 99;

	private bool lowerPricedChestsTimerOn;

	private float lowerPricedChestsTimer;

	private float lowerPricedChestsDuration = 10f;

	private static readonly StringConVar cvRunSceneOverride = new StringConVar("run_scene_override", ConVarFlags.Cheat, "", "Overrides the first scene to enter in a run.");

	private readonly HashSet<UnlockableDef> unlockablesUnlockedByAnyUser = new HashSet<UnlockableDef>();

	private readonly HashSet<UnlockableDef> unlockablesUnlockedByAllUsers = new HashSet<UnlockableDef>();

	private readonly HashSet<UnlockableDef> unlockablesAlreadyFullyObtained = new HashSet<UnlockableDef>();

	private bool shutdown;

	private Dictionary<NetworkUserId, CharacterMaster> userMasters = new Dictionary<NetworkUserId, CharacterMaster>();

	private bool allowNewParticipants;

	public readonly List<PickupIndex> availableTier1DropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableTier2DropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableTier3DropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableEquipmentDropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableLunarEquipmentDropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableLunarItemDropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableLunarCombinedDropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableBossDropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableVoidTier1DropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableVoidTier2DropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableVoidTier3DropList = new List<PickupIndex>();

	public readonly List<PickupIndex> availableVoidBossDropList = new List<PickupIndex>();

	public WeightedSelection<List<PickupIndex>> smallChestDropTierSelector = new WeightedSelection<List<PickupIndex>>();

	public WeightedSelection<List<PickupIndex>> mediumChestDropTierSelector = new WeightedSelection<List<PickupIndex>>();

	public WeightedSelection<List<PickupIndex>> largeChestDropTierSelector = new WeightedSelection<List<PickupIndex>>();

	private readonly HashSet<string> eventFlags = new HashSet<string>();

	public static Run instance { get; private set; }

	public RuleBook ruleBook => networkRuleBookComponent.ruleBook;

	public bool isRunStopwatchPaused => runStopwatch.isPaused;

	public virtual int loopClearCount => stageClearCount / stagesPerLoop;

	public virtual bool spawnWithPod => instance.stageClearCount == 0;

	public virtual bool autoGenerateSpawnPoints => true;

	public virtual bool canFamilyEventTrigger => true;

	public IList<GameObject> uiInstances { get; protected set; } = new List<GameObject>();


	public ulong seed
	{
		get
		{
			return _seed;
		}
		set
		{
			_seed = value;
			OnSeedSet();
		}
	}

	public DifficultyIndex selectedDifficulty
	{
		get
		{
			return (DifficultyIndex)selectedDifficultyInternal;
		}
		set
		{
			NetworkselectedDifficultyInternal = (int)value;
		}
	}

	public int livingPlayerCount => PlayerCharacterMasterController.GetPlayersWithBodiesCount();

	public int participatingPlayerCount => PlayerCharacterMasterController.instances.Count;

	public float ambientLevel { get; protected set; }

	public int ambientLevelFloor { get; protected set; }

	public float teamlessDamageCoefficient => difficultyCoefficient;

	public bool isGameOverServer { get; private set; }

	public NetworkGuid Network_uniqueId
	{
		get
		{
			return _uniqueId;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref _uniqueId, 1u);
		}
	}

	public NetworkDateTime NetworkstartTimeUtc
	{
		get
		{
			return startTimeUtc;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref startTimeUtc, 2u);
		}
	}

	public float NetworkfixedTime
	{
		get
		{
			return fixedTime;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref fixedTime, 4u);
		}
	}

	public RunStopwatch NetworkrunStopwatch
	{
		get
		{
			return runStopwatch;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref runStopwatch, 8u);
		}
	}

	public int NetworkstageClearCount
	{
		get
		{
			return stageClearCount;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref stageClearCount, 16u);
		}
	}

	public int NetworkselectedDifficultyInternal
	{
		get
		{
			return selectedDifficultyInternal;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref selectedDifficultyInternal, 32u);
		}
	}

	public static event Action<Run, RuleBook> onServerRunSetRuleBookGlobal;

	public static event Action<Run, RuleBook> onRunSetRuleBookGlobal;

	public static event Action<Run> onRunAmbientLevelUp;

	public static event Action<Run, PlayerCharacterMasterController> onPlayerFirstCreatedServer;

	public static event Action<Run, GameEndingDef> onServerGameOver;

	public static event Action<Run, RunReport> onClientGameOverGlobal;

	public static event Action<Run> onRunStartGlobal;

	public static event Action<Run> onRunDestroyGlobal;

	public static event Action<Run> onAvailablePickupsModified;

	protected virtual void OnEnable()
	{
		instance = SingletonHelper.Assign(instance, this);
	}

	protected virtual void OnDisable()
	{
		instance = SingletonHelper.Unassign(instance, this);
		if (instance == null)
		{
			RewiredIntegrationManager.RefreshAfterRunFinish();
		}
	}

	protected void Awake()
	{
		networkRuleBookComponent = GetComponent<NetworkRuleBook>();
		networkRuleBookComponent.onRuleBookUpdated += OnRuleBookUpdated;
		availableItems = ItemMask.Rent();
		expansionLockedItems = ItemMask.Rent();
		availableEquipment = EquipmentMask.Rent();
		expansionLockedEquipment = EquipmentMask.Rent();
		if (NetworkServer.active)
		{
			Network_uniqueId = (NetworkGuid)Guid.NewGuid();
			NetworkstartTimeUtc = (NetworkDateTime)DateTime.UtcNow;
		}
	}

	[Server]
	public void SetRuleBook(RuleBook newRuleBook)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::SetRuleBook(RoR2.RuleBook)' called on client");
		}
		else
		{
			networkRuleBookComponent.SetRuleBook(newRuleBook);
		}
	}

	private void OnRuleBookUpdated(NetworkRuleBook networkRuleBookComponent)
	{
		RuleBook ruleBook = networkRuleBookComponent.ruleBook;
		selectedDifficulty = ruleBook.FindDifficulty();
		ruleBook.GenerateItemMask(availableItems);
		ruleBook.GenerateEquipmentMask(availableEquipment);
		expansionLockedItems.Clear();
		foreach (ItemDef allItemDef in ItemCatalog.allItemDefs)
		{
			if ((bool)allItemDef && (bool)allItemDef.requiredExpansion && !IsExpansionEnabled(allItemDef.requiredExpansion))
			{
				expansionLockedItems.Add(allItemDef.itemIndex);
			}
		}
		expansionLockedEquipment.Clear();
		foreach (EquipmentIndex item in EquipmentCatalog.allEquipment)
		{
			EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef(item);
			if ((bool)equipmentDef && (bool)equipmentDef.requiredExpansion && !IsExpansionEnabled(equipmentDef.requiredExpansion))
			{
				expansionLockedEquipment.Add(equipmentDef.equipmentIndex);
			}
		}
		if (NetworkServer.active)
		{
			Run.onServerRunSetRuleBookGlobal?.Invoke(this, ruleBook);
		}
		Run.onRunSetRuleBookGlobal?.Invoke(this, ruleBook);
	}

	public Guid GetUniqueId()
	{
		return (Guid)_uniqueId;
	}

	public DateTime GetStartTimeUtc()
	{
		return (DateTime)startTimeUtc;
	}

	[Server]
	private void SetRunStopwatchPaused(bool isPaused)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::SetRunStopwatchPaused(System.Boolean)' called on client");
		}
		else if (isPaused != runStopwatch.isPaused)
		{
			RunStopwatch networkrunStopwatch = runStopwatch;
			networkrunStopwatch.isPaused = isPaused;
			float num = GetRunStopwatch();
			if (isPaused)
			{
				networkrunStopwatch.offsetFromFixedTime = num;
			}
			else
			{
				networkrunStopwatch.offsetFromFixedTime = num - fixedTime;
			}
			NetworkrunStopwatch = networkrunStopwatch;
		}
	}

	public float GetRunStopwatch()
	{
		if (runStopwatch.isPaused)
		{
			return runStopwatch.offsetFromFixedTime;
		}
		return fixedTime + runStopwatch.offsetFromFixedTime;
	}

	[Server]
	public void SetRunStopwatch(float t)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::SetRunStopwatch(System.Single)' called on client");
			return;
		}
		RunStopwatch networkrunStopwatch = runStopwatch;
		if (networkrunStopwatch.isPaused)
		{
			networkrunStopwatch.offsetFromFixedTime = t;
		}
		else
		{
			networkrunStopwatch.offsetFromFixedTime = t - fixedTime;
		}
		NetworkrunStopwatch = networkrunStopwatch;
	}

	public virtual GameObject InstantiateUi(Transform uiRoot)
	{
		GameObject gameObject = null;
		if ((bool)uiRoot && (bool)uiPrefab)
		{
			gameObject = UnityEngine.Object.Instantiate(uiPrefab, uiRoot);
			uiInstances.Add(gameObject);
		}
		return gameObject;
	}

	public void UnregisterUi(GameObject uiInstance)
	{
		uiInstances.Remove(uiInstance);
	}

	private void GenerateStageRNG()
	{
		stageRng = new Xoroshiro128Plus(stageRngGenerator.nextUlong);
		bossRewardRng = new Xoroshiro128Plus(stageRng.nextUlong);
		treasureRng = new Xoroshiro128Plus(stageRng.nextUlong);
		spawnRng = new Xoroshiro128Plus(stageRng.nextUlong);
		randomSurvivorOnRespawnRng = new Xoroshiro128Plus(stageRng.nextUlong);
	}

	protected void OnAmbientLevelUp()
	{
		Run.onRunAmbientLevelUp?.Invoke(this);
	}

	protected virtual void FixedUpdate()
	{
		NetworkfixedTime = fixedTime + Time.fixedDeltaTime;
		FixedTimeStamp.Update();
		if (NetworkServer.active)
		{
			SetRunStopwatchPaused(!ShouldUpdateRunStopwatch());
		}
		OnFixedUpdate();
	}

	public void RecalculateDifficultyCoefficent()
	{
		RecalculateDifficultyCoefficentInternal();
	}

	protected virtual void RecalculateDifficultyCoefficentInternal()
	{
		float num = GetRunStopwatch();
		DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(selectedDifficulty);
		float num2 = Mathf.Floor(num * (1f / 60f));
		float num3 = (float)participatingPlayerCount * 0.3f;
		float num4 = 0.7f + num3;
		float num5 = 0.7f + num3;
		float num6 = Mathf.Pow(participatingPlayerCount, 0.2f);
		float num7 = 0.0506f * difficultyDef.scalingValue * num6;
		float num8 = 0.0506f * difficultyDef.scalingValue * num6;
		float num9 = Mathf.Pow(1.15f, stageClearCount);
		compensatedDifficultyCoefficient = (num5 + num8 * num2) * num9;
		difficultyCoefficient = (num4 + num7 * num2) * num9;
		oneOverCompensatedDifficultyCoefficientSquared = 1f / (compensatedDifficultyCoefficient * compensatedDifficultyCoefficient);
		float num10 = (num4 + num7 * (num * (1f / 60f))) * Mathf.Pow(1.15f, stageClearCount);
		ambientLevel = Mathf.Min((num10 - num4) / 0.33f + 1f, ambientLevelCap);
		int num11 = ambientLevelFloor;
		ambientLevelFloor = Mathf.FloorToInt(ambientLevel);
		if (num11 != ambientLevelFloor && num11 != 0 && ambientLevelFloor > num11)
		{
			OnAmbientLevelUp();
		}
	}

	private void InitializeTimeStamps()
	{
		NetworkfixedTime = 0f;
		time = 0f;
		FixedTimeStamp.Update();
		TimeStamp.Update();
	}

	protected virtual void OnFixedUpdate()
	{
		RecalculateDifficultyCoefficent();
	}

	public bool GetLowerPricedChestsTimer()
	{
		return lowerPricedChestsTimerOn;
	}

	protected void Update()
	{
		time = Mathf.Clamp(time + Time.deltaTime, fixedTime, fixedTime + Time.fixedDeltaTime);
		TimeStamp.Update();
		if ((double)GetRunStopwatch() - Math.Floor(GetRunStopwatch() * (1f / 60f)) * 60.0 <= 0.1 && !lowerPricedChestsTimerOn)
		{
			lowerPricedChestsTimerOn = true;
		}
		if (lowerPricedChestsTimerOn)
		{
			lowerPricedChestsTimer += Time.fixedDeltaTime;
		}
		if (lowerPricedChestsTimer >= lowerPricedChestsDuration)
		{
			lowerPricedChestsTimerOn = false;
			lowerPricedChestsTimer = 0f;
		}
	}

	protected virtual bool ShouldUpdateRunStopwatch()
	{
		SceneDef mostRecentSceneDef = SceneCatalog.mostRecentSceneDef;
		if (mostRecentSceneDef.sceneType != SceneType.Stage && mostRecentSceneDef.sceneType != SceneType.TimedIntermission)
		{
			return false;
		}
		return livingPlayerCount > 0;
	}

	[Obsolete("Use the overload that accepts an UnlockableDef instead. This method may be removed from future releases.", false)]
	[Server]
	public bool CanUnlockableBeGrantedThisRun(string unlockableName)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::CanUnlockableBeGrantedThisRun(System.String)' called on client");
			return false;
		}
		return CanUnlockableBeGrantedThisRun(UnlockableCatalog.GetUnlockableDef(unlockableName));
	}

	[Server]
	public virtual bool CanUnlockableBeGrantedThisRun(UnlockableDef unlockableDef)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::CanUnlockableBeGrantedThisRun(RoR2.UnlockableDef)' called on client");
			return false;
		}
		return !unlockablesAlreadyFullyObtained.Contains(unlockableDef);
	}

	[Obsolete("Use the overload that accepts an UnlockableDef instead. This method may be removed from future releases.", false)]
	[Server]
	public void GrantUnlockToAllParticipatingPlayers(string unlockableName)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::GrantUnlockToAllParticipatingPlayers(System.String)' called on client");
		}
		else
		{
			GrantUnlockToAllParticipatingPlayers(UnlockableCatalog.GetUnlockableDef(unlockableName));
		}
	}

	[Server]
	public void GrantUnlockToAllParticipatingPlayers(UnlockableDef unlockableDef)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::GrantUnlockToAllParticipatingPlayers(RoR2.UnlockableDef)' called on client");
		}
		else
		{
			if (!unlockableDef || unlockableDef.index == UnlockableIndex.None || unlockablesAlreadyFullyObtained.Contains(unlockableDef))
			{
				return;
			}
			unlockablesAlreadyFullyObtained.Add(unlockableDef);
			foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList)
			{
				if (readOnlyInstances.isParticipating)
				{
					readOnlyInstances.ServerHandleUnlock(unlockableDef);
				}
			}
		}
	}

	[Server]
	[Obsolete("Use the overload that accepts an UnlockableDef instead. This method may be removed from future releases.", false)]
	public void GrantUnlockToSinglePlayer(string unlockableName, CharacterBody body)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::GrantUnlockToSinglePlayer(System.String,RoR2.CharacterBody)' called on client");
		}
		else
		{
			GrantUnlockToSinglePlayer(UnlockableCatalog.GetUnlockableDef(unlockableName), body);
		}
	}

	[Server]
	public void GrantUnlockToSinglePlayer(UnlockableDef unlockableDef, CharacterBody body)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::GrantUnlockToSinglePlayer(RoR2.UnlockableDef,RoR2.CharacterBody)' called on client");
		}
		else if ((bool)unlockableDef && unlockableDef.index != UnlockableIndex.None && (bool)body)
		{
			NetworkUser networkUser = Util.LookUpBodyNetworkUser(body);
			if ((bool)networkUser)
			{
				networkUser.ServerHandleUnlock(unlockableDef);
			}
		}
	}

	[Obsolete("Use the overload that accepts an UnlockableDef instead. This method may be removed from future releases.", false)]
	[Server]
	public bool IsUnlockableUnlocked(string unlockableName)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::IsUnlockableUnlocked(System.String)' called on client");
			return false;
		}
		return IsUnlockableUnlocked(UnlockableCatalog.GetUnlockableDef(unlockableName));
	}

	[Server]
	public virtual bool IsUnlockableUnlocked(UnlockableDef unlockableDef)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::IsUnlockableUnlocked(RoR2.UnlockableDef)' called on client");
			return false;
		}
		return unlockablesUnlockedByAnyUser.Contains(unlockableDef);
	}

	[Server]
	[Obsolete("Use the overload that accepts an UnlockableDef instead. This method may be removed from future releases.", false)]
	public bool DoesEveryoneHaveThisUnlockableUnlocked(string unlockableName)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::DoesEveryoneHaveThisUnlockableUnlocked(System.String)' called on client");
			return false;
		}
		return DoesEveryoneHaveThisUnlockableUnlocked(UnlockableCatalog.GetUnlockableDef(unlockableName));
	}

	[Server]
	public virtual bool DoesEveryoneHaveThisUnlockableUnlocked(UnlockableDef unlockableDef)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::DoesEveryoneHaveThisUnlockableUnlocked(RoR2.UnlockableDef)' called on client");
			return false;
		}
		return unlockablesUnlockedByAllUsers.Contains(unlockableDef);
	}

	[Server]
	[Obsolete("Use the overload that accepts an UnlockableDef instead. This method may be removed from future releases.", false)]
	public void ForceUnlockImmediate(string unlockableName)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::ForceUnlockImmediate(System.String)' called on client");
		}
		else
		{
			ForceUnlockImmediate(UnlockableCatalog.GetUnlockableDef(unlockableName));
		}
	}

	[Server]
	public void ForceUnlockImmediate(UnlockableDef unlockableDef)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::ForceUnlockImmediate(RoR2.UnlockableDef)' called on client");
		}
		else
		{
			unlockablesUnlockedByAnyUser.Add(unlockableDef);
		}
	}

	public void PickNextStageSceneFromCurrentSceneDestinations()
	{
		WeightedSelection<SceneDef> weightedSelection = new WeightedSelection<SceneDef>();
		SceneCatalog.mostRecentSceneDef.AddDestinationsToWeightedSelection(weightedSelection, CanPickStage);
		PickNextStageScene(weightedSelection);
	}

	public bool CanPickStage(SceneDef sceneDef)
	{
		if ((bool)sceneDef.requiredExpansion)
		{
			return IsExpansionEnabled(sceneDef.requiredExpansion);
		}
		return true;
	}

	public void PickNextStageScene(WeightedSelection<SceneDef> choices)
	{
		if (choices.Count != 0)
		{
			if (ruleBook.stageOrder == StageOrder.Normal)
			{
				nextStageScene = choices.Evaluate(nextStageRng.nextNormalizedFloat);
				return;
			}
			SceneDef[] array = SceneCatalog.allStageSceneDefs.Where(IsValidNextStage).ToArray();
			nextStageScene = nextStageRng.NextElementUniform(array);
		}
		bool IsValidNextStage(SceneDef sceneDef)
		{
			if (nextStageScene != null && nextStageScene.baseSceneName == sceneDef.baseSceneName)
			{
				return false;
			}
			if (!sceneDef.hasAnyDestinations)
			{
				return false;
			}
			if (stageClearCount == 0 && blacklistedScenesForFirstScene.Contains(sceneDef))
			{
				return false;
			}
			return sceneDef.validForRandomSelection;
		}
	}

	public virtual ulong GenerateSeedForNewRun()
	{
		return RoR2Application.rng.nextUlong;
	}

	[Server]
	public void RefreshUnlockAvailability()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::RefreshUnlockAvailability()' called on client");
		}
		else
		{
			BuildUnlockAvailability();
		}
	}

	protected virtual void BuildUnlockAvailability()
	{
		unlockablesUnlockedByAnyUser.Clear();
		unlockablesUnlockedByAllUsers.Clear();
		unlockablesAlreadyFullyObtained.Clear();
		int num = 0;
		Dictionary<UnlockableDef, int> dictionary = new Dictionary<UnlockableDef, int>();
		foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList)
		{
			if (!readOnlyInstances.isParticipating)
			{
				continue;
			}
			num++;
			foreach (UnlockableDef unlockable in readOnlyInstances.unlockables)
			{
				unlockablesUnlockedByAnyUser.Add(unlockable);
				if (!dictionary.ContainsKey(unlockable))
				{
					dictionary.Add(unlockable, 0);
				}
				UnlockableDef key = unlockable;
				int value = dictionary[key] + 1;
				dictionary[key] = value;
			}
		}
		if (num <= 0)
		{
			return;
		}
		foreach (KeyValuePair<UnlockableDef, int> item in dictionary)
		{
			if (item.Value == num)
			{
				unlockablesUnlockedByAllUsers.Add(item.Key);
				unlockablesAlreadyFullyObtained.Add(item.Key);
			}
		}
	}

	protected virtual void Start()
	{
		InitializeTimeStamps();
		OnRuleBookUpdated(networkRuleBookComponent);
		if (NetworkServer.active)
		{
			runRNG = new Xoroshiro128Plus(seed);
			nextStageRng = new Xoroshiro128Plus(runRNG.nextUlong);
			stageRngGenerator = new Xoroshiro128Plus(runRNG.nextUlong);
			GenerateStageRNG();
		}
		allowNewParticipants = true;
		UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
		ReadOnlyCollection<NetworkUser> readOnlyInstancesList = NetworkUser.readOnlyInstancesList;
		for (int i = 0; i < readOnlyInstancesList.Count; i++)
		{
			OnUserAdded(readOnlyInstancesList[i]);
		}
		allowNewParticipants = false;
		if (NetworkServer.active)
		{
			WeightedSelection<SceneDef> weightedSelection = new WeightedSelection<SceneDef>();
			string @string = cvRunSceneOverride.GetString();
			if (@string != "")
			{
				weightedSelection.AddChoice(SceneCatalog.GetSceneDefFromSceneName(@string), 1f);
			}
			else if ((bool)startingSceneGroup)
			{
				startingSceneGroup.AddToWeightedSelection(weightedSelection, CanPickStage);
			}
			else
			{
				for (int j = 0; j < startingScenes.Length; j++)
				{
					if (CanPickStage(startingScenes[j]))
					{
						weightedSelection.AddChoice(startingScenes[j], 1f);
					}
				}
			}
			PickNextStageScene(weightedSelection);
			if (!nextStageScene)
			{
				Debug.LogError("Cannot set next scene. nextStageScene is null!");
			}
			NetworkManager.singleton.ServerChangeScene(nextStageScene.cachedName);
		}
		BuildUnlockAvailability();
		BuildDropTable();
		isRunning = true;
		Run.onRunStartGlobal?.Invoke(this);
		if (NetworkServer.active && RunArtifactManager.instance.IsArtifactEnabled(DLC2Content.Artifacts.Rebirth))
		{
			ServerGiveRebirthItems();
		}
		if (!NetworkClient.active || !RunArtifactManager.instance.IsArtifactEnabled(DLC2Content.Artifacts.Rebirth))
		{
			return;
		}
		foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList)
		{
			if (readOnlyInstances.isLocalPlayer)
			{
				readOnlyInstances.localUser.userProfile.RebirthItem = null;
				break;
			}
		}
	}

	protected virtual void OnDestroy()
	{
		Run.onRunDestroyGlobal?.Invoke(this);
		if ((bool)GameOverController.instance)
		{
			UnityEngine.Object.Destroy(GameOverController.instance.gameObject);
		}
		ReadOnlyCollection<CharacterBody> readOnlyInstancesList = CharacterBody.readOnlyInstancesList;
		for (int num = readOnlyInstancesList.Count - 1; num >= 0; num--)
		{
			if ((bool)readOnlyInstancesList[num])
			{
				UnityEngine.Object.Destroy(readOnlyInstancesList[num].gameObject);
			}
		}
		ReadOnlyCollection<CharacterMaster> readOnlyInstancesList2 = CharacterMaster.readOnlyInstancesList;
		for (int num2 = readOnlyInstancesList2.Count - 1; num2 >= 0; num2--)
		{
			if ((bool)readOnlyInstancesList2[num2])
			{
				UnityEngine.Object.Destroy(readOnlyInstancesList2[num2].gameObject);
			}
		}
		if ((bool)Stage.instance)
		{
			UnityEngine.Object.Destroy(Stage.instance.gameObject);
		}
		Chat.Clear();
		if (!shutdown && PlatformSystems.networkManager.isNetworkActive)
		{
			HandlePostRunDestination();
		}
		ItemMask.Return(availableItems);
		ItemMask.Return(expansionLockedItems);
		EquipmentMask.Return(availableEquipment);
		EquipmentMask.Return(expansionLockedEquipment);
		PlatformSystems.saveSystem.SaveLoggedInUserProfiles();
	}

	[Server]
	private void ServerGiveRebirthItems()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::ServerGiveRebirthItems()' called on client");
		}
		else
		{
			if (!NetworkServer.active)
			{
				return;
			}
			ItemIndex itemIndex = ItemIndex.None;
			foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances)
			{
				Inventory inventory = instance.master.inventory;
				NetworkUser networkUser = instance.networkUser;
				if (networkUser.rebirthItem != ItemIndex.None)
				{
					if ((bool)inventory)
					{
						itemIndex = networkUser.rebirthItem;
						networkUser.NetworkrebirthItem = ItemIndex.None;
					}
				}
				else if (rebirthDropTable != null)
				{
					Xoroshiro128Plus rng = new Xoroshiro128Plus(Run.instance.treasureRng.nextUlong);
					itemIndex = PickupCatalog.GetPickupDef(rebirthDropTable.GenerateDrop(rng)).itemIndex;
				}
				if ((bool)inventory)
				{
					inventory.GiveItem(itemIndex);
				}
			}
		}
	}

	protected virtual void HandlePostRunDestination()
	{
		if (NetworkServer.active)
		{
			NetworkManager.singleton.ServerChangeScene("lobby");
		}
	}

	protected void OnApplicationQuit()
	{
		shutdown = true;
	}

	[Server]
	public CharacterMaster GetUserMaster(NetworkUserId networkUserId)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'RoR2.CharacterMaster RoR2.Run::GetUserMaster(RoR2.NetworkUserId)' called on client");
			return null;
		}
		userMasters.TryGetValue(networkUserId, out var value);
		return value;
	}

	[Server]
	public void OnServerSceneChanged(string sceneName)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::OnServerSceneChanged(System.String)' called on client");
			return;
		}
		BeginStage();
		isGameOverServer = false;
	}

	[Server]
	private void BeginStage()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::BeginStage()' called on client");
		}
		else
		{
			NetworkServer.Spawn(UnityEngine.Object.Instantiate(LegacyResourcesAPI.Load<GameObject>("Prefabs/Stage")));
		}
	}

	[Server]
	private void EndStage()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::EndStage()' called on client");
		}
		else if ((bool)Stage.instance)
		{
			UnityEngine.Object.Destroy(Stage.instance);
		}
	}

	public void OnUserAdded(NetworkUser user)
	{
		if (NetworkServer.active)
		{
			SetupUserCharacterMaster(user);
		}
	}

	public void OnUserRemoved(NetworkUser user)
	{
	}

	public virtual bool ShouldAllowNewParticipant(NetworkUser user)
	{
		if (!allowNewParticipants)
		{
			return (bool)ruleBook.GetRuleChoice(RuleCatalog.FindRuleDef("Misc.AllowDropIn"))?.extraData;
		}
		return true;
	}

	[Server]
	private void SetupUserCharacterMaster(NetworkUser user)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::SetupUserCharacterMaster(RoR2.NetworkUser)' called on client");
		}
		else if (!user)
		{
			Debug.LogError("SetupUserCharacterMaster user is null");
		}
		else
		{
			if ((bool)user.masterObject)
			{
				return;
			}
			CharacterMaster characterMaster = GetUserMaster(user.id);
			bool flag = !characterMaster && ShouldAllowNewParticipant(user);
			if (flag)
			{
				characterMaster = UnityEngine.Object.Instantiate(LegacyResourcesAPI.Load<GameObject>("Prefabs/CharacterMasters/CommandoMaster"), Vector3.zero, Quaternion.identity).GetComponent<CharacterMaster>();
				if ((bool)characterMaster)
				{
					userMasters[user.id] = characterMaster;
					if (ruleBook != null)
					{
						characterMaster.GiveMoney(ruleBook.startingMoney);
					}
					else
					{
						Debug.LogError("Can't give new master money because there's no rulebook");
					}
					DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(selectedDifficulty);
					if ((bool)characterMaster.inventory)
					{
						if (selectedDifficulty == DifficultyIndex.Easy)
						{
							characterMaster.inventory.GiveItem(RoR2Content.Items.DrizzlePlayerHelper);
						}
						else if (difficultyDef.countsAsHardMode)
						{
							characterMaster.inventory.GiveItem(RoR2Content.Items.MonsoonPlayerHelper);
						}
					}
					else
					{
						Debug.LogError("Instantiated master has no inventory");
					}
					NetworkServer.Spawn(characterMaster.gameObject);
				}
				else
				{
					Debug.LogError("Instantiated master object has no CharacterMaster");
				}
			}
			if ((bool)characterMaster)
			{
				PlayerCharacterMasterController component = characterMaster.GetComponent<PlayerCharacterMasterController>();
				if ((bool)component)
				{
					component.LinkToNetworkUserServer(user);
				}
				else
				{
					Debug.LogError("CharacterMaster has no PlayerCharacterMasterController");
				}
				if (flag)
				{
					Run.onPlayerFirstCreatedServer?.Invoke(this, component);
				}
			}
		}
	}

	[Server]
	public virtual void HandlePlayerFirstEntryAnimation(CharacterBody body, Vector3 spawnPosition, Quaternion spawnRotation)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::HandlePlayerFirstEntryAnimation(RoR2.CharacterBody,UnityEngine.Vector3,UnityEngine.Quaternion)' called on client");
		}
		else if ((bool)body)
		{
			if ((bool)body.preferredPodPrefab)
			{
				GameObject gameObject = UnityEngine.Object.Instantiate(body.preferredPodPrefab, body.transform.position, spawnRotation);
				VehicleSeat component = gameObject.GetComponent<VehicleSeat>();
				if ((bool)component)
				{
					component.AssignPassenger(body.gameObject);
				}
				else
				{
					Debug.LogError("Body " + body.name + " has preferred pod " + body.preferredPodPrefab.name + ", but that object has no VehicleSeat.");
				}
				NetworkServer.Spawn(gameObject);
			}
			else
			{
				body.SetBodyStateToPreferredInitialState();
			}
		}
		else
		{
			Debug.LogError("Can't handle player first entry animation for null body.");
		}
	}

	public virtual void OnServerBossAdded(BossGroup bossGroup, CharacterMaster characterMaster)
	{
	}

	public virtual void OnServerBossDefeated(BossGroup bossGroup)
	{
	}

	public virtual void OnServerCharacterBodySpawned(CharacterBody characterBody)
	{
	}

	public virtual void OnServerTeleporterPlaced(SceneDirector sceneDirector, GameObject teleporter)
	{
	}

	public virtual void OnPlayerSpawnPointsPlaced(SceneDirector sceneDirector)
	{
	}

	public virtual GameObject GetTeleportEffectPrefab(GameObject objectToTeleport)
	{
		return LegacyResourcesAPI.Load<GameObject>("Prefabs/Effects/TeleportOutBoom");
	}

	public int GetDifficultyScaledCost(int baseCost, float difficultyCoefficient)
	{
		return (int)((float)baseCost * Mathf.Pow(difficultyCoefficient, 1.25f));
	}

	public int GetDifficultyScaledCost(int baseCost)
	{
		return GetDifficultyScaledCost(baseCost, instance.difficultyCoefficient);
	}

	public void BuildDropTable()
	{
		availableTier1DropList.Clear();
		availableTier2DropList.Clear();
		availableTier3DropList.Clear();
		availableLunarItemDropList.Clear();
		availableEquipmentDropList.Clear();
		availableBossDropList.Clear();
		availableLunarEquipmentDropList.Clear();
		availableVoidTier1DropList.Clear();
		availableVoidTier2DropList.Clear();
		availableVoidTier3DropList.Clear();
		availableVoidBossDropList.Clear();
		ItemIndex itemIndex = ItemIndex.Count;
		for (ItemIndex itemCount = (ItemIndex)ItemCatalog.itemCount; itemIndex < itemCount; itemIndex++)
		{
			if (availableItems.Contains(itemIndex))
			{
				ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex);
				List<PickupIndex> list = null;
				switch (itemDef.tier)
				{
				case ItemTier.Tier1:
					list = availableTier1DropList;
					break;
				case ItemTier.Tier2:
					list = availableTier2DropList;
					break;
				case ItemTier.Tier3:
					list = availableTier3DropList;
					break;
				case ItemTier.Lunar:
					list = availableLunarItemDropList;
					break;
				case ItemTier.Boss:
					list = availableBossDropList;
					break;
				case ItemTier.VoidTier1:
					list = availableVoidTier1DropList;
					break;
				case ItemTier.VoidTier2:
					list = availableVoidTier2DropList;
					break;
				case ItemTier.VoidTier3:
					list = availableVoidTier3DropList;
					break;
				case ItemTier.VoidBoss:
					list = availableVoidBossDropList;
					break;
				}
				if (list != null && itemDef.DoesNotContainTag(ItemTag.WorldUnique))
				{
					list.Add(PickupCatalog.FindPickupIndex(itemIndex));
				}
			}
		}
		EquipmentIndex equipmentIndex = (EquipmentIndex)0;
		for (EquipmentIndex equipmentCount = (EquipmentIndex)EquipmentCatalog.equipmentCount; equipmentIndex < equipmentCount; equipmentIndex++)
		{
			if (!availableEquipment.Contains(equipmentIndex))
			{
				continue;
			}
			EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef(equipmentIndex);
			if (equipmentDef.canDrop)
			{
				if (!equipmentDef.isLunar)
				{
					availableEquipmentDropList.Add(PickupCatalog.FindPickupIndex(equipmentIndex));
				}
				else
				{
					availableLunarEquipmentDropList.Add(PickupCatalog.FindPickupIndex(equipmentIndex));
				}
			}
		}
		smallChestDropTierSelector.Clear();
		smallChestDropTierSelector.AddChoice(availableTier1DropList, 0.8f);
		smallChestDropTierSelector.AddChoice(availableTier2DropList, 0.2f);
		smallChestDropTierSelector.AddChoice(availableTier3DropList, 0.01f);
		mediumChestDropTierSelector.Clear();
		mediumChestDropTierSelector.AddChoice(availableTier2DropList, 0.8f);
		mediumChestDropTierSelector.AddChoice(availableTier3DropList, 0.2f);
		largeChestDropTierSelector.Clear();
		RefreshLunarCombinedDropList();
	}

	public bool IsItemAvailable(ItemIndex itemIndex)
	{
		return availableItems.Contains(itemIndex);
	}

	public bool IsEquipmentAvailable(EquipmentIndex equipmentIndex)
	{
		return availableEquipment.Contains(equipmentIndex);
	}

	public bool IsItemExpansionLocked(ItemIndex itemIndex)
	{
		return expansionLockedItems.Contains(itemIndex);
	}

	public bool IsEquipmentExpansionLocked(EquipmentIndex equipmentIndex)
	{
		return expansionLockedEquipment.Contains(equipmentIndex);
	}

	public bool IsPickupAvailable(PickupIndex pickupIndex)
	{
		PickupDef pickupDef = PickupCatalog.GetPickupDef(pickupIndex);
		if (pickupDef.itemIndex != ItemIndex.None)
		{
			return IsItemAvailable(pickupDef.itemIndex);
		}
		if (pickupDef.equipmentIndex != EquipmentIndex.None)
		{
			return IsEquipmentAvailable(pickupDef.equipmentIndex);
		}
		return true;
	}

	[Server]
	public void DisablePickupDrop(PickupIndex pickupIndex)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::DisablePickupDrop(RoR2.PickupIndex)' called on client");
			return;
		}
		PickupDef pickupDef = PickupCatalog.GetPickupDef(pickupIndex);
		if (pickupDef.itemIndex != ItemIndex.None)
		{
			DisableItemDrop(pickupDef.itemIndex);
		}
		if (pickupDef.equipmentIndex != EquipmentIndex.None)
		{
			DisableEquipmentDrop(pickupDef.equipmentIndex);
		}
	}

	[Server]
	public void DisableItemDrop(ItemIndex itemIndex)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::DisableItemDrop(RoR2.ItemIndex)' called on client");
			return;
		}
		ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex);
		List<PickupIndex> list = null;
		bool flag = false;
		switch (itemDef.tier)
		{
		case ItemTier.Tier1:
			list = availableTier1DropList;
			break;
		case ItemTier.Tier2:
			list = availableTier2DropList;
			break;
		case ItemTier.Tier3:
			list = availableTier3DropList;
			break;
		case ItemTier.Lunar:
			list = availableLunarItemDropList;
			break;
		case ItemTier.Boss:
			list = availableBossDropList;
			break;
		case ItemTier.VoidTier1:
			list = availableVoidTier1DropList;
			break;
		case ItemTier.VoidTier2:
			list = availableVoidTier2DropList;
			break;
		case ItemTier.VoidTier3:
			list = availableVoidTier3DropList;
			break;
		case ItemTier.VoidBoss:
			list = availableVoidBossDropList;
			break;
		}
		PickupIndex pickupIndex = PickupCatalog.FindPickupIndex(itemIndex);
		if (list != null && pickupIndex != PickupIndex.none)
		{
			list.Remove(pickupIndex);
			if (flag)
			{
				RefreshLunarCombinedDropList();
			}
			Run.onAvailablePickupsModified?.Invoke(this);
		}
	}

	[Server]
	public void DisableEquipmentDrop(EquipmentIndex equipmentIndex)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::DisableEquipmentDrop(RoR2.EquipmentIndex)' called on client");
			return;
		}
		PickupIndex pickupIndex = PickupCatalog.FindPickupIndex(equipmentIndex);
		if (!(pickupIndex != PickupIndex.none))
		{
			return;
		}
		List<PickupIndex> list = null;
		bool flag = false;
		if (PickupCatalog.GetPickupDef(pickupIndex).isLunar)
		{
			flag = true;
			list = availableLunarEquipmentDropList;
		}
		else
		{
			list = availableEquipmentDropList;
		}
		if (list.Contains(pickupIndex))
		{
			list.Remove(pickupIndex);
			if (flag)
			{
				RefreshLunarCombinedDropList();
			}
			Run.onAvailablePickupsModified?.Invoke(this);
		}
	}

	[Server]
	public void EnablePickupDrop(PickupIndex pickupIndex)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::EnablePickupDrop(RoR2.PickupIndex)' called on client");
			return;
		}
		PickupDef pickupDef = PickupCatalog.GetPickupDef(pickupIndex);
		if (pickupDef.itemIndex != ItemIndex.None)
		{
			EnableItemDrop(pickupDef.itemIndex);
		}
		if (pickupDef.equipmentIndex != EquipmentIndex.None)
		{
			EnableEquipmentDrop(pickupDef.equipmentIndex);
		}
	}

	[Server]
	public void EnableItemDrop(ItemIndex itemIndex)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::EnableItemDrop(RoR2.ItemIndex)' called on client");
			return;
		}
		ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex);
		List<PickupIndex> list = null;
		bool flag = false;
		switch (itemDef.tier)
		{
		case ItemTier.Tier1:
			list = availableTier1DropList;
			break;
		case ItemTier.Tier2:
			list = availableTier2DropList;
			break;
		case ItemTier.Tier3:
			list = availableTier3DropList;
			break;
		case ItemTier.Lunar:
			list = availableLunarItemDropList;
			break;
		case ItemTier.Boss:
			list = availableBossDropList;
			break;
		case ItemTier.VoidTier1:
			list = availableVoidTier1DropList;
			break;
		case ItemTier.VoidTier2:
			list = availableVoidTier2DropList;
			break;
		case ItemTier.VoidTier3:
			list = availableVoidTier3DropList;
			break;
		case ItemTier.VoidBoss:
			list = availableVoidBossDropList;
			break;
		}
		PickupIndex pickupIndex = PickupCatalog.FindPickupIndex(itemIndex);
		if (list != null && pickupIndex != PickupIndex.none && !list.Contains(pickupIndex))
		{
			list.Add(pickupIndex);
			if (flag)
			{
				RefreshLunarCombinedDropList();
			}
			Run.onAvailablePickupsModified?.Invoke(this);
		}
	}

	[Server]
	public void EnableEquipmentDrop(EquipmentIndex equipmentIndex)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::EnableEquipmentDrop(RoR2.EquipmentIndex)' called on client");
			return;
		}
		PickupIndex pickupIndex = PickupCatalog.FindPickupIndex(equipmentIndex);
		if (!(pickupIndex != PickupIndex.none))
		{
			return;
		}
		List<PickupIndex> list = null;
		bool flag = false;
		list = ((!PickupCatalog.GetPickupDef(pickupIndex).isLunar) ? availableEquipmentDropList : availableLunarEquipmentDropList);
		if (!list.Contains(pickupIndex))
		{
			list.Add(pickupIndex);
			if (flag)
			{
				RefreshLunarCombinedDropList();
			}
			Run.onAvailablePickupsModified?.Invoke(this);
		}
	}

	[Server]
	private void RefreshLunarCombinedDropList()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::RefreshLunarCombinedDropList()' called on client");
			return;
		}
		availableLunarCombinedDropList.Clear();
		availableLunarCombinedDropList.AddRange(availableLunarEquipmentDropList);
		availableLunarCombinedDropList.AddRange(availableLunarItemDropList);
	}

	[ConCommand(commandName = "run_end", flags = ConVarFlags.SenderMustBeServer, helpText = "Ends the current run.")]
	private static void CCRunEnd(ConCommandArgs args)
	{
		if ((bool)instance)
		{
			UnityEngine.Object.Destroy(instance.gameObject);
		}
	}

	[ConCommand(commandName = "run_print_unlockables", flags = ConVarFlags.SenderMustBeServer, helpText = "Prints all unlockables available in this run.")]
	private static void CCRunPrintUnlockables(ConCommandArgs args)
	{
		if (!instance)
		{
			throw new ConCommandException("No run is currently in progress.");
		}
		List<string> list = new List<string>();
		foreach (UnlockableDef item in instance.unlockablesUnlockedByAnyUser)
		{
			list.Add(item.cachedName);
		}
	}

	[ConCommand(commandName = "run_print_seed", flags = ConVarFlags.None, helpText = "Prints the seed of the current run.")]
	private static void CCRunPrintSeed(ConCommandArgs args)
	{
		if (!instance)
		{
			throw new ConCommandException("No run is currently in progress.");
		}
		Debug.LogFormat("Current run seed: {0}", instance.seed);
	}

	[ConCommand(commandName = "run_set_stages_cleared", flags = (ConVarFlags.ExecuteOnServer | ConVarFlags.Cheat), helpText = "Sets the current number of stages cleared in the run.")]
	private static void CCRunSetStagesCleared(ConCommandArgs args)
	{
		if (!instance)
		{
			throw new ConCommandException("No run is currently in progress.");
		}
		instance.NetworkstageClearCount = args.GetArgInt(0);
	}

	public virtual void AdvanceStage(SceneDef nextScene)
	{
		if ((bool)Stage.instance)
		{
			Stage.instance.CompleteServer();
			SceneType sceneType = SceneCatalog.GetSceneDefForCurrentScene().sceneType;
			if (sceneType == SceneType.Stage || sceneType == SceneType.UntimedStage)
			{
				NetworkstageClearCount = stageClearCount + 1;
			}
		}
		GenerateStageRNG();
		RecalculateDifficultyCoefficent();
		NetworkManager.singleton.ServerChangeScene(nextScene.cachedName);
	}

	public void BeginGameOver([NotNull] GameEndingDef gameEndingDef)
	{
		if (isGameOverServer)
		{
			return;
		}
		if ((bool)Stage.instance && gameEndingDef.isWin)
		{
			Stage.instance.CompleteServer();
		}
		isGameOverServer = true;
		if (gameEndingDef.lunarCoinReward != 0)
		{
			for (int i = 0; i < NetworkUser.readOnlyInstancesList.Count; i++)
			{
				NetworkUser networkUser = NetworkUser.readOnlyInstancesList[i];
				if ((bool)networkUser && networkUser.isParticipating)
				{
					networkUser.AwardLunarCoins(gameEndingDef.lunarCoinReward);
				}
			}
		}
		StatManager.ForceUpdate();
		GameObject obj = UnityEngine.Object.Instantiate(gameOverPrefab);
		GameOverController component = obj.GetComponent<GameOverController>();
		component.SetRunReport(RunReport.Generate(this, gameEndingDef));
		Run.onServerGameOver?.Invoke(this, gameEndingDef);
		NetworkServer.Spawn(obj);
		isRunning = false;
		component.CallRpcClientGameOver();
	}

	public virtual void OnClientGameOver(RunReport runReport)
	{
		isRunning = false;
		Run.onClientGameOverGlobal?.Invoke(this, runReport);
	}

	public virtual void OverrideRuleChoices(RuleChoiceMask mustInclude, RuleChoiceMask mustExclude, ulong runSeed)
	{
		ForceChoice(mustInclude, mustExclude, "Misc.KeepMoneyBetweenStages.Off");
	}

	protected void ForceChoice(RuleChoiceMask mustInclude, RuleChoiceMask mustExclude, RuleChoiceDef choiceDef)
	{
		foreach (RuleChoiceDef choice in choiceDef.ruleDef.choices)
		{
			mustInclude[choice.globalIndex] = false;
			mustExclude[choice.globalIndex] = true;
		}
		mustInclude[choiceDef.globalIndex] = true;
		mustExclude[choiceDef.globalIndex] = false;
	}

	protected void ForceChoice(RuleChoiceMask mustInclude, RuleChoiceMask mustExclude, string choiceDefGlobalName)
	{
		ForceChoice(mustInclude, mustExclude, RuleCatalog.FindChoiceDef(choiceDefGlobalName));
	}

	public virtual Vector3 FindSafeTeleportPosition(CharacterBody characterBody, Transform targetDestination)
	{
		return FindSafeTeleportPosition(characterBody, targetDestination, float.NegativeInfinity, float.NegativeInfinity);
	}

	public virtual Vector3 FindSafeTeleportPosition(CharacterBody characterBody, Transform targetDestination, float idealMinDistance, float idealMaxDistance)
	{
		Vector3 vector = (targetDestination ? targetDestination.position : characterBody.transform.position);
		SpawnCard spawnCard = ScriptableObject.CreateInstance<SpawnCard>();
		spawnCard.hullSize = characterBody.hullClassification;
		spawnCard.nodeGraphType = MapNodeGroup.GraphType.Ground;
		spawnCard.prefab = LegacyResourcesAPI.Load<GameObject>("SpawnCards/HelperPrefab");
		Vector3 result = vector;
		GameObject gameObject = null;
		if (idealMaxDistance > 0f && idealMinDistance < idealMaxDistance)
		{
			gameObject = DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(spawnCard, new DirectorPlacementRule
			{
				placementMode = DirectorPlacementRule.PlacementMode.Approximate,
				minDistance = idealMinDistance,
				maxDistance = idealMaxDistance,
				position = vector
			}, RoR2Application.rng));
		}
		if (!gameObject)
		{
			gameObject = DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(spawnCard, new DirectorPlacementRule
			{
				placementMode = DirectorPlacementRule.PlacementMode.NearestNode,
				position = vector
			}, RoR2Application.rng));
			if ((bool)gameObject)
			{
				result = gameObject.transform.position;
			}
		}
		if ((bool)gameObject)
		{
			UnityEngine.Object.Destroy(gameObject);
		}
		UnityEngine.Object.Destroy(spawnCard);
		return result;
	}

	[Server]
	public void SetEventFlag(string name)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::SetEventFlag(System.String)' called on client");
		}
		else
		{
			eventFlags.Add(name);
		}
	}

	[Server]
	public bool GetEventFlag(string name)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Boolean RoR2.Run::GetEventFlag(System.String)' called on client");
			return false;
		}
		return eventFlags.Contains(name);
	}

	[Server]
	public void ResetEventFlag(string name)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.Run::ResetEventFlag(System.String)' called on client");
		}
		else
		{
			eventFlags.Remove(name);
		}
	}

	public virtual bool ShouldAllowNonChampionBossSpawn()
	{
		return stageClearCount > 0;
	}

	public bool IsExpansionEnabled([NotNull] ExpansionDef expansionDef)
	{
		if (expansionDef.enabledChoice != null)
		{
			return ruleBook.IsChoiceActive(expansionDef.enabledChoice);
		}
		return false;
	}

	protected virtual void OnSeedSet()
	{
	}

	private void UNetVersion()
	{
	}

	public override bool OnSerialize(NetworkWriter writer, bool forceAll)
	{
		if (forceAll)
		{
			GeneratedNetworkCode._WriteNetworkGuid_None(writer, _uniqueId);
			GeneratedNetworkCode._WriteNetworkDateTime_None(writer, startTimeUtc);
			writer.Write(fixedTime);
			GeneratedNetworkCode._WriteRunStopwatch_Run(writer, runStopwatch);
			writer.WritePackedUInt32((uint)stageClearCount);
			writer.WritePackedUInt32((uint)selectedDifficultyInternal);
			return true;
		}
		bool flag = false;
		if ((base.syncVarDirtyBits & (true ? 1u : 0u)) != 0)
		{
			if (!flag)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag = true;
			}
			GeneratedNetworkCode._WriteNetworkGuid_None(writer, _uniqueId);
		}
		if ((base.syncVarDirtyBits & 2u) != 0)
		{
			if (!flag)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag = true;
			}
			GeneratedNetworkCode._WriteNetworkDateTime_None(writer, startTimeUtc);
		}
		if ((base.syncVarDirtyBits & 4u) != 0)
		{
			if (!flag)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag = true;
			}
			writer.Write(fixedTime);
		}
		if ((base.syncVarDirtyBits & 8u) != 0)
		{
			if (!flag)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag = true;
			}
			GeneratedNetworkCode._WriteRunStopwatch_Run(writer, runStopwatch);
		}
		if ((base.syncVarDirtyBits & 0x10u) != 0)
		{
			if (!flag)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag = true;
			}
			writer.WritePackedUInt32((uint)stageClearCount);
		}
		if ((base.syncVarDirtyBits & 0x20u) != 0)
		{
			if (!flag)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag = true;
			}
			writer.WritePackedUInt32((uint)selectedDifficultyInternal);
		}
		if (!flag)
		{
			writer.WritePackedUInt32(base.syncVarDirtyBits);
		}
		return flag;
	}

	public override void OnDeserialize(NetworkReader reader, bool initialState)
	{
		if (initialState)
		{
			_uniqueId = GeneratedNetworkCode._ReadNetworkGuid_None(reader);
			startTimeUtc = GeneratedNetworkCode._ReadNetworkDateTime_None(reader);
			fixedTime = reader.ReadSingle();
			runStopwatch = GeneratedNetworkCode._ReadRunStopwatch_Run(reader);
			stageClearCount = (int)reader.ReadPackedUInt32();
			selectedDifficultyInternal = (int)reader.ReadPackedUInt32();
			return;
		}
		int num = (int)reader.ReadPackedUInt32();
		if (((uint)num & (true ? 1u : 0u)) != 0)
		{
			_uniqueId = GeneratedNetworkCode._ReadNetworkGuid_None(reader);
		}
		if (((uint)num & 2u) != 0)
		{
			startTimeUtc = GeneratedNetworkCode._ReadNetworkDateTime_None(reader);
		}
		if (((uint)num & 4u) != 0)
		{
			fixedTime = reader.ReadSingle();
		}
		if (((uint)num & 8u) != 0)
		{
			runStopwatch = GeneratedNetworkCode._ReadRunStopwatch_Run(reader);
		}
		if (((uint)num & 0x10u) != 0)
		{
			stageClearCount = (int)reader.ReadPackedUInt32();
		}
		if (((uint)num & 0x20u) != 0)
		{
			selectedDifficultyInternal = (int)reader.ReadPackedUInt32();
		}
	}

	public override void PreStartClient()
	{
	}
}
