Decompiled source of ElevatorRedux v1.0.0

plugins\ElevatorRedux.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ElevatorRedux.Compat;
using ElevatorRedux.Net;
using ElevatorRedux.Patches;
using ElevatorRedux.Visibility;
using Extensions;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ElevatorRedux")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Per-floor elevator travel for Gamble With Your Friends")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ElevatorRedux")]
[assembly: AssemblyTitle("ElevatorRedux")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ElevatorRedux
{
	internal static class ElevatorAccess
	{
		private static readonly FieldRef<ElevatorManager, Collider> CheckColliderRef = AccessTools.FieldRefAccess<ElevatorManager, Collider>("checkCollider");

		private static readonly FieldRef<ElevatorManager, List<CasinoFloor>> AllFloorsRef = AccessTools.FieldRefAccess<ElevatorManager, List<CasinoFloor>>("allFloors");

		private static readonly FieldRef<ElevatorManager, int> CurrentFloorIndexRef = AccessTools.FieldRefAccess<ElevatorManager, int>("_currentFloorIndex");

		private static ElevatorManager _cached;

		public static ElevatorManager FindInstance()
		{
			if ((Object)(object)_cached != (Object)null)
			{
				return _cached;
			}
			_cached = Object.FindAnyObjectByType<ElevatorManager>();
			return _cached;
		}

		public static void InvalidateInstance()
		{
			_cached = null;
		}

		public static Collider CheckCollider(ElevatorManager instance)
		{
			if (!((Object)(object)instance == (Object)null))
			{
				return CheckColliderRef.Invoke(instance);
			}
			return null;
		}

		public static List<CasinoFloor> AllFloors(ElevatorManager instance)
		{
			if (!((Object)(object)instance == (Object)null))
			{
				return AllFloorsRef.Invoke(instance);
			}
			return null;
		}

		public static int CurrentFloorIndex(ElevatorManager instance)
		{
			if (!((Object)(object)instance == (Object)null))
			{
				return CurrentFloorIndexRef.Invoke(instance);
			}
			return 0;
		}

		public static void SetCurrentFloorIndex(ElevatorManager instance, int value)
		{
			if (!((Object)(object)instance == (Object)null))
			{
				CurrentFloorIndexRef.Invoke(instance) = value;
			}
		}
	}
	internal static class FloorTracker
	{
		private static readonly Dictionary<uint, int> FloorByNetId = new Dictionary<uint, int>();

		private static readonly HashSet<int> InhabitedScratch = new HashSet<int>();

		private static bool _hasSnapshot;

		public static int CarFloor { get; set; }

		public static int FallbackFloor { get; set; }

		public static int LocalFloor
		{
			get
			{
				NetworkIdentity localPlayer = NetworkClient.localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					return FallbackFloor;
				}
				if (!FloorByNetId.TryGetValue(localPlayer.netId, out var value))
				{
					return FallbackFloor;
				}
				return value;
			}
		}

		public static bool HasSnapshot
		{
			get
			{
				if (!NetworkServer.active)
				{
					return _hasSnapshot;
				}
				return true;
			}
		}

		public static int FloorOf(uint netId)
		{
			if (!FloorByNetId.TryGetValue(netId, out var value))
			{
				return 0;
			}
			return value;
		}

		public static bool TryGetFloor(uint netId, out int floor)
		{
			return FloorByNetId.TryGetValue(netId, out floor);
		}

		public static void SetFloorServer(uint netId, int floor)
		{
			FloorByNetId[netId] = floor;
		}

		public static HashSet<int> InhabitedFloors()
		{
			InhabitedScratch.Clear();
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if (instance?.players == null)
			{
				InhabitedScratch.Add(LocalFloor);
				return InhabitedScratch;
			}
			foreach (PlayerReferences player in instance.players)
			{
				if (!((Object)(object)player?.identity == (Object)null))
				{
					InhabitedScratch.Add(FloorOf(player.identity.netId));
				}
			}
			if (InhabitedScratch.Count == 0)
			{
				InhabitedScratch.Add(LocalFloor);
			}
			return InhabitedScratch;
		}

		public static void BroadcastServer()
		{
			if (!NetworkServer.active)
			{
				return;
			}
			PruneServer();
			FloorSyncMsg floorSyncMsg = BuildSnapshot();
			int num = 0;
			foreach (NetworkConnectionToClient value in NetworkServer.connections.Values)
			{
				if (value != null && (ClientGate.IsModded(value.connectionId) || IsHost(value)))
				{
					((NetworkConnection)value).Send<FloorSyncMsg>(floorSyncMsg, 0);
					num++;
				}
			}
			Log.Debug($"Sent floor map for {FloorByNetId.Count} player(s) to {num} peer(s).");
		}

		public static void SendSnapshotTo(NetworkConnectionToClient conn)
		{
			if (NetworkServer.active && conn != null)
			{
				PruneServer();
				((NetworkConnection)conn).Send<FloorSyncMsg>(BuildSnapshot(), 0);
			}
		}

		private static bool IsHost(NetworkConnectionToClient conn)
		{
			return conn is LocalConnectionToClient;
		}

		private static FloorSyncMsg BuildSnapshot()
		{
			int count = FloorByNetId.Count;
			uint[] array = new uint[count];
			int[] array2 = new int[count];
			int num = 0;
			foreach (KeyValuePair<uint, int> item in FloorByNetId)
			{
				array[num] = item.Key;
				array2[num] = item.Value;
				num++;
			}
			return new FloorSyncMsg
			{
				netIds = array,
				floors = array2
			};
		}

		private static void PruneServer()
		{
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if (instance?.players == null)
			{
				return;
			}
			HashSet<uint> hashSet = new HashSet<uint>();
			foreach (PlayerReferences player in instance.players)
			{
				if ((Object)(object)player?.identity != (Object)null)
				{
					hashSet.Add(player.identity.netId);
				}
			}
			if (hashSet.Count == 0)
			{
				return;
			}
			List<uint> list = null;
			foreach (uint key in FloorByNetId.Keys)
			{
				if (!hashSet.Contains(key))
				{
					(list ?? (list = new List<uint>())).Add(key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (uint item in list)
			{
				FloorByNetId.Remove(item);
			}
			Log.Debug($"Pruned {list.Count} disconnected player(s) from the floor map.");
		}

		public static void ApplySnapshot(FloorSyncMsg msg)
		{
			if (msg.netIds != null && msg.floors != null && !NetworkServer.active)
			{
				FloorByNetId.Clear();
				int num = Math.Min(msg.netIds.Length, msg.floors.Length);
				for (int i = 0; i < num; i++)
				{
					FloorByNetId[msg.netIds[i]] = msg.floors[i];
				}
				_hasSnapshot = true;
			}
		}

		public static void Reset()
		{
			FloorByNetId.Clear();
			InhabitedScratch.Clear();
			ClientGate.Reset();
			CarFloor = 0;
			FallbackFloor = 0;
			_hasSnapshot = false;
		}
	}
	internal static class Log
	{
		private static ManualLogSource _source;

		public static void Init(ManualLogSource source)
		{
			_source = source;
		}

		public static void Info(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogInfo((object)message);
			}
		}

		public static void Warn(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogWarning((object)message);
			}
		}

		public static void Error(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogError((object)message);
			}
		}

		public static void Debug(string message)
		{
			if (ModConfig.VerboseLogging != null && ModConfig.VerboseLogging.Value)
			{
				ManualLogSource source = _source;
				if (source != null)
				{
					source.LogInfo((object)message);
				}
			}
		}
	}
	internal static class ModConfig
	{
		public enum UnmoddedClientPolicy
		{
			Kick,
			VanillaFallback,
			Ignore
		}

		public enum HiddenColliderMode
		{
			Auto,
			ExemptLocalPlayer,
			DisableColliders,
			Never
		}

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> ShowStatusDisplay;

		public static ConfigEntry<bool> KeepHeldItemsOfStayingPlayers;

		public static ConfigEntry<HiddenColliderMode> HiddenFloorColliders;

		public static ConfigEntry<bool> HideRemotePlayersOnOtherFloors;

		public static ConfigEntry<bool> LocalMusicFollowsLocalFloor;

		public static ConfigEntry<bool> RepairMissingGameFeedbacks;

		public static ConfigEntry<bool> FloorAwareGoHomeZone;

		public static ConfigEntry<bool> AnnounceToHost;

		public static ConfigEntry<UnmoddedClientPolicy> UnmoddedClients;

		public static ConfigEntry<bool> FixCaseOpeningLayers;

		public static ConfigEntry<bool> VerboseLogging;

		public static void Bind(ConfigFile cfg)
		{
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. When false the elevator behaves exactly like vanilla.");
			ShowStatusDisplay = cfg.Bind<bool>("General", "ShowStatusDisplay", true, "Show the READY / BUSY elevator indicator when you are near the elevator.");
			LocalMusicFollowsLocalFloor = cfg.Bind<bool>("General", "LocalMusicFollowsLocalFloor", true, "Music playlist follows the floor YOU are on rather than wherever the elevator went.");
			KeepHeldItemsOfStayingPlayers = cfg.Bind<bool>("Fixes", "KeepHeldItemsOfStayingPlayers", true, "Vanilla disables (and force-drops) every item outside the elevator on each trip. With this on, items held by players who are staying behind are left alone.");
			HiddenFloorColliders = cfg.Bind<HiddenColliderMode>("Fixes", "HiddenFloorColliders", HiddenColliderMode.Auto, "What to do about colliders on a floor kept loaded for networking but hidden from you (only ever happens on the host). Auto measures the floor layout and, if floors overlap, exempts just your own body from them so the floor stays solid for the items and NPCs of whoever is on it. DisableColliders is cheaper but drops that floor's items through the world.");
			HideRemotePlayersOnOtherFloors = cfg.Bind<bool>("Fixes", "HideRemotePlayersOnOtherFloors", true, "Hide players who are on a different floor, and stop them colliding with you.");
			RepairMissingGameFeedbacks = cfg.Bind<bool>("Compatibility", "RepairMissingGameFeedbacks", true, "Unrelated to the elevator: fixes the NullReferenceException storm from custom gambling machines (CaseOpening) that have no CasinoGameFeedbacks component. The game tries to self-heal this but its null check is inverted, so it never does. Also restores the win/loss events those machines were losing, because the crash happened before they fired.");
			FloorAwareGoHomeZone = cfg.Bind<bool>("Fixes", "FloorAwareGoHomeZone", false, "The end-of-run vehicle waits for EVERY player to stand in it, including anyone on another floor. Turning this on makes it only wait for people on its own floor. Off by default: a straggler can always call the elevator down, and letting the vehicle leave without them ends the run for them too.");
			UnmoddedClients = cfg.Bind<UnmoddedClientPolicy>("Compatibility", "UnmoddedClients", UnmoddedClientPolicy.Kick, "Host only: what to do when someone joins without ElevatorRedux. They cannot be sent floor updates (Mirror drops clients that receive a message they cannot read), and vanilla's floor RPC will move their world to the elevator's destination even when they stayed behind - so they end up standing in the wrong floor. Kick turns them away. VanillaFallback lets them stay and makes the elevator behave like the base game for everyone until they leave. Ignore lets them stay and be broken.");
			AnnounceToHost = cfg.Bind<bool>("Compatibility", "AnnounceToHost", true, "Tell the host you have the mod, so it knows it may send you floor updates. If the host does NOT have the mod, Mirror will disconnect you for sending a message it cannot read - turn this off to sit quietly in unmodded lobbies with vanilla elevator behaviour. Has no effect when you are the host.");
			FixCaseOpeningLayers = cfg.Bind<bool>("Compatibility", "FixCaseOpeningLayers", true, "CaseOpening gives each machine its own render layer by counting down from 31 and wrapping, without checking whether a layer is already taken or is one the game uses by name. Keeping more floors loaded makes a collision more likely, so this steers it onto layers that are genuinely free. No effect if CaseOpening is not installed.");
			VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Log every floor transition and visibility change. Useful when reporting a bug.");
		}
	}
	internal sealed class ModRuntime : MonoBehaviour
	{
		private const float DisplayDistance = 15f;

		private const float RediscoverInterval = 1f;

		private const float CompatRetryInterval = 3f;

		private const float CompatRetryWindow = 60f;

		private ElevatorManager _elevator;

		private float _nextDiscover;

		private float _nextCompatRetry;

		private bool _handlersRegistered;

		private bool _wasConnected;

		private bool _announced;

		private float _announcedAt;

		private bool _wasTeleporting;

		private GUIStyle _boxStyle;

		private GUIStyle _labelStyle;

		private bool _stylesReady;

		private bool _shouldDraw;

		private bool _busy;

		private int _shownFloor;

		private void OnEnable()
		{
			SceneManager.sceneLoaded += OnSceneLoaded;
		}

		private void OnDisable()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			Log.Debug("Scene '" + ((Scene)(ref scene)).name + "' loaded - dropping cached world state.");
			LocalFloorState.RestoreAll();
			FloorTracker.Reset();
			FloorGeometry.Reset();
			ItemPatches.Reset();
			LimoDoorFix.Reset();
			ElevatorAccess.InvalidateInstance();
			_elevator = null;
			_announced = false;
		}

		private void Update()
		{
			RetryCompatShims();
			bool active = NetworkClient.active;
			if (active && !_wasConnected)
			{
				OnSessionStart();
			}
			else if (!active && _wasConnected)
			{
				OnSessionEnd();
			}
			_wasConnected = active;
			if (!active)
			{
				_shouldDraw = false;
				return;
			}
			if (!_handlersRegistered)
			{
				RegisterHandlers();
			}
			AnnounceIfNeeded();
			if (NetworkServer.active)
			{
				ClientGate.Tick();
			}
			if ((Object)(object)_elevator == (Object)null && Time.unscaledTime >= _nextDiscover)
			{
				_nextDiscover = Time.unscaledTime + 1f;
				_elevator = ElevatorAccess.FindInstance();
			}
			WatchTripEnd();
			UpdateHudState();
		}

		private void WatchTripEnd()
		{
			if ((Object)(object)_elevator == (Object)null)
			{
				return;
			}
			bool isTeleporting = _elevator.isTeleporting;
			if (_wasTeleporting && !isTeleporting)
			{
				if (ModConfig.Enabled.Value)
				{
					LocalFloorState.Apply();
				}
				LimoDoorFix.Reconcile();
			}
			_wasTeleporting = isTeleporting;
		}

		private void UpdateHudState()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			_shouldDraw = false;
			if (!ModConfig.Enabled.Value || !ModConfig.ShowStatusDisplay.Value || (Object)(object)_elevator == (Object)null)
			{
				return;
			}
			NetworkIdentity localPlayer = NetworkClient.localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				Vector3 val = ((Component)localPlayer).transform.position - ((Component)_elevator).transform.position;
				if (!(((Vector3)(ref val)).sqrMagnitude > 225f))
				{
					_shouldDraw = true;
					_busy = _elevator.isTeleporting;
					_shownFloor = FloorTracker.LocalFloor;
				}
			}
		}

		private void RetryCompatShims()
		{
			if (!(Time.unscaledTime > 60f) && !(Time.unscaledTime < _nextCompatRetry))
			{
				_nextCompatRetry = Time.unscaledTime + 3f;
				if (Plugin.HarmonyInstance != null)
				{
					CompatManager.Retry(Plugin.HarmonyInstance);
				}
			}
		}

		private void OnSessionStart()
		{
			Log.Debug("Session started.");
			RegisterHandlers();
		}

		private void OnSessionEnd()
		{
			if (_announced && !NetworkServer.active && Time.unscaledTime - _announcedAt < 5f)
			{
				Log.Warn("Disconnected right after announcing ElevatorRedux. The host most likely does not have it installed - Mirror drops clients that send a message it has no handler for. Everyone in the lobby needs the mod. To join unmodded lobbies anyway, set AnnounceToHost = false in the config (the elevator will then behave like vanilla).");
			}
			Log.Debug("Session ended - restoring everything we touched.");
			LocalFloorState.RestoreAll();
			FloorTracker.Reset();
			FloorGeometry.Reset();
			ItemPatches.Reset();
			LimoDoorFix.Reset();
			ElevatorAccess.InvalidateInstance();
			CompatManager.Reset();
			_elevator = null;
			_handlersRegistered = false;
			_announced = false;
		}

		private void RegisterHandlers()
		{
			if (NetworkClient.active)
			{
				NetworkClient.ReplaceHandler<FloorSyncMsg>((Action<FloorSyncMsg>)OnFloorSync, true);
				if (NetworkServer.active)
				{
					NetworkServer.ReplaceHandler<HelloMsg>((Action<NetworkConnectionToClient, HelloMsg>)OnHello, true);
				}
				_handlersRegistered = true;
				_announced = false;
				Log.Debug("Network handlers registered.");
			}
		}

		private static void OnHello(NetworkConnectionToClient conn, HelloMsg msg)
		{
			if (msg.version != 1)
			{
				Log.Warn($"A client announced ElevatorRedux protocol v{msg.version} but this host " + $"speaks v{1}. Mod versions do not match.");
			}
			else
			{
				ClientGate.MarkModded(conn);
			}
		}

		private void AnnounceIfNeeded()
		{
			if (!_announced && !NetworkServer.active && NetworkClient.active && NetworkClient.isConnected && ModConfig.AnnounceToHost.Value)
			{
				_announced = true;
				_announcedAt = Time.unscaledTime;
				NetworkClient.Send<HelloMsg>(new HelloMsg
				{
					version = 1
				}, 0);
				Log.Debug("Announced ElevatorRedux to the host.");
			}
		}

		private static void OnFloorSync(FloorSyncMsg msg)
		{
			FloorTracker.ApplySnapshot(msg);
			LocalFloorState.Apply();
		}

		private void OnGUI()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			if (_shouldDraw)
			{
				EnsureStyles();
				Rect val = new Rect((float)Screen.width / 2f - 130f, 20f, 260f, 52f);
				Color backgroundColor = GUI.backgroundColor;
				GUI.backgroundColor = (_busy ? new Color(0.8f, 0.15f, 0.1f, 0.85f) : new Color(0.1f, 0.65f, 0.15f, 0.85f));
				GUI.Box(val, GUIContent.none, _boxStyle);
				GUI.backgroundColor = backgroundColor;
				string arg = (_busy ? "ELEVATOR — BUSY" : "ELEVATOR — READY");
				GUI.Label(val, $"{arg}\nYou are on floor {_shownFloor}", _labelStyle);
			}
		}

		private void EnsureStyles()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			if (!_stylesReady)
			{
				_boxStyle = new GUIStyle(GUI.skin.box);
				_labelStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)4,
					wordWrap = false
				};
				_labelStyle.normal.textColor = Color.white;
				_stylesReady = true;
			}
		}

		private void OnDestroy()
		{
			LocalFloorState.RestoreAll();
		}
	}
	[BepInPlugin("com.callbacked.elevatorredux", "ElevatorRedux", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		internal static Harmony HarmonyInstance { get; private set; }

		private void Awake()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			Log.Init(((BaseUnityPlugin)this).Logger);
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			ModConfig.Enabled.SettingChanged += delegate
			{
				if (!ModConfig.Enabled.Value)
				{
					LocalFloorState.RestoreAll();
				}
			};
			FloorSyncSerializer.Register();
			GameObject val = new GameObject("ElevatorReduxRuntime");
			val.AddComponent<ModRuntime>();
			Object.DontDestroyOnLoad((Object)val);
			_harmony = new Harmony("com.callbacked.elevatorredux");
			HarmonyInstance = _harmony;
			_harmony.PatchAll();
			CompatManager.Initialize(_harmony);
			Log.Info("ElevatorRedux 1.0.0 loaded.");
		}

		private void OnDestroy()
		{
			LocalFloorState.RestoreAll();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class PluginInfo
	{
		public const string Guid = "com.callbacked.elevatorredux";

		public const string Name = "ElevatorRedux";

		public const string Version = "1.0.0";
	}
}
namespace ElevatorRedux.Visibility
{
	internal static class FloorCollisionExemption
	{
		private static readonly Dictionary<CasinoFloor, HashSet<Collider>> Ignored = new Dictionary<CasinoFloor, HashSet<Collider>>();

		public static void Set(CasinoFloor floor, bool exempt)
		{
			if (!((Object)(object)floor == (Object)null))
			{
				if (exempt)
				{
					Refresh(floor);
				}
				else
				{
					Clear(floor);
				}
			}
		}

		public static void Refresh(CasinoFloor floor)
		{
			if ((Object)(object)floor == (Object)null)
			{
				return;
			}
			if (!Ignored.TryGetValue(floor, out var value))
			{
				value = new HashSet<Collider>();
				Ignored[floor] = value;
			}
			int num = 0;
			Collider[] componentsInChildren = ((Component)floor).GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && value.Add(val))
				{
					LocalPlayerColliders.SetIgnored((Collider[])(object)new Collider[1] { val }, ignored: true);
					num++;
				}
			}
			if (num > 0)
			{
				Log.Debug($"Floor {floor.floorIndex} collision exemption: +{num} collider(s).");
			}
		}

		public static void Reapply(CasinoFloor floor)
		{
			if ((Object)(object)floor == (Object)null || !Ignored.TryGetValue(floor, out var value))
			{
				return;
			}
			foreach (Collider item in value)
			{
				if ((Object)(object)item != (Object)null)
				{
					LocalPlayerColliders.SetIgnored((Collider[])(object)new Collider[1] { item }, ignored: true);
				}
			}
		}

		private static void Clear(CasinoFloor floor)
		{
			if (!Ignored.TryGetValue(floor, out var value))
			{
				return;
			}
			foreach (Collider item in value)
			{
				if ((Object)(object)item != (Object)null)
				{
					LocalPlayerColliders.SetIgnored((Collider[])(object)new Collider[1] { item }, ignored: false);
				}
			}
			Ignored.Remove(floor);
		}

		public static void ClearAll()
		{
			foreach (KeyValuePair<CasinoFloor, HashSet<Collider>> item in Ignored)
			{
				foreach (Collider item2 in item.Value)
				{
					if ((Object)(object)item2 != (Object)null)
					{
						LocalPlayerColliders.SetIgnored((Collider[])(object)new Collider[1] { item2 }, ignored: false);
					}
				}
			}
			Ignored.Clear();
		}
	}
	internal static class FloorGeometry
	{
		private static bool _measured;

		private static bool _overlaps;

		private const int SampleLimit = 400;

		public static bool FloorsOverlap(List<CasinoFloor> floors)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if (_measured)
			{
				return _overlaps;
			}
			if (floors == null || floors.Count < 2)
			{
				return false;
			}
			List<Bounds> list = new List<Bounds>();
			foreach (CasinoFloor floor in floors)
			{
				if (!((Object)(object)floor == (Object)null) && TryMeasure(floor, out var bounds))
				{
					list.Add(bounds);
				}
			}
			_overlaps = false;
			for (int i = 0; i < list.Count; i++)
			{
				if (_overlaps)
				{
					break;
				}
				for (int j = i + 1; j < list.Count; j++)
				{
					Bounds val = list[i];
					if (((Bounds)(ref val)).Intersects(list[j]))
					{
						_overlaps = true;
						break;
					}
				}
			}
			_measured = true;
			Log.Info(_overlaps ? "Floors share the same physical space, so background floors will be hidden from you locally." : "Floors are physically separate, so background floors can be left alone.");
			return _overlaps;
		}

		private static bool TryMeasure(CasinoFloor floor, out Bounds bounds)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			bounds = default(Bounds);
			Transform[] componentsInChildren = ((Component)floor).GetComponentsInChildren<Transform>(true);
			if (componentsInChildren == null || componentsInChildren.Length == 0)
			{
				return false;
			}
			int num = Mathf.Max(1, componentsInChildren.Length / 400);
			bool flag = false;
			for (int i = 0; i < componentsInChildren.Length; i += num)
			{
				Transform val = componentsInChildren[i];
				if (!((Object)(object)val == (Object)null))
				{
					if (!flag)
					{
						bounds = new Bounds(val.position, Vector3.zero);
						flag = true;
					}
					else
					{
						((Bounds)(ref bounds)).Encapsulate(val.position);
					}
				}
			}
			return flag;
		}

		public static void Reset()
		{
			_measured = false;
			_overlaps = false;
		}
	}
	internal sealed class HiddenState
	{
		private readonly List<KeyValuePair<Renderer, bool>> _renderers = new List<KeyValuePair<Renderer, bool>>();

		private readonly List<KeyValuePair<Light, bool>> _lights = new List<KeyValuePair<Light, bool>>();

		private readonly List<KeyValuePair<Canvas, bool>> _canvases = new List<KeyValuePair<Canvas, bool>>();

		private readonly List<KeyValuePair<Camera, bool>> _cameras = new List<KeyValuePair<Camera, bool>>();

		private readonly List<KeyValuePair<AudioSource, bool>> _audio = new List<KeyValuePair<AudioSource, bool>>();

		private readonly List<KeyValuePair<Collider, bool>> _colliders = new List<KeyValuePair<Collider, bool>>();

		private readonly HashSet<Object> _seen = new HashSet<Object>();

		public bool IsCaptured { get; private set; }

		public void CaptureAndHide(GameObject root, bool includeColliders)
		{
			if ((Object)(object)root == (Object)null)
			{
				return;
			}
			IsCaptured = true;
			Renderer[] componentsInChildren = root.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && _seen.Add((Object)(object)val))
				{
					_renderers.Add(new KeyValuePair<Renderer, bool>(val, val.enabled));
					val.enabled = false;
				}
			}
			Light[] componentsInChildren2 = root.GetComponentsInChildren<Light>(true);
			foreach (Light val2 in componentsInChildren2)
			{
				if (!((Object)(object)val2 == (Object)null) && _seen.Add((Object)(object)val2))
				{
					_lights.Add(new KeyValuePair<Light, bool>(val2, ((Behaviour)val2).enabled));
					((Behaviour)val2).enabled = false;
				}
			}
			Canvas[] componentsInChildren3 = root.GetComponentsInChildren<Canvas>(true);
			foreach (Canvas val3 in componentsInChildren3)
			{
				if (!((Object)(object)val3 == (Object)null) && _seen.Add((Object)(object)val3))
				{
					_canvases.Add(new KeyValuePair<Canvas, bool>(val3, ((Behaviour)val3).enabled));
					((Behaviour)val3).enabled = false;
				}
			}
			Camera[] componentsInChildren4 = root.GetComponentsInChildren<Camera>(true);
			foreach (Camera val4 in componentsInChildren4)
			{
				if (!((Object)(object)val4 == (Object)null) && _seen.Add((Object)(object)val4))
				{
					_cameras.Add(new KeyValuePair<Camera, bool>(val4, ((Behaviour)val4).enabled));
					((Behaviour)val4).enabled = false;
				}
			}
			AudioSource[] componentsInChildren5 = root.GetComponentsInChildren<AudioSource>(true);
			foreach (AudioSource val5 in componentsInChildren5)
			{
				if (!((Object)(object)val5 == (Object)null) && _seen.Add((Object)(object)val5))
				{
					_audio.Add(new KeyValuePair<AudioSource, bool>(val5, val5.mute));
					val5.mute = true;
				}
			}
			if (!includeColliders)
			{
				return;
			}
			Collider[] componentsInChildren6 = root.GetComponentsInChildren<Collider>(true);
			foreach (Collider val6 in componentsInChildren6)
			{
				if (!((Object)(object)val6 == (Object)null) && _seen.Add((Object)(object)val6))
				{
					_colliders.Add(new KeyValuePair<Collider, bool>(val6, val6.enabled));
					val6.enabled = false;
				}
			}
		}

		public void Restore()
		{
			if (!IsCaptured)
			{
				return;
			}
			foreach (KeyValuePair<Renderer, bool> renderer in _renderers)
			{
				if ((Object)(object)renderer.Key != (Object)null)
				{
					renderer.Key.enabled = renderer.Value;
				}
			}
			foreach (KeyValuePair<Light, bool> light in _lights)
			{
				if ((Object)(object)light.Key != (Object)null)
				{
					((Behaviour)light.Key).enabled = light.Value;
				}
			}
			foreach (KeyValuePair<Canvas, bool> canvase in _canvases)
			{
				if ((Object)(object)canvase.Key != (Object)null)
				{
					((Behaviour)canvase.Key).enabled = canvase.Value;
				}
			}
			foreach (KeyValuePair<Camera, bool> camera in _cameras)
			{
				if ((Object)(object)camera.Key != (Object)null)
				{
					((Behaviour)camera.Key).enabled = camera.Value;
				}
			}
			foreach (KeyValuePair<AudioSource, bool> item in _audio)
			{
				if ((Object)(object)item.Key != (Object)null)
				{
					item.Key.mute = item.Value;
				}
			}
			foreach (KeyValuePair<Collider, bool> collider in _colliders)
			{
				if ((Object)(object)collider.Key != (Object)null)
				{
					collider.Key.enabled = collider.Value;
				}
			}
			_renderers.Clear();
			_lights.Clear();
			_canvases.Clear();
			_cameras.Clear();
			_audio.Clear();
			_colliders.Clear();
			_seen.Clear();
			IsCaptured = false;
		}
	}
	internal static class LocalFloorState
	{
		private static readonly Dictionary<CasinoFloor, HiddenState> Hidden = new Dictionary<CasinoFloor, HiddenState>();

		private static readonly HashSet<int> Desired = new HashSet<int>();

		public static bool FloorControlStarted { get; private set; }

		public static void BeginFloorControl()
		{
			FloorControlStarted = true;
		}

		public static void Apply()
		{
			if (!ModConfig.Enabled.Value || !FloorControlStarted)
			{
				return;
			}
			ElevatorManager val = ElevatorAccess.FindInstance();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			List<CasinoFloor> list = ElevatorAccess.AllFloors(val);
			if (list == null || list.Count == 0)
			{
				return;
			}
			int localFloor = FloorTracker.LocalFloor;
			Desired.Clear();
			Desired.Add(localFloor);
			if (NetworkServer.active)
			{
				foreach (int item in FloorTracker.InhabitedFloors())
				{
					Desired.Add(item);
				}
			}
			ModConfig.HiddenColliderMode hiddenColliderMode = ResolveColliderMode(list);
			foreach (CasinoFloor item2 in list)
			{
				if ((Object)(object)item2 == (Object)null)
				{
					continue;
				}
				bool num = Desired.Contains(item2.floorIndex);
				bool flag = item2.floorIndex == localFloor;
				if (!num)
				{
					Reveal(item2);
					if (((Component)item2).gameObject.activeSelf)
					{
						((Component)item2).gameObject.SetActive(false);
					}
					continue;
				}
				bool flag2 = !((Component)item2).gameObject.activeSelf;
				if (flag2)
				{
					((Component)item2).gameObject.SetActive(true);
				}
				if (flag)
				{
					Reveal(item2);
				}
				else
				{
					Conceal(item2, hiddenColliderMode == ModConfig.HiddenColliderMode.DisableColliders);
					if (hiddenColliderMode == ModConfig.HiddenColliderMode.Auto || hiddenColliderMode == ModConfig.HiddenColliderMode.ExemptLocalPlayer)
					{
						FloorCollisionExemption.Set(item2, exempt: true);
						if (flag2)
						{
							FloorCollisionExemption.Reapply(item2);
						}
					}
				}
				item2.SetSfxTrigger(flag);
			}
			ApplyLightVolume(localFloor);
			PlayerVisibility.Apply(localFloor);
			if (ModConfig.VerboseLogging.Value)
			{
				Log.Debug(string.Format("Applied local state: floor {0}, loaded [{1}].", localFloor, string.Join(",", ToStrings(Desired))));
			}
		}

		private static ModConfig.HiddenColliderMode ResolveColliderMode(List<CasinoFloor> allFloors)
		{
			ModConfig.HiddenColliderMode value = ModConfig.HiddenFloorColliders.Value;
			if (value != ModConfig.HiddenColliderMode.Auto)
			{
				return value;
			}
			if (!FloorGeometry.FloorsOverlap(allFloors))
			{
				return ModConfig.HiddenColliderMode.Never;
			}
			return ModConfig.HiddenColliderMode.Auto;
		}

		public static bool IsConcealed(CasinoFloor floor)
		{
			if ((Object)(object)floor != (Object)null && Hidden.TryGetValue(floor, out var value))
			{
				return value.IsCaptured;
			}
			return false;
		}

		private static void Conceal(CasinoFloor floor, bool includeColliders)
		{
			if (!Hidden.TryGetValue(floor, out var value))
			{
				value = new HiddenState();
				Hidden[floor] = value;
			}
			bool num = !value.IsCaptured;
			value.CaptureAndHide(((Component)floor).gameObject, includeColliders);
			if (num)
			{
				Log.Debug($"Concealed floor {floor.floorIndex} (loaded for networking, not mine).");
			}
		}

		private static void Reveal(CasinoFloor floor)
		{
			FloorCollisionExemption.Set(floor, exempt: false);
			if (Hidden.TryGetValue(floor, out var value))
			{
				value.Restore();
				Hidden.Remove(floor);
				Log.Debug($"Revealed floor {floor.floorIndex}.");
			}
		}

		private static void ApplyLightVolume(int floorIndex)
		{
			LightbakerManager instance = MonoSingleton<LightbakerManager>.Instance;
			if (instance?.lightVolumes != null && floorIndex >= 0 && floorIndex < instance.lightVolumes.Count)
			{
				instance.SetLightVolume(floorIndex);
			}
		}

		public static void RevealAllLoaded()
		{
			foreach (HiddenState value in Hidden.Values)
			{
				value.Restore();
			}
			Hidden.Clear();
		}

		public static void RestoreAll()
		{
			FloorControlStarted = false;
			FloorCollisionExemption.ClearAll();
			foreach (HiddenState value in Hidden.Values)
			{
				value.Restore();
			}
			Hidden.Clear();
			PlayerVisibility.RestoreAll();
			LocalPlayerColliders.Invalidate();
		}

		private static string[] ToStrings(HashSet<int> values)
		{
			string[] array = new string[values.Count];
			int num = 0;
			foreach (int value in values)
			{
				array[num++] = value.ToString();
			}
			return array;
		}
	}
	internal static class LocalPlayerColliders
	{
		private static Collider[] _colliders;

		private static uint _cachedFor;

		public static Collider[] Get()
		{
			NetworkIdentity localPlayer = NetworkClient.localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				_colliders = null;
				_cachedFor = 0u;
				return null;
			}
			if (_colliders != null && _cachedFor == localPlayer.netId)
			{
				return _colliders;
			}
			_colliders = ((Component)localPlayer).GetComponentsInChildren<Collider>(true);
			_cachedFor = localPlayer.netId;
			return _colliders;
		}

		public static void Invalidate()
		{
			_colliders = null;
			_cachedFor = 0u;
		}

		public static void SetIgnored(Collider[] others, bool ignored)
		{
			Collider[] array = Get();
			if (array == null || others == null)
			{
				return;
			}
			Collider[] array2 = array;
			foreach (Collider val in array2)
			{
				if ((Object)(object)val == (Object)null || !val.enabled || !((Component)val).gameObject.activeInHierarchy)
				{
					continue;
				}
				foreach (Collider val2 in others)
				{
					if (!((Object)(object)val2 == (Object)null) && val2.enabled && ((Component)val2).gameObject.activeInHierarchy)
					{
						Physics.IgnoreCollision(val, val2, ignored);
					}
				}
			}
		}
	}
	internal static class PlayerVisibility
	{
		private static readonly Dictionary<uint, HiddenState> Hidden = new Dictionary<uint, HiddenState>();

		private static readonly Dictionary<uint, Collider[]> IgnoredColliders = new Dictionary<uint, Collider[]>();

		public static bool IsHidden(uint netId)
		{
			if (Hidden.TryGetValue(netId, out var value))
			{
				return value.IsCaptured;
			}
			return false;
		}

		public static void Apply(int localFloor)
		{
			if (!ModConfig.HideRemotePlayersOnOtherFloors.Value)
			{
				RestoreAll();
				return;
			}
			if (!FloorTracker.HasSnapshot)
			{
				RestoreAll();
				return;
			}
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if (instance?.players == null)
			{
				return;
			}
			NetworkIdentity localPlayer = NetworkClient.localPlayer;
			foreach (PlayerReferences player in instance.players)
			{
				NetworkIdentity val = player?.identity;
				if (!((Object)(object)val == (Object)null) && (!((Object)(object)localPlayer != (Object)null) || !((Object)(object)val == (Object)(object)localPlayer)))
				{
					if (FloorTracker.FloorOf(val.netId) == localFloor)
					{
						Show(val);
					}
					else
					{
						Hide(val);
					}
				}
			}
			PruneDeparted(instance);
		}

		private static void Hide(NetworkIdentity identity)
		{
			SetCollisionIgnored(identity, ignored: true);
			if (!Hidden.TryGetValue(identity.netId, out var value) || !value.IsCaptured)
			{
				if (value == null)
				{
					value = new HiddenState();
					Hidden[identity.netId] = value;
				}
				value.CaptureAndHide(((Component)identity).gameObject, includeColliders: false);
				Log.Debug($"Hid player {identity.netId} (different floor).");
			}
		}

		private static void Show(NetworkIdentity identity)
		{
			SetCollisionIgnored(identity, ignored: false);
			if (Hidden.TryGetValue(identity.netId, out var value))
			{
				value.Restore();
				Hidden.Remove(identity.netId);
				Log.Debug($"Revealed player {identity.netId} (same floor).");
			}
		}

		private static void SetCollisionIgnored(NetworkIdentity identity, bool ignored)
		{
			if (!IgnoredColliders.TryGetValue(identity.netId, out var value))
			{
				value = ((Component)identity).GetComponentsInChildren<Collider>(true);
				IgnoredColliders[identity.netId] = value;
			}
			LocalPlayerColliders.SetIgnored(value, ignored);
		}

		private static void PruneDeparted(LocalManager localManager)
		{
			if (Hidden.Count == 0 && IgnoredColliders.Count == 0)
			{
				return;
			}
			HashSet<uint> hashSet = new HashSet<uint>();
			foreach (PlayerReferences player in localManager.players)
			{
				if ((Object)(object)player?.identity != (Object)null)
				{
					hashSet.Add(player.identity.netId);
				}
			}
			Prune(Hidden, hashSet);
			Prune(IgnoredColliders, hashSet);
		}

		private static void Prune<T>(Dictionary<uint, T> map, HashSet<uint> live)
		{
			List<uint> list = null;
			foreach (uint key in map.Keys)
			{
				if (!live.Contains(key))
				{
					(list ?? (list = new List<uint>())).Add(key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (uint item in list)
			{
				map.Remove(item);
			}
		}

		public static void RestoreAll()
		{
			foreach (KeyValuePair<uint, Collider[]> ignoredCollider in IgnoredColliders)
			{
				LocalPlayerColliders.SetIgnored(ignoredCollider.Value, ignored: false);
			}
			IgnoredColliders.Clear();
			foreach (HiddenState value in Hidden.Values)
			{
				value.Restore();
			}
			Hidden.Clear();
		}
	}
}
namespace ElevatorRedux.Patches
{
	internal struct ElevatorTripScope
	{
		public bool WasTeleporting;

		public int PreviousFloorIndex;
	}
	[HarmonyPatch(typeof(ElevatorManager), "CheckPlayersInside")]
	internal static class CheckPlayersInsidePatch
	{
		private static bool Prefix(ElevatorManager __instance, ref bool __result)
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value)
			{
				return true;
			}
			if (!ClientGate.PartialTravelAllowed)
			{
				return true;
			}
			Collider val = ElevatorAccess.CheckCollider(__instance);
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if ((Object)(object)val == (Object)null || instance?.players == null)
			{
				return true;
			}
			__result = false;
			foreach (PlayerReferences player in instance.players)
			{
				if (!((Object)(object)player?.transform == (Object)null))
				{
					Bounds bounds = val.bounds;
					if (((Bounds)(ref bounds)).Contains(player.transform.position))
					{
						__result = true;
						break;
					}
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(ElevatorManager), "ServerTryTeleportPlayers")]
	internal static class ServerTryTeleportPlayersPatch
	{
		private static void Prefix(ElevatorManager __instance, int toIndex, out ElevatorTripScope __state)
		{
			__state = new ElevatorTripScope
			{
				WasTeleporting = ((Object)(object)__instance != (Object)null && __instance.isTeleporting),
				PreviousFloorIndex = ElevatorAccess.CurrentFloorIndex(__instance)
			};
			if (ModConfig.Enabled.Value)
			{
				int value = (FloorTracker.CarFloor = ComputeCarFloor(__instance, toIndex));
				ElevatorAccess.SetCurrentFloorIndex(__instance, value);
			}
		}

		private static void Postfix(ElevatorManager __instance, int toIndex, ElevatorTripScope __state)
		{
			if (ModConfig.Enabled.Value && !((Object)(object)__instance == (Object)null))
			{
				if (!__state.WasTeleporting && __instance.isTeleporting)
				{
					ItemPatches.TripInProgress = true;
				}
				else
				{
					ElevatorAccess.SetCurrentFloorIndex(__instance, __state.PreviousFloorIndex);
				}
			}
		}

		private static int ComputeCarFloor(ElevatorManager elevator, int toIndex)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			Collider val = ElevatorAccess.CheckCollider(elevator);
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if ((Object)(object)val == (Object)null || instance?.players == null)
			{
				return ElevatorAccess.CurrentFloorIndex(elevator);
			}
			bool flag = false;
			foreach (PlayerReferences player in instance.players)
			{
				if ((Object)(object)player?.transform == (Object)null || (Object)(object)player.identity == (Object)null)
				{
					continue;
				}
				Bounds bounds = val.bounds;
				if (((Bounds)(ref bounds)).Contains(player.transform.position))
				{
					flag = true;
					int num = FloorTracker.FloorOf(player.identity.netId);
					if (num != toIndex)
					{
						return num;
					}
				}
			}
			if (!flag)
			{
				return ElevatorAccess.CurrentFloorIndex(elevator);
			}
			return toIndex;
		}
	}
	[HarmonyPatch(typeof(ElevatorManager), "ServerTeleportPlayersOutside")]
	internal static class ServerTeleportPlayersOutsidePatch
	{
		private static bool Prefix(ElevatorManager __instance)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value)
			{
				return true;
			}
			Collider val = ElevatorAccess.CheckCollider(__instance);
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if ((Object)(object)val == (Object)null || instance?.players == null)
			{
				return true;
			}
			ItemPatches.TripInProgress = false;
			int num = ElevatorAccess.CurrentFloorIndex(__instance);
			int num2 = 0;
			int num3 = 0;
			foreach (PlayerReferences player in instance.players)
			{
				NetworkIdentity val2 = player?.identity;
				if ((Object)(object)val2 == (Object)null || (Object)(object)player.transform == (Object)null)
				{
					continue;
				}
				Bounds bounds = val.bounds;
				if (((Bounds)(ref bounds)).Contains(player.transform.position))
				{
					FloorTracker.SetFloorServer(val2.netId, num);
					num2++;
					continue;
				}
				if (!FloorTracker.TryGetFloor(val2.netId, out var _))
				{
					FloorTracker.SetFloorServer(val2.netId, FloorTracker.CarFloor);
				}
				num3++;
			}
			FloorTracker.BroadcastServer();
			Log.Debug($"Trip to floor {num}: {num2} travelled, {num3} stayed behind.");
			return false;
		}
	}
	[HarmonyPatch(typeof(ElevatorManager), "UserCode_RpcSetActiveFloorOnly__Int32")]
	internal static class RpcSetActiveFloorOnlyPatch
	{
		private static bool _logged;

		private static bool Prefix(int index)
		{
			if (!ModConfig.Enabled.Value)
			{
				return true;
			}
			if (!_logged)
			{
				_logged = true;
				Log.Info("Floor control active on this peer (" + (NetworkServer.active ? "host" : "client") + ").");
			}
			FloorTracker.FallbackFloor = index;
			LocalFloorState.BeginFloorControl();
			LocalFloorState.Apply();
			return false;
		}
	}
	[HarmonyPatch(typeof(ElevatorManager), "ServerForceTeleportPlayers")]
	internal static class ServerForceTeleportPlayersPatch
	{
		private static void Prefix(ElevatorManager __instance, int toIndex, out bool __state)
		{
			__state = false;
			if (!((Object)(object)__instance == (Object)null))
			{
				if (ElevatorAccess.CurrentFloorIndex(__instance) != toIndex)
				{
					__state = true;
				}
				else if (ModConfig.Enabled.Value && NetworkServer.active && AnyoneAwayFrom(toIndex))
				{
					ElevatorAccess.SetCurrentFloorIndex(__instance, (toIndex > 0) ? (toIndex - 1) : (toIndex + 1));
					__state = true;
					Log.Debug($"Forcing the gather to floor {toIndex} - the party is split.");
				}
			}
		}

		private static bool AnyoneAwayFrom(int floor)
		{
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if (instance?.players == null)
			{
				return false;
			}
			foreach (PlayerReferences player in instance.players)
			{
				if (!((Object)(object)player?.identity == (Object)null) && FloorTracker.FloorOf(player.identity.netId) != floor)
				{
					return true;
				}
			}
			return false;
		}

		private static void Postfix(int toIndex, bool __state)
		{
			if (!__state || !ModConfig.Enabled.Value || !NetworkServer.active)
			{
				return;
			}
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if (instance?.players == null)
			{
				return;
			}
			foreach (PlayerReferences player in instance.players)
			{
				if (!((Object)(object)player?.identity == (Object)null))
				{
					FloorTracker.SetFloorServer(player.identity.netId, toIndex);
				}
			}
			FloorTracker.CarFloor = toIndex;
			FloorTracker.BroadcastServer();
			Log.Debug($"Forced everyone to floor {toIndex}.");
		}
	}
	[HarmonyPatch(typeof(ElevatorManager), "UserCode_RpcSetMusicPlaylistInAdvance__Int32")]
	internal static class RpcSetMusicPlaylistPatch
	{
		private static void Prefix(ref int index)
		{
			if (ModConfig.Enabled.Value && ModConfig.LocalMusicFollowsLocalFloor.Value)
			{
				index = FloorTracker.LocalFloor;
			}
		}
	}
	[HarmonyPatch(typeof(LocalManager), "RegisterPlayer")]
	internal static class RegisterPlayerPatch
	{
		private static void Postfix(NetworkIdentity identity)
		{
			if (!ModConfig.Enabled.Value || (Object)(object)identity == (Object)null)
			{
				return;
			}
			if (NetworkServer.active)
			{
				ElevatorManager val = ElevatorAccess.FindInstance();
				int floor = (((Object)(object)val != (Object)null) ? ElevatorAccess.CurrentFloorIndex(val) : 0);
				if (!FloorTracker.TryGetFloor(identity.netId, out var _))
				{
					FloorTracker.SetFloorServer(identity.netId, floor);
				}
				FloorTracker.BroadcastServer();
			}
			LocalFloorState.Apply();
		}
	}
	[HarmonyPatch(typeof(LocalManager), "UnregisterPlayer")]
	internal static class UnregisterPlayerPatch
	{
		private static void Postfix()
		{
			if (ModConfig.Enabled.Value)
			{
				if (NetworkServer.active)
				{
					FloorTracker.BroadcastServer();
				}
				LocalFloorState.Apply();
			}
		}
	}
	[HarmonyPatch(typeof(GameBase), "UserCode_RpcEndGameFeedBacks__Double")]
	internal static class EndGameFeedbacksRepair
	{
		private static readonly FieldRef<GameBase, CasinoGameFeedbacks> FeedbacksRef = AccessTools.FieldRefAccess<GameBase, CasinoGameFeedbacks>("gameFeedbacks");

		private static readonly FieldRef<GameBase, UnityEvent> ProfitRef = AccessTools.FieldRefAccess<GameBase, UnityEvent>("onProfitEvent");

		private static readonly FieldRef<GameBase, UnityEvent> LossRef = AccessTools.FieldRefAccess<GameBase, UnityEvent>("onLossEvent");

		private static readonly FieldRef<GameBase, UnityEvent> TieRef = AccessTools.FieldRefAccess<GameBase, UnityEvent>("onTieEvent");

		private static bool _warned;

		private static bool Prefix(GameBase __instance, double multiplier)
		{
			if (!ModConfig.RepairMissingGameFeedbacks.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			CasinoGameFeedbacks val = FeedbacksRef.Invoke(__instance);
			if ((Object)(object)val == (Object)null)
			{
				CasinoGameFeedbacks val2 = ((Component)__instance).GetComponent<CasinoGameFeedbacks>();
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = ((Component)__instance).GetComponentInChildren<CasinoGameFeedbacks>(true);
				}
				if ((Object)(object)val2 != (Object)null)
				{
					val = val2;
					FeedbacksRef.Invoke(__instance) = val2;
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				return true;
			}
			if (!_warned)
			{
				_warned = true;
				Log.Warn("'" + ((Object)__instance).name + "' has no CasinoGameFeedbacks component, so its end-of-round feedback cannot play. This is a bug in a custom machine mod, not in ElevatorRedux. Running the round's win/loss events without it - they were being skipped entirely before, because the game threw before reaching them. Reported once per session.");
			}
			try
			{
				if (multiplier > 1.0)
				{
					UnityEvent obj = ProfitRef.Invoke(__instance);
					if (obj != null)
					{
						obj.Invoke();
					}
				}
				else if (multiplier < 1.0)
				{
					UnityEvent obj2 = LossRef.Invoke(__instance);
					if (obj2 != null)
					{
						obj2.Invoke();
					}
				}
				else
				{
					UnityEvent obj3 = TieRef.Invoke(__instance);
					if (obj3 != null)
					{
						obj3.Invoke();
					}
				}
			}
			catch (Exception ex)
			{
				Log.Warn("End-of-round event on '" + ((Object)__instance).name + "' threw: " + ex.Message);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(AllPlayersTriggerZone), "CheckPlayers")]
	internal static class GoHomeZonePatch
	{
		private static readonly FieldRef<AllPlayersTriggerZone, Collider> CheckColliderRef = AccessTools.FieldRefAccess<AllPlayersTriggerZone, Collider>("checkCollider");

		private static void Prefix(AllPlayersTriggerZone __instance, out List<PlayerReferences> __state)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			__state = null;
			if (!ModConfig.Enabled.Value || !ModConfig.FloorAwareGoHomeZone.Value || !NetworkServer.active || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			List<PlayerReferences> list = instance?.players;
			if (list == null || list.Count == 0)
			{
				return;
			}
			Collider val = CheckColliderRef.Invoke(__instance);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Bounds bounds = val.bounds;
			int num = -1;
			foreach (PlayerReferences item in list)
			{
				if (!((Object)(object)item?.transform == (Object)null) && !((Object)(object)item.identity == (Object)null) && ((Bounds)(ref bounds)).Contains(item.transform.position))
				{
					num = FloorTracker.FloorOf(item.identity.netId);
					break;
				}
			}
			if (num < 0)
			{
				return;
			}
			List<PlayerReferences> list2 = new List<PlayerReferences>(list.Count);
			foreach (PlayerReferences item2 in list)
			{
				if (!((Object)(object)item2?.identity == (Object)null) && FloorTracker.FloorOf(item2.identity.netId) == num)
				{
					list2.Add(item2);
				}
			}
			if (list2.Count != list.Count)
			{
				__state = list;
				instance.players = list2;
				Log.Debug($"Go-home zone: ignoring {list.Count - list2.Count} player(s) on other floors.");
			}
		}

		private static Exception Finalizer(Exception __exception, List<PlayerReferences> __state)
		{
			if (__state != null)
			{
				LocalManager instance = MonoSingleton<LocalManager>.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.players = __state;
				}
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(PlayerInteract), "SetTargetInteractable")]
	internal static class InteractionFilterPatch
	{
		private static void Prefix(ref IInteractable interactable)
		{
			if (ModConfig.Enabled.Value && interactable != null)
			{
				IInteractable obj = interactable;
				Component val = (Component)(object)((obj is Component) ? obj : null);
				if (val != null && !((Object)(object)val == (Object)null) && (IsHiddenPlayer(val) || IsOnConcealedFloor(val)))
				{
					interactable = null;
				}
			}
		}

		private static bool IsHiddenPlayer(Component component)
		{
			if (!ModConfig.HideRemotePlayersOnOtherFloors.Value)
			{
				return false;
			}
			NetworkIdentity componentInParent = component.GetComponentInParent<NetworkIdentity>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return PlayerVisibility.IsHidden(componentInParent.netId);
			}
			return false;
		}

		private static bool IsOnConcealedFloor(Component component)
		{
			CasinoFloor componentInParent = component.GetComponentInParent<CasinoFloor>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return LocalFloorState.IsConcealed(componentInParent);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Item), "ServerSetEnabled")]
	internal static class ItemPatches
	{
		public static bool TripInProgress;

		private static bool Prefix(Item __instance, bool isEnabled)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value)
			{
				return true;
			}
			if (!ModConfig.KeepHeldItemsOfStayingPlayers.Value)
			{
				return true;
			}
			if (isEnabled)
			{
				return true;
			}
			if (!TripInProgress)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			try
			{
				PlayerInventory networkHolder = __instance.NetworkHolder;
				if ((Object)(object)networkHolder == (Object)null)
				{
					return true;
				}
				Collider val = ElevatorAccess.CheckCollider(ElevatorAccess.FindInstance());
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				Bounds bounds = val.bounds;
				if (((Bounds)(ref bounds)).Contains(((Component)networkHolder).transform.position))
				{
					return true;
				}
				Log.Debug("Kept '" + ((Object)__instance).name + "' held by a player staying behind.");
				return false;
			}
			catch (Exception ex)
			{
				Log.Warn("Item guard failed, deferring to vanilla: " + ex.Message);
				return true;
			}
		}

		public static void Reset()
		{
			TripInProgress = false;
		}
	}
	internal static class LimoDoorFix
	{
		private static readonly FieldRef<VehicleDoors, Animator> AnimatorRef = AccessTools.FieldRefAccess<VehicleDoors, Animator>("animator");

		private static VehicleDoors _cached;

		public static void Reconcile()
		{
			if (!NetworkClient.active)
			{
				return;
			}
			if ((Object)(object)_cached == (Object)null)
			{
				_cached = Object.FindFirstObjectByType<VehicleDoors>((FindObjectsInactive)1);
			}
			VehicleDoors cached = _cached;
			if (!((Object)(object)cached == (Object)null) && cached.Network_doorsOpen)
			{
				Animator val = AnimatorRef.Invoke(cached);
				if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled)
				{
					val.SetTrigger("openDoors");
					Log.Debug("Re-asserted go-home vehicle doors open (SyncVar hooks skip initial state).");
				}
			}
		}

		public static void Reset()
		{
			_cached = null;
		}
	}
	[HarmonyPatch(typeof(NavMeshManager), "UserCode_RpcInitializeNavMesh")]
	internal static class NavMeshRebuildPatch
	{
		private static void Prefix(out bool __state)
		{
			__state = false;
			if (ModConfig.Enabled.Value && NetworkServer.active)
			{
				__state = true;
				LocalFloorState.RevealAllLoaded();
			}
		}

		private static void Postfix(bool __state)
		{
			if (__state)
			{
				LocalFloorState.Apply();
			}
		}
	}
}
namespace ElevatorRedux.Net
{
	internal static class ClientGate
	{
		private static readonly HashSet<int> Modded = new HashSet<int>();

		private static readonly Dictionary<int, float> FirstSeen = new Dictionary<int, float>();

		private static readonly HashSet<int> Reported = new HashSet<int>();

		private const float AnnounceGrace = 15f;

		public static bool PartialTravelAllowed { get; private set; } = true;

		public static bool IsModded(int connectionId)
		{
			return Modded.Contains(connectionId);
		}

		public static void MarkModded(NetworkConnectionToClient conn)
		{
			if (conn != null && Modded.Add(conn.connectionId))
			{
				Reported.Remove(conn.connectionId);
				Log.Debug($"Connection {conn.connectionId} has ElevatorRedux.");
				FloorTracker.SendSnapshotTo(conn);
				Reevaluate();
			}
		}

		public static void Forget(int connectionId)
		{
			Modded.Remove(connectionId);
			FirstSeen.Remove(connectionId);
			Reported.Remove(connectionId);
			Reevaluate();
		}

		public static void Tick()
		{
			if (!NetworkServer.active)
			{
				return;
			}
			PruneDeadConnections();
			ModConfig.UnmoddedClientPolicy value = ModConfig.UnmoddedClients.Value;
			float unscaledTime = Time.unscaledTime;
			List<NetworkConnectionToClient> list = null;
			foreach (NetworkConnectionToClient value3 in NetworkServer.connections.Values)
			{
				if (value3 == null || IsHost(value3) || Modded.Contains(value3.connectionId))
				{
					continue;
				}
				if (!FirstSeen.TryGetValue(value3.connectionId, out var value2))
				{
					FirstSeen[value3.connectionId] = unscaledTime;
				}
				else if (!(unscaledTime - value2 < 15f) && Reported.Add(value3.connectionId))
				{
					switch (value)
					{
					case ModConfig.UnmoddedClientPolicy.Kick:
						Log.Warn($"Disconnecting connection {value3.connectionId}: no ElevatorRedux. " + "Everyone in the lobby needs it. Set UnmoddedClients to VanillaFallback to let them stay with the elevator behaving like the base game instead.");
						(list ?? (list = new List<NetworkConnectionToClient>())).Add(value3);
						break;
					case ModConfig.UnmoddedClientPolicy.VanillaFallback:
						Log.Warn($"Connection {value3.connectionId} does not have ElevatorRedux. " + "The elevator will behave like the base game until they leave.");
						break;
					default:
						Log.Warn($"Connection {value3.connectionId} does not have ElevatorRedux and " + "will get a broken view of any split floors. This is what UnmoddedClients = Ignore means.");
						break;
					}
				}
			}
			Reevaluate();
			if (list == null)
			{
				return;
			}
			foreach (NetworkConnectionToClient item in list)
			{
				Forget(item.connectionId);
				((NetworkConnection)item).Disconnect();
			}
		}

		private static void Reevaluate()
		{
			if (ModConfig.UnmoddedClients.Value != ModConfig.UnmoddedClientPolicy.VanillaFallback)
			{
				PartialTravelAllowed = true;
				return;
			}
			bool flag = false;
			foreach (NetworkConnectionToClient value in NetworkServer.connections.Values)
			{
				if (value != null && !IsHost(value) && !Modded.Contains(value.connectionId) && Reported.Contains(value.connectionId))
				{
					flag = true;
					break;
				}
			}
			if (PartialTravelAllowed != !flag)
			{
				PartialTravelAllowed = !flag;
				Log.Info(PartialTravelAllowed ? "Everyone has ElevatorRedux - partial elevator travel enabled." : "Someone in the lobby is missing ElevatorRedux - falling back to vanilla travel.");
			}
		}

		private static void PruneDeadConnections()
		{
			if (Modded.Count == 0 && FirstSeen.Count == 0 && Reported.Count == 0)
			{
				return;
			}
			Dictionary<int, NetworkConnectionToClient> connections = NetworkServer.connections;
			Prune(Modded, connections);
			Prune(Reported, connections);
			List<int> list = null;
			foreach (int key in FirstSeen.Keys)
			{
				if (!connections.ContainsKey(key))
				{
					(list ?? (list = new List<int>())).Add(key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (int item in list)
			{
				FirstSeen.Remove(item);
			}
		}

		private static void Prune(HashSet<int> set, Dictionary<int, NetworkConnectionToClient> live)
		{
			if (set.Count == 0)
			{
				return;
			}
			List<int> list = null;
			foreach (int item in set)
			{
				if (!live.ContainsKey(item))
				{
					(list ?? (list = new List<int>())).Add(item);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (int item2 in list)
			{
				set.Remove(item2);
			}
		}

		private static bool IsHost(NetworkConnectionToClient conn)
		{
			return conn is LocalConnectionToClient;
		}

		public static void Reset()
		{
			Modded.Clear();
			FirstSeen.Clear();
			Reported.Clear();
			PartialTravelAllowed = true;
		}
	}
	public struct FloorSyncMsg : NetworkMessage
	{
		public uint[] netIds;

		public int[] floors;
	}
	public struct HelloMsg : NetworkMessage
	{
		public int version;
	}
	internal static class FloorSyncSerializer
	{
		private const int MaxEntries = 512;

		private static bool _registered;

		public const int ProtocolVersion = 1;

		public static void Register()
		{
			if (_registered)
			{
				return;
			}
			_registered = true;
			Writer<FloorSyncMsg>.write = delegate(NetworkWriter writer, FloorSyncMsg msg)
			{
				int num = ((msg.netIds != null) ? msg.netIds.Length : 0);
				if (msg.floors == null || msg.floors.Length < num)
				{
					num = 0;
				}
				NetworkWriterExtensions.WriteInt(writer, num);
				for (int i = 0; i < num; i++)
				{
					NetworkWriterExtensions.WriteUInt(writer, msg.netIds[i]);
					NetworkWriterExtensions.WriteInt(writer, msg.floors[i]);
				}
			};
			Reader<FloorSyncMsg>.read = delegate(NetworkReader reader)
			{
				int num = NetworkReaderExtensions.ReadInt(reader);
				if (num < 0 || num > 512)
				{
					Log.Warn($"Ignoring FloorSyncMsg with implausible entry count {num}.");
					return new FloorSyncMsg
					{
						netIds = new uint[0],
						floors = new int[0]
					};
				}
				uint[] array = new uint[num];
				int[] array2 = new int[num];
				for (int i = 0; i < num; i++)
				{
					array[i] = NetworkReaderExtensions.ReadUInt(reader);
					array2[i] = NetworkReaderExtensions.ReadInt(reader);
				}
				return new FloorSyncMsg
				{
					netIds = array,
					floors = array2
				};
			};
			Writer<HelloMsg>.write = delegate(NetworkWriter writer, HelloMsg msg)
			{
				NetworkWriterExtensions.WriteInt(writer, msg.version);
			};
			Reader<HelloMsg>.read = (NetworkReader reader) => new HelloMsg
			{
				version = NetworkReaderExtensions.ReadInt(reader)
			};
		}
	}
}
namespace ElevatorRedux.Compat
{
	internal static class CaseOpeningCompat
	{
		private const string TypeName = "MoreGamesBase.CaseOpening";

		private const string IsolateMethod = "IsolateCameraAndAssets";

		private const string LayerCounterField = "nextAvailableLayer";

		private const string InstanceLayerField = "instanceLayer";

		private static FieldInfo _layerCounter;

		private static FieldInfo _instanceLayer;

		private static bool _installed;

		private static bool _exhaustedWarned;

		private static readonly Dictionary<int, Component> Owners = new Dictionary<int, Component>();

		public static void TryInstall(Harmony harmony)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			//IL_00c1: Expected O, but got Unknown
			if (_installed || !ModConfig.FixCaseOpeningLayers.Value)
			{
				return;
			}
			Type type = AccessTools.TypeByName("MoreGamesBase.CaseOpening");
			if (type == null)
			{
				return;
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, "IsolateCameraAndAssets", (Type[])null, (Type[])null);
				_layerCounter = AccessTools.Field(type, "nextAvailableLayer");
				_instanceLayer = AccessTools.Field(type, "instanceLayer");
				if (methodInfo == null || _layerCounter == null || _instanceLayer == null)
				{
					Log.Info("CaseOpening found but its layer isolation looks different to what this build knows about - leaving it alone.");
					_installed = true;
				}
				else
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(CaseOpeningCompat), "BeforeIsolate", (Type[])null), new HarmonyMethod(typeof(CaseOpeningCompat), "AfterIsolate", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					_installed = true;
					Log.Info("CaseOpening detected - its per-machine render layers will be kept distinct.");
				}
			}
			catch (Exception ex)
			{
				_installed = true;
				Log.Warn("Could not install the CaseOpening compatibility patch: " + ex.Message);
			}
		}

		private static void BeforeIsolate()
		{
			try
			{
				int num = PickFreeLayer();
				if (num < 0)
				{
					if (!_exhaustedWarned)
					{
						_exhaustedWarned = true;
						Log.Warn("No unused Unity layer is available for a CaseOpening machine. Letting it choose for itself; two machines may render into each other.");
					}
				}
				else
				{
					_layerCounter.SetValue(null, num);
				}
			}
			catch (Exception ex)
			{
				Log.Warn("CaseOpening layer pre-selection failed, deferring to its own choice: " + ex.Message);
			}
		}

		private static void AfterIsolate(object __instance)
		{
			try
			{
				Component val = (Component)((__instance is Component) ? __instance : null);
				if (val != null)
				{
					int num = (int)_instanceLayer.GetValue(__instance);
					if (num >= 0 && num <= 31)
					{
						Owners[num] = val;
					}
				}
			}
			catch
			{
			}
		}

		private static int PickFreeLayer()
		{
			PruneOwners();
			for (int num = 31; num >= 8; num--)
			{
				if (string.IsNullOrEmpty(LayerMask.LayerToName(num)) && !Owners.ContainsKey(num))
				{
					return num;
				}
			}
			return -1;
		}

		private static void PruneOwners()
		{
			if (Owners.Count == 0)
			{
				return;
			}
			List<int> list = null;
			foreach (KeyValuePair<int, Component> owner in Owners)
			{
				if ((Object)(object)owner.Value == (Object)null)
				{
					(list ?? (list = new List<int>())).Add(owner.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (int item in list)
			{
				Owners.Remove(item);
			}
		}

		public static void Reset()
		{
			Owners.Clear();
			_exhaustedWarned = false;
		}
	}
	internal static class CompatManager
	{
		private static bool _reported;

		public static void Initialize(Harmony harmony)
		{
			Report();
			CaseOpeningCompat.TryInstall(harmony);
		}

		public static void Retry(Harmony harmony)
		{
			Report();
			CaseOpeningCompat.TryInstall(harmony);
		}

		private static void Report()
		{
			if (_reported)
			{
				return;
			}
			try
			{
				Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos;
				if (pluginInfos == null || pluginInfos.Count == 0)
				{
					return;
				}
				List<string> list = new List<string>();
				foreach (KeyValuePair<string, PluginInfo> item in pluginInfos)
				{
					if (!(item.Key == "com.callbacked.elevatorredux"))
					{
						PluginInfo value = item.Value;
						BepInPlugin val = ((value != null) ? value.Metadata : null);
						if (val != null)
						{
							list.Add($"{val.Name} {val.Version}");
						}
					}
				}
				_reported = true;
				Log.Info((list.Count == 0) ? "No other plugins loaded." : ("Alongside: " + string.Join(", ", list.ToArray())));
				if (pluginInfos.ContainsKey("com.yourname.elevatormod"))
				{
					Log.Error("The old ElevatorMod is loaded at the same time as ElevatorRedux. They both patch ElevatorManager and will fight each other. Disable one of them.");
				}
			}
			catch (Exception ex)
			{
				_reported = true;
				Log.Debug("Could not enumerate loaded plugins: " + ex.Message);
			}
		}

		public static void Reset()
		{
			CaseOpeningCompat.Reset();
		}
	}
}